题记(讲述本文的内容)
一、引入依赖
<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配置文件,加载信息,上面的两种方式都可以初始化数据。