一、liquibase是什么
LiquiBase是一个用于数据库重构和迁移的开源工具,通过日志文件的形式记录数据库的变更,然后执行日志文件中的修改,将数据库更新或回滚到一致的状态。它的目标是提供一种数据库类型无关的解决方案,通过执行schema类型的文件来达到迁移。
其有点主要有以下:
- 支持几乎所有主流的数据库,如MySQL, PostgreSQL, Oracle, Sql Server, DB2等;
- 支持多开发者的协作维护;
- 日志文件支持多种格式,如XML, YAML, JSON, SQL等;
- 支持多种运行方式,如命令行、Spring集成、Maven插件、Gradle插件等。
liquibase 官方文档地址:hhttps://www.liquibase.org/
liquibase 源码下载地址:https://github.com/liquibase/liquibase
版本说明
本次测试使用时DM8版本+liquibase的3.8.10版本。
二、整合liquibase报:dm.jdbc.driver.DMException: 数据类型不匹配
这是因为:整合liquibase会初始化两张表,因为框架不支持所以生成的sql不对,从而导致报错。
三、调整liquibase的源码
下载liquibase源码:https://gitee.com/mirrors_addons/liquibase/tree/v3.8.9/或者去https://github.com/liquibase/liquibase下载
核心思路
继承OracleDatabase实现类复写端口跟驱动包名的部分,以及mustQuoteObjectName方法,加入关键字过滤。(不采用网上的继承AbstractJdbcDatabase类模仿Oracle类的方法去写,因为代码的其他部分有很多对oracle的判断,多达三十多处左右)
1. 新建DM8Database
新建DM8Database类并集成OracleDatabase,并重写以下方法。
package liquibase.database.core;
import liquibase.CatalogAndSchema;
import liquibase.Scope;
import liquibase.database.AbstractJdbcDatabase;
import liquibase.database.DatabaseConnection;
import liquibase.database.OfflineConnection;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.DatabaseException;
import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.exception.ValidationErrors;
import liquibase.executor.ExecutorService;
import liquibase.logging.LogService;
import liquibase.logging.LogType;
import liquibase.statement.DatabaseFunction;
import liquibase.statement.SequenceCurrentValueFunction;
import liquibase.statement.SequenceNextValueFunction;
import liquibase.statement.core.RawCallStatement;
import liquibase.statement.core.RawSqlStatement;
import liquibase.structure.DatabaseObject;
import liquibase.structure.core.Catalog;
import liquibase.structure.core.Index;
import liquibase.structure.core.PrimaryKey;
import liquibase.structure.core.Schema;
import liquibase.util.JdbcUtils;
import liquibase.util.StringUtils;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.util.ResourceBundle.getBundle;
public class DM8Database extends OracleDatabase {
private static final String PRODUCT_NAME = "DM DBMS";
private static final String PRODUCT_SHORT_NAME = "DM DBMS";
private static final String DRIVER_CONN_PERFIX = "jdbc:dm";
private static final String DRIVER_STRING = "dm.jdbc.driver.DmDriver";
private static final List QUOTE_KEYWORDS = Arrays.asList("ABORT", "ABSTRACT", "ACCESSED", "ACCOUNT", "ACROSS", "ACTION", "AFTER", "AGGREGATE", "ALLOW_DATETIME", "ALLOW_IP", "ANALYZE", "ARCHIVE", "ARCHIVEDIR", "ARCHIVELOG", "ARCHIVESTYLE", "ARRAYLEN", "ASENSITIVE", "ASYNCHRONOUS", "AT", "ATTACH", "AUTO", "AUTOEXTEND", "AUTONOMOUS_TRANSACTION", "AVG", "BACKED", "BACKUP", "BACKUPDIR", "BACKUPINFO", "BACKSET", "BADFILE", "BAKFILE", "BASE", "BEFORE", "BEGIN", "BIGINT", "BINARY", "BIT", "BITMAP", "BLOB", "BLOCK", "BOOLEAN", "BRANCH", "BTREE", "BUFFER ", "BUILD ", "BULK", "CACHE", "CALCULATE", "CASCADE", "CASCADED", "CATALOG", "CHAIN", "CHARACTER", "CHARACTERISTICS", "CIPHER", "CLOB", "CLOSE", "COLLATE", "COLLECT", "COLUMNS", "COMMITTED", "COMPILE", "COMPLETE", "COMPRESS", "COMPRESSED", "CONNECT_BY_IS_CYCLE", "CONNECT_BY_ISLEAF", "CONNECT_IDLE_TIME", "CONNECT_TIME", "CONSTANT", "CONSTRAINTS", "CONSTRUCTOR", "CONTEXT", "COPY", "CORRESPONDING", "COUNT", "COUNTER", "CPU_PER_CALL", "CPU_PER_SESSION", "CTLFILE", "CUMULATIVE", "CURRENT_SCHEMA", "CURRENT_USER", "CYCLE", "DANGLING", "DATA", "DATABASE", "DATAFILE", "DATE", "DATETIME", "DAY", "DBFILE", "DDL", "DDL_CLONE", "DEBUG", "DEC", "DEFERRABLE", "DEFERRED", "DEFINER", "DELETING", "DELIMITED", "DELTA", "DEMAND", "DENSE_RANK", "DEREF", "DETACH", "DETERMINISTIC", "DEVICE", "DIAGNOSTICS", "DICTIONARY", "DISCONNECT", "DOWN", "DUMP", "EACH", "ENCRYPT", "ENCRYPTION", " ERROR", "ERRORS", "ESCAPE", "EVENTINFO", "EVENTS", "EXCEPTION", "EXCEPTIONS", "EXCEPTION_INIT", "EXCLUDE", "EXCLUDING", "EXCLUSIVE", "EXTENDS", "EXTERNAL", "EXTERNALLY", "FAILED_LOGIN_ATTEMPS", "FAST", "FIELDS", "FILE", "FILEGROUP", "FILESIZE", "FILLFACTOR", "FINAL", "FOLLOWING", "FORALL", "FORCE", "FREQUENCE", "GLOBAL", "GLOBALLY", "HASH", "HEXTORAW", "HOLD", "HOUR", "HUGE", "IDENTIFIED", "IDENTITY_INSERT", "IMAGE", "IN", "INCLUDE", "INCLUDING", "INCREASE", "INCREMENT", "INDEXES", "INDICES", "INITIAL", "INITIALIZED", "INITIALLY", "INNERID", "INPUT", "INSENSITIVE", "INSERTING", "INSTANTIABLE", "INSTEAD", "INTEGER", "INTENT", "INVISIBLE", "ISOLATION", "JOB", "KEY", "LABEL", "LARGE", "LAST", "LESS", "LEVEL", "LEXER", "LIMIT", "LOB", "LOCAL", "LOCALLY", "LOCK", "LOCKED", "LOG", "LOGFILE", "LOGGING", "LOGIC", "LOGOFF", "LOGON", "LOGOUT", "LONG", "LONGVARBINARY", "LONGVARCHAR", "LSN", "MANUAL", "MAP", "MAPPED", "MATCH", "MATCHED", "MATERIALIZED", "MAX", "MAXPIECESIZE", "MAXSIZE", "MAXVALUE", "MEMORY", "MEM_SPACE", "MERGE", "MIN", "MINEXTENTS", "MINUTE", "MINVALUE", "MIRROR", "MOD", "MODE", "MODIFY", "MONEY", "MONITORING", "MONTH", "MOUNT", "MOVEMENT", "NATIONAL", "NCHAR", "NCHARACTER", "NEVER", "NO", "NOARCHIVELOG", "NOAUDIT", "NOBRANCH", "NOCACHE", "NOLOGGING", "NOMAXVALUE", "NOMINVALUE", "NOMONITORING", "NONE", "NOORDER", "NORMAL", "NOSORT", "NOT_ALLOW_DATETIME", "NOT_ALLOW_IP", "NOWAIT", "NULLS", "NUMBER", "NUMERIC", "OFF", "OFFLINE", "OFFSET", "OLD", "ONCE", "ONLINE", "ONLY", "OPEN", "OPTIMIZE", "OPTION", "OUTER", "OVERLAPS", "OVERRIDING", "PACKAGE", "PAD", "PAGE", "PARALLEL", "PARALLEL_ENABLE", "PARMS", "PARTIAL", "PARTITIONS", "PASSING", "PASSWORD_GRACE_TIME", "PASSWORD_LIFE_TIME", "PASSWORD_LOCK_TIME", "PASSWORD_POLICY", "PASSWORD_REUSE_MAX", "PASSWORD_REUSE_TIME", "PATH", "PIPE", "PIPELINED", "PIVOT", "PLACING", "PLS_INTEGER", "PRAGMA", "PRECEDING", "PRECISION", "PRESERVE", "PRIVILEGE", "PURGE", "QUERY_REWRITE_INTEGRITY", "RANGE", "RAWTOHEX", "READ", "READONLY", "READ_PER_CALL", "READ_PER_SESSION", "REAL", "REBUILD", "RECORD", "RECORDS", "REFRESH", "RELATED", "RENAME", "REPEATABLE", "REPLACE", "REPLAY", "RESIZE", "RESTORE", "RESTRICT", "RESULT", "RESULT_CACHE", "ROLE", "ROLLFILE", "ROOT", "ROWCOUNT", "ROWID", "RULE", "SALT", "SAMPLE", "SAVE", "SCOPE", "SCROLL", "SECOND", "SEED", "SELF", "SENSITIVE", "SEQUENCE", "SERERR", "SERIALIZABLE", "SERVER", "SESSION", "SESSION_PER_USER", "SHARE", "SHUTDOWN", "SIBLINGS", "SIMPLE", "SINCE", "SIZE", "SKIP", "SMALLINT", "SNAPSHOT", "SOUND", "SPACE", "SPATIAL", "SPFILE", "SPLIT", "SQL", "STANDBY", "STARTUP", "STAT", "STATEMENT", "STORAGE", "STORE", "STRING", "STYLE", "SUBPARTITION", "SUBPARTITIONS", "SUBSTRING", "SUBTYPE", "SUCCESSFUL", "SUM", "SUSPEND", "SYNC", "SYNCHRONOUS", "SYSTEM ", "SYS_CONNECT_BY_PATH", "TABLESPACE", "TASK", "TEMPLATE", "TEMPORARY", "TEXT", "THAN", "THEN", "THREAD", "TIES", "TIME", "TIMER", "TIMES", "TIMESTAMP", "TIME_ZONE", "TINYINT", "TRACE ", "TRANSACTION", "TRANSACTIONAL", "TRIGGERS", "TRUNCSIZE", "TRXID", "TYPE", "UNBOUNDED", "UNCOMMITTED", "UNDER", "UNLIMITED", "UNLOCK ", "UNPIVOT", "UNUSABLE ", "UP", "UPDATING", " USE_HASH ", "USE_MERGE ", "USE_NL ", "USE_NL_WITH_INDEX ", "VALUE", "VARBINARY", "VARCHAR", "VARCHAR2", "VARIANCE", "VARYING", "VERSIONS", "VERSIONS_STARTTIME", "VERSIONS_ENDTIME", "VERSIONS_STARTTRXID", "VERSIONS_ENDTRXID", "VERSIONS_OPERATION", "VERTICAL", "VISIBLE ", "VSIZE", "WAIT", "WEEK", "WITHOUT", "WORK", "WRAPPED", "WRITE", "XML", "XMLTABLE", "YEAR", "ZONE");
private Integer databaseMajorVersion;
private Integer databaseMinorVersion;
public DM8Database() {
}
public int getDatabaseMajorVersion() throws DatabaseException {
return super.getDatabaseMajorVersion();
}
public int getDatabaseMinorVersion() throws DatabaseException {
return super.getDatabaseMinorVersion();
}
//加入关键字
protected boolean mustQuoteObjectName(String objectName, Class<? extends DatabaseObject> objectType) {
return QUOTE_KEYWORDS.contains(objectName.toUpperCase());
}
@Override
public String getShortName() {
return PRODUCT_SHORT_NAME;
}
@Override
protected String getDefaultDatabaseProductName() {
return getDatabaseProductName();
}
@Override
public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException {
return PRODUCT_NAME.equalsIgnoreCase(conn.getDatabaseProductName());
}
@Override
public String getDefaultDriver(String url) {
return url.startsWith(DRIVER_CONN_PERFIX) ? DRIVER_STRING : null;
}
@Override
public Integer getDefaultPort() {
return 5236;
}
}
2. 部分雾化试图不兼容重写fastFetchQuery
liquibase.snapshot.ResultSetCache.SingleResultSetExtractor#fastFetchQuery
一定要放到oracle判断之前
if (database instanceof DM8Database) {
return queryDameng(catalogAndSchema, table);
}else if (database instanceof OracleDatabase) {
return queryOracle(catalogAndSchema, table);
} else if (database instanceof MSSQLDatabase) {
return queryMssql(catalogAndSchema, table);
} else if (database instanceof Db2zDatabase) {
return queryDb2Zos(catalogAndSchema, table);
}
3. 部分雾化试图不兼容重写bulkFetchQuery
liquibase.snapshot.ResultSetCache.SingleResultSetExtractor#bulkFetchQuery
instanceof类型比较的原因所以判断一定要放到oracle判断之前。
if(database instanceof DM8Database) {
return queryDameng(catalogAndSchema, null);
}else if (database instanceof OracleDatabase) {
return queryOracle(catalogAndSchema, null);
} else if (database instanceof MSSQLDatabase) {
return queryMssql(catalogAndSchema, null);
} else if (database instanceof Db2zDatabase) {
return queryDb2Zos(catalogAndSchema, null);
}
4. 新增queryDameng方法
liquibase.snapshot.ResultSetCache.SingleResultSetExtractor#queryDameng
private List<CachedRow> queryDameng(CatalogAndSchema catalogAndSchema, String viewName) throws DatabaseException, SQLException {
String ownerName = CachingDatabaseMetaData.this.database.correctObjectName(catalogAndSchema.getCatalogName(), Schema.class);
String sql = "SELECT null as TABLE_CAT, a.OWNER as TABLE_SCHEM, a.VIEW_NAME as TABLE_NAME, 'TABLE' as TABLE_TYPE, c.COMMENTS as REMARKS, TEXT as OBJECT_BODY";
if (CachingDatabaseMetaData.this.database.getDatabaseMajorVersion() > 10) {
sql = sql + ", EDITIONING_VIEW";
}
sql = sql + " from ALL_VIEWS a join ALL_TAB_COMMENTS c on a.VIEW_NAME=c.table_name and a.owner=c.owner ";
if (viewName == null && JdbcDatabaseSnapshot.this.getAllCatalogsStringScratchData() != null) {
sql = sql + "WHERE a.OWNER IN ('" + ownerName + "', " + JdbcDatabaseSnapshot.this.getAllCatalogsStringScratchData() + ")";
} else {
sql = sql + "WHERE a.OWNER='" + ownerName + "'";
}
if (viewName != null) {
sql = sql + " AND a.VIEW_NAME='" + CachingDatabaseMetaData.this.database.correctObjectName(viewName, View.class) + "'";
}
return this.executeAndExtract(sql, CachingDatabaseMetaData.this.database);
}
5. 修正源码的test方法
liquibase.change.ChangeParameterMetaDataTest#computedDatabasesCorrect
6. 修改版本号
maven打包的话,只修改liquibase-core的pom的版本号,然后打包即可。
已经提交打包了一份:点击跳转liquibase-core-3.8.10-local-SNAPSHOT.jar
四、修改后的校验
项目中的校验
gitee项目地址:https://gitee.com/gy297879328/jpa_dm_liquibase
Demo已经搭建完毕,启动项目后,数据库中的日志会初始化liquibase两张表跟master.xml的表。