Springboot整合liqubase

题记(讲述本文的内容)


一、引入依赖

 <dependency>
    <groupId>org.liquibase</groupId>
    <artifactId>liquibase-core</artifactId>
    <version>3.8.9</version>
 </dependency>

二、添加配置

spring:
  liquibase:
    change-log: classpath:db/master.xml

master.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
        xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
   
    <include file="classpath:db/changelog/v1.0.0.xml" relativeToChangelogFile="false"/>
</databaseChangeLog>

v1.0.0.xml

<?xml version="1.0" encoding="UTF-8"?>

<databaseChangeLog
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
        xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
    <property name="now" value="now()" dbms="mysql,h2"/>
    <property name="now" value="sysdate" dbms="oracle"/>
    <property name="autoIncrement" value="true" dbms="mysql,h2,postgresql,oracle"/>
    <property name="amount" value="decimal(20,2)"/>

    <changeSet id="demo_202207011859001" author="heiyu">
        <createTable tableName="user" remarks="用户表">
            <column name="id" type="bigint">
                <constraints nullable="false" primaryKey="true" primaryKeyName="pk_user"/>
            </column>
            <column name="user_name" type="varchar(50)" remarks="用户名">
                <constraints nullable="false"/>
            </column>
            <column name="password" type="varchar(50)" remarks="密码">
                <constraints nullable="false"/>
            </column>
            <column name="real_name" type="int" remarks="真实姓名">
                <constraints nullable="false"/>
            </column>
            <column name="version_number" type="int" remarks="版本号">
                <constraints nullable="false"/>
            </column>
            <column name="task_date" type="timestamp" remarks="创建时间" defaultValueComputed="CURRENT_TIMESTAMP"/>
            <column name="created_date" type="timestamp" defaultValue="${now}" remarks="创建日期">
                <constraints nullable="false"/>
            </column>
            <column name="created_by" type="bigint" remarks="创建用户ID">
                <constraints nullable="false"/>
            </column>
            <column name="last_updated_date" type="timestamp" defaultValue="${now}" remarks="最后更新日期">
                <constraints nullable="false"/>
            </column>
            <column name="last_updated_by" type="bigint" remarks="最后更新用户ID">
                <constraints nullable="false"/>
            </column>
        </createTable>
    </changeSet>

</databaseChangeLog>

启动项目的时候,程序就会根据配置扫描文件,将配置文件的建表语句写入数据库。



三、数据初始化

当我们进行代码开发的过程中,可能会存在需要初始化数据情况。

一般有两种方式进行数据初始化操作。

1、通过csv文件进行数据初始化

在v1.0.0.xml文件写入配置

<changeSet id="demo_202207071041002" author="heiyu">
        <loadData
                file="config/liquibase/data/user.csv"
                separator=","
                tableName="user">
        </loadData>
    </changeSet>

在config/liquibase/data目录下创建user.csv文件

id,user_name,password,nick_name,status,avatar,phone,email,dept_id,tenant_id,type,gender,is_deleted,create_time,update_time,is_activated
1544612717153771529,root,root,1,null,null,null,null,null,1544860097295970306,1,0,0,2022-07-06 17:23:25,null,1

2、通过customChange进行数据初始化

在v1.0.0.xml文件写入配置

<changeSet id="sys_ua_202207061920001" author="xuejun.zhu" runAlways="false">
        <comment>初始化数据</comment>
        <customChange class="com.demo.init.LiquibaseTaskChange">
            <param name="serviceBeanName" value="initDataService"/>
            <param name="paramClassName" value="com.demo.init.TenantDTO"/>
            <param name="serviceMethodName" value="initInterface"/>
            <param name="file" value="classpath:config/csv/initData.csv"/>
        </customChange>
</changeSet>

LiquibaseTaskChange类

import cn.hutool.core.io.BomReader;
import liquibase.change.custom.CustomTaskChange;
import liquibase.database.Database;
import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.exception.ValidationErrors;
import liquibase.resource.ResourceAccessor;
import liquibase.util.StreamUtil;
import liquibase.util.csv.CSVReader;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.reflection.Reflector;
import org.apache.ibatis.reflection.invoker.Invoker;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Description
 * @Author heiyu
 * @Date 2022-07-06 19:49
 */
public class LiquibaseTaskChange implements CustomTaskChange {
    private String separator = liquibase.util.csv.CSVReader.DEFAULT_SEPARATOR + "";
    /**
     * 文件路径
     */
    private String file;
    /**
     * 调用方法的bean名称
     */
    private String serviceBeanName;
    /**
     * 调用参数的类名
     */
    private String paramClassName;

    private ResourceAccessor resourceAccessor;
    /**
     * 调用方法的名称
     */
    private String serviceMethodName;

