携手创作,共同成长!这是我参与「掘金日新计划 · 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的源码。