在liquibase的实际使用过程中,发现了不少过往没有注意到的问题,秉承自其所以然的思想,借机阅读了下liquibase的源码,深感代码质量和设计还是相当不错的。
1. 前言
最近部门内部终于开始了对于liquibase的试点,在实际的使用过程中发现了不少困惑点,秉承追根溯源的思想,借机对liquibase源码做了简单的了解。
2. 源码解读
liquibase官方对诸如servlet, spring等都进行集成,但不论前期多么花里胡哨,最终的调用都是落在了Liquibase.update() 上,本文的解读也由此开始。
2.1 Liquibase.update()
public void update(Contexts contexts, LabelExpression labelExpression, boolean checkLiquibaseTables)
throws LiquibaseException {
LockService lockService = LockServiceFactory.getInstance().getLockService(database);
lockService.waitForLock();
changeLogParameters.setContexts(contexts);
changeLogParameters.setLabels(labelExpression);
try {
DatabaseChangeLog changeLog = getDatabaseChangeLog();
if (checkLiquibaseTables) {
checkLiquibaseTables(true, changeLog, contexts, labelExpression);
}
ChangeLogHistoryServiceFactory.getInstance().getChangeLogService(database).generateDeploymentId();
changeLog.validate(database, contexts, labelExpression);
ChangeLogIterator changeLogIterator = getStandardChangelogIterator(contexts, labelExpression, changeLog);
changeLogIterator.run(createUpdateVisitor(), new RuntimeEnvironment(database, contexts, labelExpression));
} finally {
database.setObjectQuotingStrategy(ObjectQuotingStrategy.LEGACY);
try {
lockService.releaseLock();
} catch (LockException e) {
LOG.severe(LogType.LOG, MSG_COULD_NOT_RELEASE_LOCK, e);
}
resetServices();
}
}
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();
}
public void validate(Database database, Contexts contexts, LabelExpression labelExpression)
throws LiquibaseException {
database.setObjectQuotingStrategy(objectQuotingStrategy);
ChangeLogIterator logIterator = new ChangeLogIterator(
this,
new DbmsChangeSetFilter(database),
new ContextChangeSetFilter(contexts),
new LabelChangeSetFilter(labelExpression)
);
ValidatingVisitor validatingVisitor = new ValidatingVisitor(database.getRanChangeSetList());
validatingVisitor.validate(database, this);
logIterator.run(validatingVisitor, new RuntimeEnvironment(database, contexts, labelExpression));
for (String message : validatingVisitor.getWarnings().getMessages()) {
LogService.getLog(getClass()).warning(LogType.LOG, message);
}
if (!validatingVisitor.validationPassed()) {
throw new ValidationFailedException(validatingVisitor);
}
}
2.2 ChangeLogIterator类
在liquibase的启动逻辑中,两次使用到了ChangeLogIterator 类:
- 在liquibase主体启动逻辑
Liquibase.update() 方法中。 - 在
DatabaseChangeLog.validate() 方法中。
ChangeLogIterator 的扩展性主要来源于其构造函数传入的ChangeSetFilter 数组, 以及其run 方法第一个参数ChangeSetVisitor (参见下方的代码讲解)。
public void run(ChangeSetVisitor visitor, RuntimeEnvironment env) throws LiquibaseException {
Logger log = LogService.getLog(getClass());
databaseChangeLog.setRuntimeEnvironment(env);
try (LoggerContext ignored = LogService.pushContext("databaseChangeLog", databaseChangeLog)) {
List<ChangeSet> changeSetList = new ArrayList<>(databaseChangeLog.getChangeSets());
if (visitor.getDirection().equals(ChangeSetVisitor.Direction.REVERSE)) {
Collections.reverse(changeSetList);
}
for (ChangeSet changeSet : changeSetList) {
boolean shouldVisit = true;
Set<ChangeSetFilterResult> reasonsAccepted = new HashSet<>();
Set<ChangeSetFilterResult> reasonsDenied = new HashSet<>();
if (changeSetFilters != null) {
for (ChangeSetFilter filter : changeSetFilters) {
ChangeSetFilterResult acceptsResult = filter.accepts(changeSet);
if (acceptsResult.isAccepted()) {
reasonsAccepted.add(acceptsResult);
} else {
shouldVisit = false;
reasonsDenied.add(acceptsResult);
break;
}
}
}
try (LoggerContext ignored2 = LogService.pushContext("changeSet", changeSet)) {
if (shouldVisit && !alreadySaw(changeSet)) {
visitor.visit(changeSet, databaseChangeLog, env.getTargetDatabase(), reasonsAccepted);
markSeen(changeSet);
} else {
if (visitor instanceof SkippedChangeSetVisitor) {
((SkippedChangeSetVisitor) visitor).skipped(changeSet, databaseChangeLog, env.getTargetDatabase(), reasonsDenied);
}
}
}
}
} finally {
databaseChangeLog.setRuntimeEnvironment(null);
}
}
3. ChangeSet类
对于这个ChangeSet,我们要单独解读下。
- 首先在liquibase中,进行CheckSum的最小维度正是ChangeSet,而不是笔者之前所以为的配置文件级别,而且内置表databasechangelog中的每一条记录也正是一个执行过的ChangeSet。
- 对于一个
ChangeSet 的CheckSum的生成规则,可以参考ChangeSet.generateCheckSum()的具体实现 - 关于ChangeSet的判等标准,可以参见
ChangeSet.equals()的具体实现 ,具体就是通过id, filePath, Author三者共同绝对的。
最后让我们来看看ChangeSet.execute() 方法的实现,可以认为是灵魂所在。
/**
* 这里故意保留了注释
* This method will actually execute each of the changes in the list against the
* specified database.
*
* @return should change set be marked as ran
*/
public ExecType execute(DatabaseChangeLog databaseChangeLog, ChangeExecListener listener, Database database)
throws MigrationFailedException {
if (validationFailed) {
return ExecType.MARK_RAN;
}
long startTime = new Date().getTime();
ExecType execType = null;
boolean skipChange = false;
Executor executor = ExecutorService.getInstance().getExecutor(database);
try {
// set object quoting strategy
database.setObjectQuotingStrategy(objectQuotingStrategy);
// 1. 开启事务
if (database.supportsDDLInTransaction()) {
database.setAutoCommit(!runInTransaction);
}
// 2. 日志输出打印<comment>注释
...
// 3. 判断<preConditions>
try {
if (preconditions != null) {
preconditions.check(database, databaseChangeLog, this, listener);
}
} catch (PreconditionFailedException e) {
......
} catch (PreconditionErrorException e) {
......
database.rollback();
} finally {
database.rollback();
}
// 4. 执行Change
// 诸如 addColumn 正是一个 Change
if (!skipChange) {
for (Change change : changes) {
try {
change.finishInitialization();
} catch (SetupException se) {
throw new MigrationFailedException(this, se);
}
}
log.debug(LogType.LOG, "Reading ChangeSet: " + toString());
for (Change change : getChanges()) {
if ((!(change instanceof DbmsTargetedChange)) || DatabaseList.definitionMatches(((DbmsTargetedChange) change).getDbms(), database, true)) {
if (listener != null) {
listener.willRun(change, this, changeLog, database);
}
if (change.generateStatementsVolatile(database)) {
executor.comment("WARNING The following SQL may change each run and therefore is possibly incorrect and/or invalid:");
}
// 执行实际的数据库DDL操作
database.executeStatements(change, databaseChangeLog, sqlVisitors);
log.info(LogType.LOG, change.getConfirmationMessage());
if (listener != null) {
listener.ran(change, this, changeLog, database);
}
} else {
log.debug(LogType.LOG, "Change " + change.getSerializedObjectName() + " not included for database " + database.getShortName());
}
}
if (runInTransaction) {
database.commit();
}
log.info(LogType.LOG, "ChangeSet " + toString(false) + " ran successfully in " + (new Date().getTime() - startTime + "ms"));
if (execType == null) {
execType = ExecType.EXECUTED;
}
} else {
log.debug(LogType.LOG, "Skipping ChangeSet: " + toString());
}
} catch (Exception e) {
......
} finally {
......
}
return execType;
}
其中涉及到两个关键:
- 整个执行过程中,我们可以通过传入的
ChangeExecListener 实现来扩展自定义逻辑扩展。 - 层级关系: 一个``
DatabaseChangeLog 1:n
ChangeSet 1:n
Change
4. Links
- Liquibase常见问题总结
- liquibase常见操作
- 官方文档 - Understanding Contexts vs. Labels
|