    @SuppressWarnings("unchecked")
    @Override
    public void execute(Database database) {
        CSVReader reader = null;
        try {
            reader = getCSVReader();
            if (reader == null) {
                throw new UnexpectedLiquibaseException("Unable to read file " + this.getFile());
            }
            String[] headers = reader.readNext();
            if (headers == null) {
                throw new UnexpectedLiquibaseException("Data file " + getFile() + " was empty");
            }
            // 通过set方法去设置值
            Class domainClass = Class.forName(getParamClassName());
            Reflector reflector = new Reflector(domainClass);
            Map<String, Invoker> invokerMap = new HashMap<>(16);
            Map<String, String> fieldTypeMap = new HashMap<>(16);
            String[] line;
            List result = new ArrayList<>();
            while ((line = reader.readNext()) != null) {
                Object tmp = domainClass.newInstance();
                for (int i = 0; i < headers.length; i++) {
                    String fieldName = headers[i];
                    String value = line[i];
                    if (!invokerMap.containsKey(fieldName)) {
                        invokerMap.put(fieldName, reflector.getSetInvoker(fieldName));
                    }
                    if (!fieldTypeMap.containsKey(fieldName)) {
                        Field field = ReflectionUtils.findField(domainClass, fieldName);
                        if (field != null) {
                            fieldTypeMap.put(fieldName, field.getType().getSimpleName());
                        } else {
                            fieldTypeMap.put(fieldName, null);
                        }
                    }
                    Invoker invoker = invokerMap.get(fieldName);
                    if (invoker != null) {
                        invoker.invoke(tmp, new Object[]{typeConvert(fieldTypeMap.get(fieldName), value)});
                    }
                }
                result.add(tmp);
            }
            if (!CollectionUtils.isEmpty(result)) {
                Object service = ApplicationContextUtils.getApplicationContext().getBean(getServiceBeanName());
                Class<?> aClass = service.getClass();
                Method method = aClass.getMethod(getServiceMethodName(), List.class);
                method.invoke(service, result);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (null != reader) {
                try {
                    reader.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Override
    public String getConfirmationMessage() {
        return null;
    }
    @Override
    public void setUp() {
    }
    @Override
    public void setFileOpener(ResourceAccessor resourceAccessor) {
        this.resourceAccessor = resourceAccessor;
    }
    @Override
    public ValidationErrors validate(Database database) {
        return null;
    }
    private CSVReader getCSVReader() {
        ResourceAccessor resourceAccessor = getResourceAccessor();
        if (resourceAccessor == null) {
            throw new UnexpectedLiquibaseException("No file resourceAccessor specified for " + getFile());
        }
        InputStream stream = null;
        try {
            stream = StreamUtil.openStream(file, false, null, resourceAccessor);
        } catch (IOException e) {
            return null;
        }
        Reader streamReader;
        //  streamReader = new UtfBomAwareReader(stream); 替换
        streamReader = new BomReader(stream);
        if (separator == null) {
            separator = liquibase.util.csv.CSVReader.DEFAULT_SEPARATOR + "";
        }
        char quoteChar = (CSVReader.DEFAULT_QUOTE_CHARACTER + "").charAt(0);

        return new CSVReader(streamReader, separator.charAt(0), quoteChar);
    }

    public String getFile() {
        return file;
    }
    public void setFile(String file) {
        this.file = file;
    }
    public ResourceAccessor getResourceAccessor() {
        return resourceAccessor;
    }
    public void setResourceAccessor(ResourceAccessor resourceAccessor) {
        this.resourceAccessor = resourceAccessor;
    }
    private Object typeConvert(String fieldTypeName, String value) {
        switch (fieldTypeName.toUpperCase()) {
            case "NUMBER":
            case "FLOAT":
                return TypeConversionUtils.parseFloat(value);
            case "DOUBLE":
                return TypeConversionUtils.parseDouble(value);
            case "BIGDECIMAL":
                return new BigDecimal(value);
            case "LONG":
                return TypeConversionUtils.parseLong(value);
            case "STRING":
                return StringUtils.isNotBlank(TypeConversionUtils.parseString(value)) ? TypeConversionUtils.parseString(value) : null;
            case "INT":
            case "INTEGER":
                return TypeConversionUtils.parseInt(value);
            case "BOOLEAN":
                if ("1".equals(value)) {
                    return Boolean.TRUE;
                } else if ("0".equals(value)) {
                    return Boolean.FALSE;
                } else {
                    return TypeConversionUtils.parseBoolean(value);
                }
            case "ZONEDDATETIME":
                return DateUtil.stringToZonedDateTime(value);
            default:
                return null;
        }
    }
    public String getServiceBeanName() {
        return serviceBeanName;
    }

    public void setServiceBeanName(String serviceBeanName) {
        this.serviceBeanName = serviceBeanName;
    }
    public String getParamClassName() {
        return paramClassName;
    }
    public void setParamClassName(String paramClassName) {
        this.paramClassName = paramClassName;
    }
    public String getServiceMethodName() {
        return serviceMethodName;
    }
    public void setServiceMethodName(String serviceMethodName) {
        this.serviceMethodName = serviceMethodName;
    }
    public String getSeparator() {
        return separator;
    }
    public void setSeparator(String separator) {
        if ((separator != null) && "\\t".equals(separator)) {
            separator = "\t";
        }
        this.separator = separator;
    }
}

initDataService类

import cn.hutool.core.collection.CollectionUtil;
import lombok.RequiredArgsConstructor;

import java.util.List;

/**
 * @Description
 * @Author heiyu
 * @Date 2022-07-06 20:10
 */

@RequiredArgsConstructor
public class InitDataService {

    public void initInterface(List<TenantDTO> tenantDTOList){
       // 逻辑处理
    }
}


TenantDTO
一个实体类


initData.csv

name,code
root,root

通过扫描v1.0.0.xml配置文件,加载信息,上面的两种方式都可以初始化数据

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小军的编程之旅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值