携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第6天,点击查看活动详情
关键组成分析
书接上文,下面来分析下liquibase的组成部分。其中最重要的组成是changeLog
文件,changeLog
有四种形式,xml、json、yml、sql ,其中xml模式较为通用,下面以xml作讲解。
具体数据库的更改使用Changesets
作为记录的基本单位,包含ChangeType
来标识操作类型,像是新增一列或者设置主键等等。
ChangeSet
核心是基于changeLog
概念来保证数据库中的元数据和xml中数据相同。每次都检查changeLog
,再往深层看最基础的是ChangeSet
,就是每个xml文件中对数据库的修改标签,例如:<changeSet id="202205131305-03" author="wqy">
。liquibase会解析xml,获取对数据库的操作,逻辑在源码ChangeLogIterator.run()
中
具体配置示例信息如下:
<changeSet id="202110081227-01" author="wyr">
<createTable tableName="remark"
remarks="备注表">
<column name="id" type="varchar(128)" autoIncrement="${autoIncrement}">
<constraints primaryKey="true" nullable="false" />
</column>
<column name="contents" type="varchar(1024)" remarks="内容信息">
<constraints nullable="true" />
</column>
</createTable>
</changeSet>
数据库配置
Liquibase
默认使用Spring中spring.datasource
配置的数据库地址,也可以配置自定义地址spring.liquibase
详细实现可见LiquibaseAutoConfiguration
。
Liquibase
会在数据库中创建两张表:DatabaseChangeLog
、DATABASECHANGELOGLOCK
,分别用于保存修改记录和加锁。两张表的ddl如下。
- DATABASECHANGELOG
CREATE TABLE `DATABASECHANGELOG` (
`ID` varchar(255) NOT NULL,
`AUTHOR` varchar(255) NOT NULL,
`FILENAME` varchar(255) NOT NULL,
`DATEEXECUTED` datetime NOT NULL,
`ORDEREXECUTED` int(11) NOT NULL,
`EXECTYPE` varchar(10) NOT NULL,
`MD5SUM` varchar(35) DEFAULT NULL,
`DESCRIPTION` varchar(255) DEFAULT NULL,
`COMMENTS` varchar(255) DEFAULT NULL,
`TAG` varchar(255) DEFAULT NULL,
`LIQUIBASE` varchar(20) DEFAULT NULL,
`CONTEXTS` varchar(255) DEFAULT NULL,
`LABELS` varchar(255) DEFAULT NULL,
`DEPLOYMENT_ID` varchar(10) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
- DATABASECHANGELOGLOCK
CREATE TABLE `DATABASECHANGELOGLOCK` (
`ID` int(11) NOT NULL,
`LOCKED` bit(1) NOT NULL,
`LOCKGRANTED` datetime DEFAULT NULL,
`LOCKEDBY` varchar(255) DEFAULT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
锁
Liquibase
在数据库中创建的表DATABASECHANGELOGLOCK
用于加锁。
常常将Liquibase
结合springboot
使用,当springboot
启动时,就会检查数据库中是否有这俩张表,没有会进行创建,检查内置表DatabaseChangeLog
是否存在的方法为:checkLiquibaseTables(boolean updateExistingNullChecksums, DatabaseChangeLog databaseChangeLog,Contexts contexts, LabelExpression labelExpression)
。
检查内置表方法源码如下:
public void checkLiquibaseTables(boolean updateExistingNullChecksums, DatabaseChangeLog databaseChangeLog, Contexts contexts, LabelExpression labelExpression) throws LiquibaseException {
ChangeLogHistoryService changeLogHistoryService = ChangeLogHistoryServiceFactory.getInstance().getChangeLogService(getDatabase());
changeLogHistoryService.init();
if (updateExistingNullChecksums) {
changeLogHistoryService.upgradeChecksums(databaseChangeLog, contexts, labelExpression);
}
LockServiceFactory.getInstance().getLockService(getDatabase()).init();
}
Liquibase
脚本执行命令为 liquibase update
,对应源码中的执行方法相应的是waitForLock
,这个方法里面会先获取mysql的锁,就是日志中的Waiting for changelog lock
。调用的方法是SelectFromDatabaseChangeLogLockStatement
,会去找DatabaseChangeLogLock
表中的LOCKED字段值是否为1,值为1获取锁失败,继续执行一定时间的获取锁程序。
waitForLock对应源码如下:
public void waitForLock() throws LockException {
boolean locked = false;
long timeToGiveUp = new Date().getTime() + (getChangeLogLockWaitTime() * 1000 * 60);
while (!locked && new Date().getTime() < timeToGiveUp) {
locked = acquireLock();
if (!locked) {
LogFactory.getLogger().info("Waiting for changelog lock....");
try {
Thread.sleep(getChangeLogLockRecheckTime() * 1000);
} catch (InterruptedException e) {
;
}
}
}
if (!locked) {
DatabaseChangeLogLock[] locks = listLocks();
String lockedBy;
if (locks.length > 0) {
DatabaseChangeLogLock lock = locks[0];
lockedBy = lock.getLockedBy() + " since " + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(lock.getLockGranted());
} else {
lockedBy = "UNKNOWN";
}
throw new LockException("Could not acquire change log lock. Currently locked by " + lockedBy);
}
}
小结
以上,总结了liuqbase
最关键的几个组成部分,且分析了一小段获取数据库表和锁的源码,后面会继续分析liquibase
的源码。