Liquibase定义
Liquibase是一个用于跟踪、管理和应用数据库变化的开源的数据库重构工具。它将所有数据库的变化(包括结构和数据)都保存在XML文件中,便于版本控制。
简单来说,liquibase就是数据库的git,用于管理数据库的所有变动
Liquibase功能
-
不依赖于特定的数据库,目前支持包括Oracle/Sql
Server/DB2/MySql/Sybase/PostgreSQL/Caché等12种数据库,这样在数据库的部署和升级环节可帮助应用系统支持多数据库。 -
提供数据库比较功能,比较结果保存在XML中,基于该XML你可用Liquibase轻松部署或升级数据库。
-
以XML存储数据库变化,其中以作者和ID唯一标识一个变化(ChangSet),支持数据库变化的合并,因此支持多开发人员同时工作。
-
在数据库中保存数据库修改历史(DatabaseChangeHistory),在数据库升级时自动跳过已应用的变化(ChangSet)。
-
提供变化应用的回滚功能,可按时间、数量或标签(tag)回滚已应用的变化。通过这种方式,开发人员可轻易的还原数据库在任何时间点的状态。
-
可生成数据库修改文档(HTML格式) 提供数据重构的独立的IDE和Eclipse插件。
Liquibase好处
减少手动管理数据库的效率低下和出错率高的问题,便于数据库版本的更替
它将所有数据库的变化(包括结构和数据)都保存在XML文件中,便于版本控制。
简单示例
一 创建一个spring boot项目
二 引入核心依赖
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<version>4.0.5</version>
</dependency>
三 引入数据库依赖
驱动版本和数据库版本要匹配
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<exclusions>
<exclusion>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
</exclusion>
</exclusions>
</dependency>
四 在application.yml写入数据库连接配置
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/liquibasedemo?useSSL=true&useUnicode=true&characterEncoding=utf8
username: root
password:
liquibase:
# 是否开启liquibase(默认为true)
enabled: true
# 配置文件的路径(默认为classpath:/db/changelog/db.changelog-master.yaml)
change-log: classpath:/db/changelog/changelog-master.xml
# 是否先 drop schema(默认为false)
drop-first: fals
五 添加liquibase的配置类 config/LiquiBaseConfig.java
import liquibase.integration.spring.SpringLiquibase;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class LiquibaseConfig {
@Bean
public SpringLiquibase springLiquibase(DataSource dataSource){
SpringLiquibase springLiquibase = new SpringLiquibase();
springLiquibase.setDataSource(dataSource);
springLiquibase.setChangeLog("classpath:liquibase/liquibase-master.xml");
springLiquibase.setDropFirst(false);
springLiquibase.setShouldRun(true);
return springLiquibase;
}
}
六 在资源目录(resources)下创建liquibase目录,在liquibase目录下创建liquibase-master.xml文件
使用xml文件编写sql
<?xml version="1.0" encoding="utf-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd">
<include file="classpath:liquibase/change_log/2020-02-06-init-schema.xml" relativeToChangelogFile="false"/>
</databaseChangeLog>
使用sql文件编写sql
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<!--
1.file:表示将此路径下 /sql/001_create_person_table.sql 目录中的 changelog 文件引入。
假设 db 目录下还有其他模块(目录),继续通过 <include> 或 <includeAll> 元素引入即可。
2.relativeToChangelogFile:为 true 时表示使用的是相对路径 而不是classpath
-->
<include file="/sql/001_create_person_table.sql" relativeToChangelogFile="true"></include>
<changeSet id="T100-20221009-001" author="txg">
<sqlFile path="/db/changelog/sql/002_create_person1_table.sql"></sqlFile>
</changeSet>
</databaseChangeLog>
七 在资源目录下,创建liquibase目录,liquibase目录下创建change_log目录,change_log目录下创建2020-02-06-init-schema.xml文件,内容如下
(changeSet id属性唯一 同一个id只会被执行一次)
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd">
<property name="autoIncrement" value="true" dbms="mysql"/>
<changeSet id="init-schema" author="Young" >
<comment>init schema</comment>
<createTable tableName="user">
<column name="id" type="bigint" autoIncrement="${autoIncrement}">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="nick_name" type="varchar(255)">
<constraints nullable="false"/>
</column>
<column name="email" type="varchar(255)">
<constraints nullable="false"/>
</column>
<column name="register_time" type="timestamp" defaultValueComputed="CURRENT_TIMESTAMP">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
</databaseChangeLog>
在 src/main/resources/db 创建目录 changelog/sql,用来存放数据库脚本文件;
例如,创建 001_create_person_table.sql 文件
- 每个sql文件必须以-- liquibase formatted sql注释开头
- 每个changeset必须以–changeset author:id注释开头
-- liquibase formatted sql
-- changeset txg:2023_03_05
insert into departments values(1, '研发部');
insert into departments values(2, '销售部');
-- changeset ttt:2023_03_06
insert into departments values(3, '后勤部');
-- changeset ggg:2023_03_06
DELETE FROM departments WHERE DEPARTMENT_NAME = '研发部'
八 启动项目即可完成sql执行 新增表和两张系统表
databasechangelog和databasechangeloglock表
LiquiBase在执行changelog时,会在数据库中插入两张表:DATABASECHANGELOG和DATABASECHANGELOGLOCK,分别记录changelog的执行日志和锁日志。
LiquiBase在执行changelog中的changeSet时,会首先查看DATABASECHANGELOG表,若是已经执行过,则会跳过(除非changeSet的runAlways属性为true),若是没有执行过,则执行并记录changelog日志;
changelog中的一个changeSet对应一个事务,在changeSet执行完后commit,若是出现错误则rollback;
标签的主要属性有:dom
- runAlways:即便已经执行过,仍然每次都执行;注意:
因为DATABASECHANGELOG表中还记录了changeSet的MD5校验值MD5SUM,若是changeSet的id和name没变,而内容变了,则因为MD5值变了,即便runAlways的值为True,执行也是失败的,会报错。这种状况应该使用runOnChange属性。 - runOnChange:第一次的时候执行以及当changeSet的内容发生变化时执行。不受MD5校验值的约束。
- runInTransaction:是否做为一个事务执行,默认为true。设置为false时须要当心:若是执行过程当中出错了则不会rollback,数据库极可能处于不一致的状态;
<changeSet author="cavan" id="22.1.V1-1">
<preConditions onFail="MARK_RAN">
<not>
<tableExists tableName="school"/>
</not>
</preConditions>
<createTable tableName="school">
<!-- int类型 -->
<column name="school_id" type="INT" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<!-- 字符串类型 -->
<column name="school_name" type="VARCHAR(100)">
<constraints nullable="true" unique="false"/>
</column>
<!-- 文本类型 -->
<column name="address" type="TEXT"/>
<!-- boolean类型 使用boolean或者tinyint(1)-->
<column name="is_top_ten" type="boolean" defaultValueBoolean="true"/>
<!-- 方式二
<column defaultValueNumeric="0" name="is_top_ten" type="tinyint(1)"/>
-->
<column defaultValue="anonymity" name="created_by" type="VARCHAR(50)"/>
<column defaultValueComputed="CURRENT_TIMESTAMP" name="created_date" type="TIMESTAMP">
<constraints nullable="false"/>
</column>
<column defaultValueComputed="CURRENT_TIMESTAMP" name="last_modified_date" type="TIMESTAMP">
<constraints nullable="false"/>
</column>
<column defaultValue="anonymity" name="last_modified_by" type="VARCHAR(50)"/>
</createTable>
</changeSet>
<changeSet author="cavan" id="22.1.V1-2">
<preConditions onFail="MARK_RAN">
<not>
<tableExists tableName="school_class"/>
</not>
</preConditions>
<createTable tableName="school_class">
<column name="class_id" type="INT">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="class_name" type="VARCHAR(100)">
<constraints nullable="true"/>
</column>
<column name="school_id" type="INT">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
<!-- 删除表 -->
<changeSet author="cavan" id="22.1.V1-3">
<preConditions>
<tableExists tableName="school_class" />
</preConditions>
<dropTable tableName="school_class" />
</changeSet>
<!-- 修改表名 -->
<changeSet author="cavan" id="22.1.V1-4">
<preConditions>
<tableExists tableName="school_class" />
</preConditions>
<renameTable oldTableName="school_class"
newTableName="class_school"/>
</changeSet>
<!-- ################### 其他相关表操作 ################### -->
<!-- 增加主键,单一主键 -->
<changeSet author="cavan" id="22.1.V2-1">
<addPrimaryKey columnNames="school_id"
constraintName="PRIMARY"
tableName="school"/>
</changeSet>
<!-- 增加主键,联合主键 -->
<changeSet author="cavan" id="22.1.V2-2">
<addPrimaryKey columnNames="school_id, school_name"
constraintName="PRIMARY"
tableName="school"/>
</changeSet>
<!-- 创建索引,删除索引 -->
<changeSet author="cavan" id="22.1.V2-3">
<!-- 创建索引 -->
<createIndex indexName="school_id" tableName="school">
<column name="school"/>
</createIndex>
<!-- 删除索引 -->
<dropIndex indexName="school_id" tableName="school"/>
</changeSet>
<!-- 增加外键约束 -->
<changeSet author="cavan" id="22.1.V2-4">
<addForeignKeyConstraint baseColumnNames="school_id"
baseTableName="school_class"
constraintName="school_class_ibfk_1"
deferrable="false"
initiallyDeferred="false"
onDelete="RESTRICT"
onUpdate="RESTRICT"
referencedColumnNames="school_id"
referencedTableName="school"/>
</changeSet>
<changeSet author="cavan" id="22.1.V2-5">
<sql>
ALTER TABLE school MODIFY COLUMN last_modified_date timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
</sql>
</changeSet>
<!-- ################### Column相关 ################### -->
<!-- 增加字段 -->
<changeSet author="cavan" id="22.1.V3-1">
<addColumn tableName="school_class">
<column name="school_name" type="VARCHAR(100)">
<constraints nullable="false"/>
</column>
</addColumn>
</changeSet>
<!-- 删除字段 -->
<changeSet author="cavan" id="22.1.V3-2">
<dropColumn tableName="school">
<column name="created_by"/>
<column name="created_date"/>
<column name="last_modified_by"/>
<column name="last_modified_date"/>
</dropColumn>
</changeSet>
<!-- 修改字段 -->
<changeSet author="cavan" id="22.1.V3-3">
<!-- 修改字段名称( 其实可以连带类型一起修改了 ) -->
<renameColumn tableName="school" oldColumnName="is_top_ten"
newColumnName="is_top_ten_new" columnDataType="varchar(20)"/>
<!-- 修改字段类型 -->
<modifyDataType tableName="school" columnName="school_name" newDataType="varchar(20)" />
</changeSet>
<!-- ################### 数据相关 ################### -->
<!-- 增删改查数据 -->
<changeSet author="cavan" id="22.1.V4-1">
<insert tableName="school">
<column name="school_id" value="2"/>
<column name="school_name" value="qinghua"/>
<column name="created_by" value="anonymity"/>
<column name="created_date" valueDate="2021-07-20 15:51:53.0"/>
<column name="last_modified_date" valueDate="2021-07-20 15:51:53.0"/>
<column name="last_modified_by" value="anonymity"/>
</insert>
</changeSet>
<changeSet author="cavan" id="22.1.V4-2">
<delete tableName="school">
<where>school_id='2'</where>
</delete>
</changeSet>
<changeSet author="cavan" id="22.1.V4-3">
<update tableName="school">
<column name="school_name" value="beida"/>
<where>school_id='2'</where>
</update>
</changeSet>
<!-- 基于SQL语句 -->
<changeSet author="cavan" id="22.1.V5-1">
<sql>
insert into school (school_id, school_name, is_top_ten) values (1, 'hafu', 1);
</sql>
</changeSet>
<!-- 基于SQL文件 -->
<changeSet author="cavan" id="22.1.V5-2">
<sqlFile path="insert-data.sql"/>
</changeSet>
</databaseChangeLog>
plugin
该插件可以根据数据库逆向生成 changlog 文件,从已有的数据库生成xml配置信息
-
生成xml文件
通过idea的maven功能,找到 liquibase plugin,双击 liquibase:generateChangeLog 选项,执行完成之后就会在 properties 文件中配置的 outputChangeLogFile 路径生成对应的xml文件
-
生成数据库修改文档
双击liquibase plugin面板中的liquibase:dbDoc选项,会生成数据库修改文档,默认会生成到target目录中
-
发布changelog
之前我们对changelog的编辑都需要通过启动项目来运行changelog,有时候我们可能想不重启项目便能将修改发布运行到数据库中
双击liquibase plugin面板中的liquibase:update选项,便可以将修改同步到数据库中
<!-- 接着引入liquibase插件, 如果你的数据库已经有表和数据了可以使用该插件反向生成xml文件 -->
<plugin>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-maven-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<propertyFile>src/main/resources/db/liquibase.properties</propertyFile>
<propertyFileWillOverride>true</propertyFileWillOverride>
<outputChangeLogFile>src/main/resources/liquibase/changelog/changelog_init.xml</outputChangeLogFile>
</configuration>
</plugin>
基本规范和常见问题见:https://blog.csdn.net/weixin_42835409/article/details/129407613
拓展
liquibase管理数据库与tk.mybatis、mybatis-generator结合使用快速生成代码
见:https://blog.csdn.net/nb7474/article/details/83387101
参考地址
- http://www.manongjc.com/detail/39-bphvettmfwuhycm.html
- https://blog.csdn.net/weixin_42835409/article/details/129407613
- https://www.kuangstudy.com/bbs/1486993129252241410
- http://www.manongjc.com/detail/39-bphvettmfwuhycm.html