flyway 跳过已预知且配置的脚本执行错误
-
前景: 在版本迭代中,过往的表结构可能无法支持新功能,则需要对表结构进行升级优化,拿某大型表为例,某次迭代中一定需要给该表增加一个字段,且该字段需要被设为索引,添加索引的需要数据库对现有数据进行计算维护索引数据结构, 而这张表的数据量是比较庞大的,所以这个升级的时间时比较长的.在服务部署中,flyway脚本执行时间过长可能会导致停机时间超过预期或超过部署等待时间导致被认定为部署失败而被杀掉.为了避免这几种情况的发生,会实现让运维同学将这部分脚本执行掉,但是在部署过程中因为脚本已执行导致报错,需要人为干预再重启,所以增加了该版本.通过提前配置某些允许的错误,在flyway脚本执行中能忽略这些问题,不影响后面未执行的脚本.
-
调研过程: 通过flyway提供的文档配置后发现,该功能为收费功能,而我们使用的是社区版本,该功能智能自己做实现.
- Flyway Pro Edition or Flyway Enterprise Edition upgrade required: errorOverrides is not supported by Flyway Community Edition.
-
实现过程: 通过某段flyway报错信息总结出,flyway的异常是在 at org.flywaydb.core.internal.command.DbMigrate.doMigrateGroup(DbMigrate.java:370)抛出的,
-
private void doMigrateGroup(LinkedHashMap<MigrationInfoImpl, Boolean> group, StopWatch stopWatch) { Context context = new Context() { @Override public Configuration getConfiguration() { return configuration; } @Override public java.sql.Connection getConnection() { return connectionUserObjects.getJdbcConnection(); } }; for (Map.Entry<MigrationInfoImpl, Boolean> entry : group.entrySet()) { final MigrationInfoImpl migration = entry.getKey(); boolean isOutOfOrder = entry.getValue(); final String migrationText = toMigrationText(migration, isOutOfOrder); stopWatch.start(); LOG.info("Migrating " + migrationText); connectionUserObjects.restoreOriginalState(); connectionUserObjects.changeCurrentSchemaTo(schema); try { callbackExecutor.setMigrationInfo(migration); callbackExecutor.onEachMigrateOrUndoEvent(Event.BEFORE_EACH_MIGRATE); try { migration.getResolvedMigration().getExecutor().execute(context); } catch (FlywayException e) { callbackExecutor.onEachMigrateOrUndoEvent(Event.AFTER_EACH_MIGRATE_ERROR); // 抛出了执行异常 throw new FlywayMigrateException(migration, isOutOfOrder, e); } catch (SQLException e) { callbackExecutor.onEachMigrateOrUndoEvent(Event.AFTER_EACH_MIGRATE_ERROR); throw new FlywayMigrateException(migration, isOutOfOrder, e); } LOG.debug("Successfully completed migration of " + migrationText); callbackExecutor.onEachMigrateOrUndoEvent(Event.AFTER_EACH_MIGRATE); } finally { callbackExecutor.setMigrationInfo(null); } stopWatch.stop(); int executionTime = (int) stopWatch.getTotalTimeMillis(); schemaHistory.addAppliedMigration(migration.getVersion(), migration.getDescription(), migration.getType(), migration.getScript(), migration.getResolvedMigration().getChecksum(), executionTime, true); } }
-
那需要优化的点就是在这里了,然后去分析flyway 的错误码配置,发现是有一个state, 和一个errorCode 说明,flyway的异常类里面是有这两个信息的.而去看FlywayException是没有的,再找FlywayException的子类,FlywaySqlException,FlywaySqlScriptException. FlywaySqlScriptException中是有这两个信息的,分析报错发现这类信息是指sql自身没有问题,由于表冲突/字段冲突等会抛出,那就要针对这个异常去做捕获分析.
-
// 判断是否属于这个异常,是的话则判断是否在已配置的异常里 if (e instanceof FlywaySqlScriptException) { if (isWarnException(e)) { return; } } /** * 处理警告异常 只针对警告信息作日志记录,并未实现像文档里可以Debug日志,Info日志.这些如果需要可以再开发,目前看是处 * 理警告信息足够了 * @param e * @return */ private boolean isWarnException(FlywayException e) { FlywaySqlScriptException e1 = (FlywaySqlScriptException) e; SQLException cause = (SQLException) e1.getCause(); String sqlState = cause.getSQLState(); int errorCode = cause.getErrorCode(); String[] errorOverrides = configuration.getErrorOverrides(); if (errorOverrides == null) { return false; } for (String errorOverride : errorOverrides) { ErrorOverride override = ErrorOverride.getFromString(errorOverride); if (override.getState().equals(sqlState) && override.getErrorCode() == errorCode) { if (override.getLevel().equals("W")) { LOG.warn(e.getMessage(), e); return true; } } } return false; }
-
在FluentConfiguration中errorCodes也需要被释放,原先是抛出了不可用异常.
-
ClassicConfiguration
-
@Override public String[] getErrorOverrides() { return errorCodes; } public void setErrorOverrides(String... errorOverrides) { this.errorCodes = errorOverrides; } // 在需要将值赋上 public void configure(Configuration configuration) { ....... setErrorOverrides(configuration.getErrorOverrides()); ...... }
-
-
即可在代码中配置使用.
-
configure.errorOverrides("42S01:1050:W","42000:1061:W","42S21:1060:W");
-
常用异常
-
表已存在: 42S01:1050
索引已存在: 42000:1061
字段已存在: 42S21:1060
-
-
-