Flowable集成达梦

Flowable 原生没有支持达梦数据库,因此需要少量修改 Flowable 的代码以适配达梦,修改后的代码需要重新打包,具体操作步骤如下。

  • 在 common-flow 包内创建包「org.flowable.common.engine.impl」,这个包名必须和 flowable 内置的完全一致,运行时才能覆盖。
  • 获取 flowable 包内 AbstractEngineConfiguration 类的源码,全部代码拷贝后,直接覆盖到上一步在 common-flow 中创建的 AbstractEngineConfiguration 类文件,flowable 中的文件位置可参考下图。
  • 在第一步创建的 org.flowable.common.engine.impl 包内,创建类 AbstractEngineConfiguration,类名必须和 flowable 内部类同名。将上一步拷贝的 flowable 源码,全部粘贴到当前新创建的文件中。
  • 由于达梦数据库和 MySQL 的语法相近,这里可以考虑使用 MySQL 的数据源类型映射。具体代码修改如下。

  • 修改 liquibase-core-4.9.1.jar 中源码。仍然在 common-flow 包内创建两个同名包「liquibase.database.core」和「liquibase.datatype.core」,具体见下图。

  • 在「liquibase.database.core」包内创建 DmDatabase 类,完整代码如下。
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.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.StringUtil;
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;
public class DmDatabase extends AbstractJdbcDatabase {
   private static final String PRODUCT_NAME = "DM DBMS";
   @Override
   protected String getDefaultDatabaseProductName() {
       return PRODUCT_NAME;
   }
   @Override
   public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException {
       return PRODUCT_NAME.equalsIgnoreCase(conn.getDatabaseProductName());
   }
   @Override
   public String getDefaultDriver(String url) {
       if(url.startsWith("jdbc:dm")) {
           return "dm.jdbc.driver.DmDriver";
       }
       return null;
   }
   @Override
   public String getShortName() {
       return "dm";
   }
   @Override
   public Integer getDefaultPort() {
       return 5236;
   }
   @Override
   public boolean supportsInitiallyDeferrableColumns() {
       return true;
   }
   @Override
   public boolean supportsTablespaces() {
       return true;
   }
   @Override
   public int getPriority() {
       return PRIORITY_DEFAULT;
   }
   private static final Pattern PROXY_USER = Pattern.compile(".*(?:thin|oci)\\:(.+)/@.*");
   protected final int SHORT_IDENTIFIERS_LENGTH = 30;
   protected final int LONG_IDENTIFIERS_LEGNTH = 128;
   public static final int ORACLE_12C_MAJOR_VERSION = 12;
   private Set<String> reservedWords = new HashSet<>();
   private Set<String> userDefinedTypes;
   private Map<String, String> savedSessionNlsSettings;
   private Boolean canAccessDbaRecycleBin;
   private Integer databaseMajorVersion;
   private Integer databaseMinorVersion;
   public DmDatabase() {
       super.unquotedObjectsAreUppercased = true;
       //noinspection HardCodedStringLiteral
       super.setCurrentDateTimeFunction("SYSTIMESTAMP");
       // Setting list of Oracle's native functions
       //noinspection HardCodedStringLiteral
       dateFunctions.add(new DatabaseFunction("SYSDATE"));
       //noinspection HardCodedStringLiteral
       dateFunctions.add(new DatabaseFunction("SYSTIMESTAMP"));
       //noinspection HardCodedStringLiteral
       dateFunctions.add(new DatabaseFunction("CURRENT_TIMESTAMP"));
       //noinspection HardCodedStringLiteral
       super.sequenceNextValueFunction = "%s.nextval";
       //noinspection HardCodedStringLiteral
       super.sequenceCurrentValueFunction = "%s.currval";
   }
   private void tryProxySession(final String url, final Connection con) {
       Matcher m = PROXY_USER.matcher(url);
       if (m.matches()) {
           Properties props = new Properties();
           props.put("PROXY_USER_NAME", m.group(1));
           try {
               Method method = con.getClass().getMethod("openProxySession", int.class, Properties.class);
               method.setAccessible(true);
               method.invoke(con, 1, props);
           } catch (Exception e) {
               Scope.getCurrentScope().getLog(getClass()).info("Could not open proxy session on OracleDatabase: " + e.getCause().getMessage());
           }
       }
   }
   @Override
   public int getDatabaseMajorVersion() throws DatabaseException {
       if (databaseMajorVersion == null) {
           return super.getDatabaseMajorVersion();
       } else {
           return databaseMajorVersion;
       }
   }
   @Override
   public int getDatabaseMinorVersion() throws DatabaseException {
       if (databaseMinorVersion == null) {
           return super.getDatabaseMinorVersion();
       } else {
           return databaseMinorVersion;
       }
   }
   @Override
   public String getJdbcCatalogName(CatalogAndSchema schema) {
       return null;
   }
   @Override
   public String getJdbcSchemaName(CatalogAndSchema schema) {
       return correctObjectName((schema.getCatalogName() == null) ? schema.getSchemaName() : schema.getCatalogName(), Schema.class);
   }
   @Override
   protected String getAutoIncrementClause(final String generationType, final Boolean defaultOnNull) {
       if (StringUtil.isEmpty(generationType)) {
           return super.getAutoIncrementClause();
       }
       String autoIncrementClause = "GENERATED %s AS IDENTITY"; // %s -- [ ALWAYS | BY DEFAULT [ ON NULL ] ]
       String generationStrategy = generationType;
       if (Boolean.TRUE.equals(defaultOnNull) && generationType.toUpperCase().equals("BY DEFAULT")) {
           generationStrategy += " ON NULL";
       }
       return String.format(autoIncrementClause, generationStrategy);
   }
   @Override
   public String generatePrimaryKeyName(String tableName) {
       if (tableName.length() > 27) {
           //noinspection HardCodedStringLiteral
           return "PK_" + tableName.toUpperCase(Locale.US).substring(0, 27);
       } else {
           //noinspection HardCodedStringLiteral
           return "PK_" + tableName.toUpperCase(Locale.US);
       }
   }
   @Override
   public boolean isReservedWord(String objectName) {
       return reservedWords.contains(objectName.toUpperCase());
   }
   @Override
   public boolean supportsSequences() {
       return true;
   }

   // Oracle supports catalogs in liquibase terms
   @Override
   public boolean supportsSchemas() {
       return false;
   }
   @Override
   protected String getConnectionCatalogName() throws DatabaseException {
       if (getConnection() instanceof OfflineConnection) {
           return getConnection().getCatalog();
       }
       try {
           //noinspection HardCodedStringLiteral
           return Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForObject(new RawCallStatement("select sys_context( 'userenv', 'current_schema' ) from dual"), String.class);
       } catch (Exception e) {
           //noinspection HardCodedStringLiteral
           Scope.getCurrentScope().getLog(getClass()).info("Error getting default schema", e);
       }
       return null;
   }
   @Override
   public String getDefaultCatalogName() {//NOPMD
       return (super.getDefaultCatalogName() == null) ? null : super.getDefaultCatalogName().toUpperCase(Locale.US);
   }
   @Override
   public String getDateLiteral(String isoDate) {
       String normalLiteral = super.getDateLiteral(isoDate);
       if (isDateOnly(isoDate)) {
           return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD')";
       } else if (isTimeOnly(isoDate)) {
           return "TO_DATE(" + normalLiteral + ", 'HH24:MI:SS')";
       } else if (isTimestamp(isoDate)) {
           return "TO_TIMESTAMP(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS.FF')";
       } else if (isDateTime(isoDate)) {
           int seppos = normalLiteral.lastIndexOf('.');
           if (seppos != -1) {
               normalLiteral = normalLiteral.substring(0, seppos) + "'";
           }
           return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS')";
       }
       return "UNSUPPORTED:" + isoDate;
   }
   @Override
   public boolean isSystemObject(DatabaseObject example) {
       if (example == null) {
           return false;
       }
       if (this.isLiquibaseObject(example)) {
           return false;
       }
       if (example instanceof Schema) {
           //noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
           if ("SYSTEM".equals(example.getName()) || "SYS".equals(example.getName()) || "CTXSYS".equals(example.getName()) || "XDB".equals(example.getName())) {
               return true;
           }
           //noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
           if ("SYSTEM".equals(example.getSchema().getCatalogName()) || "SYS".equals(example.getSchema().getCatalogName()) || "CTXSYS".equals(example.getSchema().getCatalogName()) || "XDB".equals(example.getSchema().getCatalogName())) {
               return true;
           }
       } else if (isSystemObject(example.getSchema())) {
           return true;
       }
       if (example instanceof Catalog) {
           //noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
           if (("SYSTEM".equals(example.getName()) || "SYS".equals(example.getName()) || "CTXSYS".equals(example.getName()) || "XDB".equals(example.getName()))) {
               return true;
           }
       } else if (example.getName() != null) {
           //noinspection HardCodedStringLiteral
           if (example.getName().startsWith("BIN$")) { //oracle deleted table
               boolean filteredInOriginalQuery = this.canAccessDbaRecycleBin();
               if (!filteredInOriginalQuery) {
                   filteredInOriginalQuery = StringUtil.trimToEmpty(example.getSchema().getName()).equalsIgnoreCase(this.getConnection().getConnectionUserName());
               }
               if (filteredInOriginalQuery) {
                   return !((example instanceof PrimaryKey) || (example instanceof Index) || (example instanceof
                           liquibase.statement.UniqueConstraint));
               } else {
                   return true;
               }
           } else //noinspection HardCodedStringLiteral
               if (example.getName().startsWith("AQ$")) { //oracle AQ tables
                   return true;
               } else //noinspection HardCodedStringLiteral
                   if (example.getName().startsWith("DR$")) { //oracle index tables
                       return true;
                   } else //noinspection HardCodedStringLiteral
                       if (example.getName().startsWith("SYS_IOT_OVER")) { //oracle system table
                           return true;
                       } else //noinspection HardCodedStringLiteral,HardCodedStringLiteral
                           if ((example.getName().startsWith("MDRT_") || example.getName().startsWith("MDRS_")) && example.getName().endsWith("$")) {
                               // CORE-1768 - Oracle creates these for spatial indices and will remove them when the index is removed.
                               return true;
                           } else //noinspection HardCodedStringLiteral
                               if (example.getName().startsWith("MLOG$_")) { //Created by materliaized view logs for every table that is part of a materialized view. Not available for DDL operations.
                                   return true;
                               } else //noinspection HardCodedStringLiteral
                                   if (example.getName().startsWith("RUPD$_")) { //Created by materialized view log tables using primary keys. Not available for DDL operations.
                                       return true;
                                   } else //noinspection HardCodedStringLiteral
                                       if (example.getName().startsWith("WM$_")) { //Workspace Manager backup tables.
                                           return true;
                                       } else //noinspection HardCodedStringLiteral
                                           if ("CREATE$JAVA$LOB$TABLE".equals(example.getName())) { //This table contains the name of the Java object, the date it was loaded, and has a BLOB column to store the Java object.
                                               return true;
                                           } else //noinspection HardCodedStringLiteral
                                               if ("JAVA$CLASS$MD5$TABLE".equals(example.getName())) { //This is a hash table that tracks the loading of Java objects into a schema.
                                                   return true;
                                               } else //noinspection HardCodedStringLiteral
                                                   if (example.getName().startsWith("ISEQ$$_")) { //System-generated sequence
                                                       return true;
                                                   } else //noinspection HardCodedStringLiteral
                                                       if (example.getName().startsWith("USLOG$")) { //for update materialized view
                                                           return true;
                                                       } else if (example.getName().startsWith("SYS_FBA")) { //for Flashback tables
                                                           return true;
                                                       }
       }
       return super.isSystemObject(example);
   }
   @Override
   public boolean supportsAutoIncrement() {
       // Oracle supports Identity beginning with version 12c
       boolean isAutoIncrementSupported = false;
       try {
           if (getDatabaseMajorVersion() >= 12) {
               isAutoIncrementSupported = true;
           }
       } catch (DatabaseException ex) {
           isAutoIncrementSupported = false;
       }
       return isAutoIncrementSupported;
   }
   @Override
   public boolean supportsRestrictForeignKeys() {
       return false;
   }
   @Override
   public int getDataTypeMaxParameters(String dataTypeName) {
       //noinspection HardCodedStringLiteral
       if ("BINARY_FLOAT".equals(dataTypeName.toUpperCase())) {
           return 0;
       }
       //noinspection HardCodedStringLiteral
       if ("BINARY_DOUBLE".equals(dataTypeName.toUpperCase())) {
           return 0;
       }
       return super.getDataTypeMaxParameters(dataTypeName);
   }
   public String getSystemTableWhereClause(String tableNameColumn) {
       List<String> clauses = new ArrayList<String>(Arrays.asList("BIN$",
               "AQ$",
               "DR$",
               "SYS_IOT_OVER",
               "MLOG$_",
               "RUPD$_",
               "WM$_",
               "ISEQ$$_",
               "USLOG$",
               "SYS_FBA"));
       for (int i = 0;i<clauses.size(); i++) {
           clauses.set(i, tableNameColumn+" NOT LIKE '"+clauses.get(i)+"%'");
       }
       return "("+ StringUtil.join(clauses, " AND ") + ")";
   }
   @Override
   public boolean jdbcCallsCatalogsSchemas() {
       return true;
   }
   public Set<String> getUserDefinedTypes() {
       if (userDefinedTypes == null) {
           userDefinedTypes = new HashSet<>();
           if ((getConnection() != null) && !(getConnection() instanceof OfflineConnection)) {
               try {
                   try {
                       //noinspection HardCodedStringLiteral
                       userDefinedTypes.addAll(Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForList(new RawSqlStatement("SELECT DISTINCT TYPE_NAME FROM ALL_TYPES"), String.class));
                   } catch (DatabaseException e) { //fall back to USER_TYPES if the user cannot see ALL_TYPES
                       //noinspection HardCodedStringLiteral
                       userDefinedTypes.addAll(Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForList(new RawSqlStatement("SELECT TYPE_NAME FROM USER_TYPES"), String.class));
                   }
               } catch (DatabaseException e) {
                   //ignore error
               }
           }
       }
       return userDefinedTypes;
   }
   @Override
   public String generateDatabaseFunctionValue(DatabaseFunction databaseFunction) {
       //noinspection HardCodedStringLiteral
       if ((databaseFunction != null) && "current_timestamp".equalsIgnoreCase(databaseFunction.toString())) {
           return databaseFunction.toString();
       }
       if ((databaseFunction instanceof SequenceNextValueFunction) || (databaseFunction instanceof
               SequenceCurrentValueFunction)) {
           String quotedSeq = super.generateDatabaseFunctionValue(databaseFunction);
           // replace "myschema.my_seq".nextval with "myschema"."my_seq".nextval
           return quotedSeq.replaceFirst("\"([^\\.\"]+)\\.([^\\.\"]+)\"", "\"$1\".\"$2\"");
       }
       return super.generateDatabaseFunctionValue(databaseFunction);
   }
   @Override
   public ValidationErrors validate() {
       ValidationErrors errors = super.validate();
       DatabaseConnection connection = getConnection();
       if ((connection == null) || (connection instanceof OfflineConnection)) {
           //noinspection HardCodedStringLiteral
           Scope.getCurrentScope().getLog(getClass()).info("Cannot validate offline database");
           return errors;
       }
       if (!canAccessDbaRecycleBin()) {
           errors.addWarning(getDbaRecycleBinWarning());
       }
       return errors;
   }
   public String getDbaRecycleBinWarning() {
       //noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,
       // HardCodedStringLiteral
       //noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
       return "Liquibase needs to access the DBA_RECYCLEBIN table so we can automatically handle the case where " +
               "constraints are deleted and restored. Since Oracle doesn't properly restore the original table names " +
               "referenced in the constraint, we use the information from the DBA_RECYCLEBIN to automatically correct this" +
               " issue.\n" +
               "\n" +
               "The user you used to connect to the database (" + getConnection().getConnectionUserName() +
               ") needs to have \"SELECT ON SYS.DBA_RECYCLEBIN\" permissions set before we can perform this operation. " +
               "Please run the following SQL to set the appropriate permissions, and try running the command again.\n" +
               "\n" +
               "     GRANT SELECT ON SYS.DBA_RECYCLEBIN TO " + getConnection().getConnectionUserName() + ";";
   }
   public boolean canAccessDbaRecycleBin() {
       if (canAccessDbaRecycleBin == null) {
           DatabaseConnection connection = getConnection();
           if ((connection == null) || (connection instanceof OfflineConnection)) {
               return false;
           }
           Statement statement = null;
           try {
               statement = ((JdbcConnection) connection).createStatement();
               @SuppressWarnings("HardCodedStringLiteral") ResultSet resultSet = statement.executeQuery("select 1 from dba_recyclebin where 0=1");
               resultSet.close(); //don't need to do anything with the result set, just make sure statement ran.
               this.canAccessDbaRecycleBin = true;
           } catch (Exception e) {
               //noinspection HardCodedStringLiteral
               if ((e instanceof SQLException) && e.getMessage().startsWith("ORA-00942")) { //ORA-00942: table or view does not exist
                   this.canAccessDbaRecycleBin = false;
               } else {
                   //noinspection HardCodedStringLiteral
                   Scope.getCurrentScope().getLog(getClass()).warning("Cannot check dba_recyclebin access", e);
                   this.canAccessDbaRecycleBin = false;
               }
           } finally {
               JdbcUtils.close(null, statement);
           }
       }
       return canAccessDbaRecycleBin;
   }
   @Override
   public boolean supportsNotNullConstraintNames() {
       return true;
   }
   public boolean isValidOracleIdentifier(String identifier, Class<? extends DatabaseObject> type) {
       if ((identifier == null) || (identifier.length() < 1)) {
           return false;
       }
       if (!identifier.matches("^(i?)[A-Z][A-Z0-9\\$\\_\\#]*$")) {
           return false;
       }
       return (identifier.length() <= LONG_IDENTIFIERS_LEGNTH);
   }
   public int getIdentifierMaximumLength() {
       try {
           if (getDatabaseMajorVersion() < ORACLE_12C_MAJOR_VERSION) {
               return SHORT_IDENTIFIERS_LENGTH;
           } else if ((getDatabaseMajorVersion() == ORACLE_12C_MAJOR_VERSION) && (getDatabaseMinorVersion() <= 1)) {
               return SHORT_IDENTIFIERS_LENGTH;
           } else {
               return LONG_IDENTIFIERS_LEGNTH;
           }
       } catch (DatabaseException ex) {
           throw new UnexpectedLiquibaseException("Cannot determine the Oracle database version number", ex);
       }
   }
}
  • 在「liquibase.datatype.core」包内创建 BooleanType 类,完整代码如下。
package liquibase.datatype.core;
import java.util.Locale;
import liquibase.change.core.LoadDataChange;
import liquibase.change.core.LoadDataChange.LOAD_DATA_TYPE;
import liquibase.database.Database;
import liquibase.database.core.*;
import liquibase.datatype.DataTypeInfo;
import liquibase.datatype.DatabaseDataType;
import liquibase.datatype.LiquibaseDataType;
import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.statement.DatabaseFunction;
import liquibase.util.StringUtil;
@DataTypeInfo(
       name = "boolean",
       aliases = {"java.sql.Types.BOOLEAN", "java.lang.Boolean", "bit", "bool"},
       minParameters = 0,
       maxParameters = 0,
       priority = 1
)
public class BooleanType extends LiquibaseDataType {
   public BooleanType() {
   }
   @Override
   public DatabaseDataType toDatabaseDataType(Database database) {
       String originalDefinition = StringUtil.trimToEmpty(getRawDefinition());
       if ((database instanceof Firebird3Database)) {
           return new DatabaseDataType("BOOLEAN");
       }
       if ((database instanceof AbstractDb2Database) || (database instanceof FirebirdDatabase)) {
           return new DatabaseDataType("SMALLINT");
       } else if (database instanceof MSSQLDatabase) {
           return new DatabaseDataType(database.escapeDataTypeName("bit"));
       } else if (database instanceof MySQLDatabase) {
           if (originalDefinition.toLowerCase(Locale.US).startsWith("bit")) {
               return new DatabaseDataType("BIT", getParameters());
           }
           return new DatabaseDataType("BIT", 1);
       } else if (database instanceof OracleDatabase) {
           return new DatabaseDataType("NUMBER", 1);
       } else if ((database instanceof SybaseASADatabase) || (database instanceof SybaseDatabase)) {
           return new DatabaseDataType("BIT");
       } else if (database instanceof DerbyDatabase) {
           if (((DerbyDatabase) database).supportsBooleanDataType()) {
               return new DatabaseDataType("BOOLEAN");
           } else {
               return new DatabaseDataType("SMALLINT");
           }
       } else if (database.getClass().isAssignableFrom(DB2Database.class)) {
           if (((DB2Database) database).supportsBooleanDataType()) {
               return new DatabaseDataType("BOOLEAN");
           } else {
               return new DatabaseDataType("SMALLINT");
           }
       } else if (database instanceof HsqlDatabase) {
           return new DatabaseDataType("BOOLEAN");
       } else if (database instanceof PostgresDatabase) {
           if (originalDefinition.toLowerCase(Locale.US).startsWith("bit")) {
               return new DatabaseDataType("BIT", getParameters());
           }
       } else if(database instanceof DmDatabase) {
           return new DatabaseDataType("bit");
       }
       return super.toDatabaseDataType(database);
   }
   @Override
   public String objectToSql(Object value, Database database) {
       if ((value == null) || "null".equals(value.toString().toLowerCase(Locale.US))) {
           return null;
       }
       String returnValue;
       if (value instanceof String) {
           value = ((String) value).replaceAll("'", "");
           if ("true".equals(((String) value).toLowerCase(Locale.US)) || "1".equals(value) || "b'1'".equals(((String) value).toLowerCase(Locale.US)) || "t".equals(((String) value).toLowerCase(Locale.US)) || ((String) value).toLowerCase(Locale.US).equals(this.getTrueBooleanValue(database).toLowerCase(Locale.US))) {
               returnValue = this.getTrueBooleanValue(database);
           } else if ("false".equals(((String) value).toLowerCase(Locale.US)) || "0".equals(value) || "b'0'".equals(
                   ((String) value).toLowerCase(Locale.US)) || "f".equals(((String) value).toLowerCase(Locale.US)) || ((String) value).toLowerCase(Locale.US).equals(this.getFalseBooleanValue(database).toLowerCase(Locale.US))) {
               returnValue = this.getFalseBooleanValue(database);
           } else {
               throw new UnexpectedLiquibaseException("Unknown boolean value: " + value);
           }
       } else if (value instanceof Long) {
           if (Long.valueOf(1).equals(value)) {
               returnValue = this.getTrueBooleanValue(database);
           } else {
               returnValue = this.getFalseBooleanValue(database);
           }
       } else if (value instanceof Number) {
           if (value.equals(1) || "1".equals(value.toString()) || "1.0".equals(value.toString())) {
               returnValue = this.getTrueBooleanValue(database);
           } else {
               returnValue = this.getFalseBooleanValue(database);
           }
       } else if (value instanceof DatabaseFunction) {
           return value.toString();
       } else if (value instanceof Boolean) {
           if (((Boolean) value)) {
               returnValue = this.getTrueBooleanValue(database);
           } else {
               returnValue = this.getFalseBooleanValue(database);
           }
       } else {
           throw new UnexpectedLiquibaseException("Cannot convert type " + value.getClass() + " to a boolean value");
       }
       return returnValue;
   }
   protected boolean isNumericBoolean(Database database) {
       if (database instanceof DerbyDatabase) {
           return !((DerbyDatabase) database).supportsBooleanDataType();
       } else if (database.getClass().isAssignableFrom(DB2Database.class)) {
           return !((DB2Database) database).supportsBooleanDataType();
       }
       return (database instanceof Db2zDatabase) || (database instanceof DB2Database) || (database instanceof FirebirdDatabase) || (database instanceof
               MSSQLDatabase) || (database instanceof MySQLDatabase) || (database instanceof OracleDatabase) ||
               (database instanceof SQLiteDatabase) || (database instanceof SybaseASADatabase) || (database instanceof
               SybaseDatabase) || (database instanceof DmDatabase);
   }
   public String getFalseBooleanValue(Database database) {
       if (isNumericBoolean(database)) {
           return "0";
       }
       if (database instanceof InformixDatabase) {
           return "'f'";
       }
       return "FALSE";
   }
   public String getTrueBooleanValue(Database database) {
       if (isNumericBoolean(database)) {
           return "1";
       }
       if (database instanceof InformixDatabase) {
           return "'t'";
       }
       return "TRUE";
   }
   @Override
   public LOAD_DATA_TYPE getLoadTypeName() {
       return LOAD_DATA_TYPE.BOOLEAN;
   }
}
  • 编译当前整个工程。在 common-flow 的 target 文件夹中,找到前两步添加的 DmDatabase 和 BooleanType 类的编译后 class 文件。
  • 定位 liquibase-core-4.9.1.jar 的所在目录,并用解压工具手动解压该 jar 文件。

  • 将之前在 common-flow 包中编译成功的两个文件 DmDatabase.class 和 BooleanType.class 拷贝到上一步解压的 liquibase-core-4.9.1 子目录内,两个目标文件的具体位置见下图。
  • 修改 liquibase-core-4.9.1/META-INF/services/liquibase.database.Database 文件,并添加以下配置。
  • 切记要在 liquibase-core-4.9.1 目录内执行打包命令,再将打包后的 liquibase-core-4.3.5.jar 移出至上级目录,并覆盖原有文件。具体操作见下图。
  • 回到开发环境,手动删除 common-flow 包内的两个源文件 DmDatabase.java 和 BooleanType.java。最后重新编译整个工程,即可引用前面手动打包的 liquibase-core-4.9.1.jar。

Flowable集成金仓

Flowable 原生没有支持人大金仓数据库,因此需要少量修改 Flowable 的代码以适配人大金仓,修改后的代码需要重新打包,具体操作步骤如下。

  • 在 common-flow 包内创建包「org.flowable.common.engine.impl」,这个包名必须和 flowable 内置的完全一致,运行时才能覆盖。
  • 获取 flowable 包内 AbstractEngineConfiguration 类的源码,全部代码拷贝后,直接覆盖到上一步在 common-flow 中创建的 AbstractEngineConfiguration 类文件,flowable 中的文件位置可参考下图。
  • 在第一步创建的 org.flowable.common.engine.impl 包内,创建类 AbstractEngineConfiguration,类名必须和 flowable 内部类同名。将上一步拷贝的 flowable 源码,全部粘贴到当前新创建的文件中。

  • 由于人大金仓数据库和 MySQL 的语法相近,这里可以考虑使用 MySQL 的数据源类型映射。具体代码修改如下。
  • 修改 liquibase-core-4.9.1.jar 中源码。仍然在 common-flow 包内创建两个同名包「liquibase.database.core」和「liquibase.datatype.core」,具体见下图。
  • 在「liquibase.database.core」包内创建 KingbaseDatabase 类,完整代码如下。
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.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.StringUtil;
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;
public class KingbaseDatabase extends AbstractJdbcDatabase {
   private static final String PRODUCT_NAME = "KingbaseES";
   @Override
   protected String getDefaultDatabaseProductName() {
       return PRODUCT_NAME;
   }
   @Override
   public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException {
       return PRODUCT_NAME.equalsIgnoreCase(conn.getDatabaseProductName());
   }
   @Override
   public String getDefaultDriver(String url) {
       if(url.startsWith("jdbc:kingbase8")) {
           return "com.kingbase8.Driver";
       }
       return null;
   }
   @Override
   public String getShortName() {
       return "kingbase";
   }
   @Override
   public Integer getDefaultPort() {
       return 54321;
   }
   @Override
   public boolean supportsInitiallyDeferrableColumns() {
       return true;
   }
   @Override
   public boolean supportsTablespaces() {
       return true;
   }
   @Override
   public int getPriority() {
       return PRIORITY_DEFAULT;
   }
   private static final Pattern PROXY_USER = Pattern.compile(".*(?:thin|oci)\\:(.+)/@.*");
   protected final int SHORT_IDENTIFIERS_LENGTH = 30;
   protected final int LONG_IDENTIFIERS_LEGNTH = 128;
   public static final int ORACLE_12C_MAJOR_VERSION = 12;
   private Set<String> reservedWords = new HashSet<>();
   private Set<String> userDefinedTypes;
   private Map<String, String> savedSessionNlsSettings;
   private Boolean canAccessDbaRecycleBin;
   private Integer databaseMajorVersion;
   private Integer databaseMinorVersion;
   public KingbaseDatabase() {
       super.unquotedObjectsAreUppercased = true;
       //noinspection HardCodedStringLiteral
       super.setCurrentDateTimeFunction("SYSTIMESTAMP");
       // Setting list of Oracle's native functions
       //noinspection HardCodedStringLiteral
       dateFunctions.add(new DatabaseFunction("SYSDATE"));
       //noinspection HardCodedStringLiteral
       dateFunctions.add(new DatabaseFunction("SYSTIMESTAMP"));
       //noinspection HardCodedStringLiteral
       dateFunctions.add(new DatabaseFunction("CURRENT_TIMESTAMP"));
       //noinspection HardCodedStringLiteral
       super.sequenceNextValueFunction = "%s.nextval";
       //noinspection HardCodedStringLiteral
       super.sequenceCurrentValueFunction = "%s.currval";
   }
   private void tryProxySession(final String url, final Connection con) {
       Matcher m = PROXY_USER.matcher(url);
       if (m.matches()) {
           Properties props = new Properties();
           props.put("PROXY_USER_NAME", m.group(1));
           try {
               Method method = con.getClass().getMethod("openProxySession", int.class, Properties.class);
               method.setAccessible(true);
               method.invoke(con, 1, props);
           } catch (Exception e) {
               Scope.getCurrentScope().getLog(getClass()).info("Could not open proxy session on OracleDatabase: " + e.getCause().getMessage());
           }
       }
   }
   @Override
   public int getDatabaseMajorVersion() throws DatabaseException {
       if (databaseMajorVersion == null) {
           return super.getDatabaseMajorVersion();
       } else {
           return databaseMajorVersion;
       }
   }
   @Override
   public int getDatabaseMinorVersion() throws DatabaseException {
       if (databaseMinorVersion == null) {
           return super.getDatabaseMinorVersion();
       } else {
           return databaseMinorVersion;
       }
   }
   @Override
   public String getJdbcCatalogName(CatalogAndSchema schema) {
       return null;
   }
   @Override
   public String getJdbcSchemaName(CatalogAndSchema schema) {
       return correctObjectName((schema.getCatalogName() == null) ? schema.getSchemaName() : schema.getCatalogName(), Schema.class);
   }
   @Override
   protected String getAutoIncrementClause(final String generationType, final Boolean defaultOnNull) {
       if (StringUtil.isEmpty(generationType)) {
           return super.getAutoIncrementClause();
       }
       String autoIncrementClause = "GENERATED %s AS IDENTITY"; // %s -- [ ALWAYS | BY DEFAULT [ ON NULL ] ]
       String generationStrategy = generationType;
       if (Boolean.TRUE.equals(defaultOnNull) && generationType.toUpperCase().equals("BY DEFAULT")) {
           generationStrategy += " ON NULL";
       }
       return String.format(autoIncrementClause, generationStrategy);
   }
   @Override
   public String generatePrimaryKeyName(String tableName) {
       if (tableName.length() > 27) {
           //noinspection HardCodedStringLiteral
           return "PK_" + tableName.toUpperCase(Locale.US).substring(0, 27);
       } else {
           //noinspection HardCodedStringLiteral
           return "PK_" + tableName.toUpperCase(Locale.US);
       }
   }
   @Override
   public boolean isReservedWord(String objectName) {
       return reservedWords.contains(objectName.toUpperCase());
   }
   @Override
   public boolean supportsSequences() {
       return true;
   }

   // Oracle supports catalogs in liquibase terms
   @Override
   public boolean supportsSchemas() {
       return false;
   }
   @Override
   protected String getConnectionCatalogName() throws DatabaseException {
       if (getConnection() instanceof OfflineConnection) {
           return getConnection().getCatalog();
       }
       try {
           //noinspection HardCodedStringLiteral
           return Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForObject(new RawCallStatement("select sys_context( 'userenv', 'current_schema' ) from dual"), String.class);
       } catch (Exception e) {
           //noinspection HardCodedStringLiteral
           Scope.getCurrentScope().getLog(getClass()).info("Error getting default schema", e);
       }
       return null;
   }
   @Override
   public String getDefaultCatalogName() {//NOPMD
       return (super.getDefaultCatalogName() == null) ? null : super.getDefaultCatalogName().toUpperCase(Locale.US);
   }
   @Override
   public String getDateLiteral(String isoDate) {
       String normalLiteral = super.getDateLiteral(isoDate);
       if (isDateOnly(isoDate)) {
           return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD')";
       } else if (isTimeOnly(isoDate)) {
           return "TO_DATE(" + normalLiteral + ", 'HH24:MI:SS')";
       } else if (isTimestamp(isoDate)) {
           return "TO_TIMESTAMP(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS.FF')";
       } else if (isDateTime(isoDate)) {
           int seppos = normalLiteral.lastIndexOf('.');
           if (seppos != -1) {
               normalLiteral = normalLiteral.substring(0, seppos) + "'";
           }
           return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS')";
       }
       return "UNSUPPORTED:" + isoDate;
   }
   @Override
   public boolean isSystemObject(DatabaseObject example) {
       if (example == null) {
           return false;
       }
       if (this.isLiquibaseObject(example)) {
           return false;
       }
       if (example instanceof Schema) {
           //noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
           if ("SYSTEM".equals(example.getName()) || "SYS".equals(example.getName()) || "CTXSYS".equals(example.getName()) || "XDB".equals(example.getName())) {
               return true;
           }
           //noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
           if ("SYSTEM".equals(example.getSchema().getCatalogName()) || "SYS".equals(example.getSchema().getCatalogName()) || "CTXSYS".equals(example.getSchema().getCatalogName()) || "XDB".equals(example.getSchema().getCatalogName())) {
               return true;
           }
       } else if (isSystemObject(example.getSchema())) {
           return true;
       }
       if (example instanceof Catalog) {
           //noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
           if (("SYSTEM".equals(example.getName()) || "SYS".equals(example.getName()) || "CTXSYS".equals(example.getName()) || "XDB".equals(example.getName()))) {
               return true;
           }
       } else if (example.getName() != null) {
           //noinspection HardCodedStringLiteral
           if (example.getName().startsWith("BIN$")) { //oracle deleted table
               boolean filteredInOriginalQuery = this.canAccessDbaRecycleBin();
               if (!filteredInOriginalQuery) {
                   filteredInOriginalQuery = StringUtil.trimToEmpty(example.getSchema().getName()).equalsIgnoreCase(this.getConnection().getConnectionUserName());
               }
               if (filteredInOriginalQuery) {
                   return !((example instanceof PrimaryKey) || (example instanceof Index) || (example instanceof
                           liquibase.statement.UniqueConstraint));
               } else {
                   return true;
               }
           } else //noinspection HardCodedStringLiteral
               if (example.getName().startsWith("AQ$")) { //oracle AQ tables
                   return true;
               } else //noinspection HardCodedStringLiteral
                   if (example.getName().startsWith("DR$")) { //oracle index tables
                       return true;
                   } else //noinspection HardCodedStringLiteral
                       if (example.getName().startsWith("SYS_IOT_OVER")) { //oracle system table
                           return true;
                       } else //noinspection HardCodedStringLiteral,HardCodedStringLiteral
                           if ((example.getName().startsWith("MDRT_") || example.getName().startsWith("MDRS_")) && example.getName().endsWith("$")) {
                               // CORE-1768 - Oracle creates these for spatial indices and will remove them when the index is removed.
                               return true;
                           } else //noinspection HardCodedStringLiteral
                               if (example.getName().startsWith("MLOG$_")) { //Created by materliaized view logs for every table that is part of a materialized view. Not available for DDL operations.
                                   return true;
                               } else //noinspection HardCodedStringLiteral
                                   if (example.getName().startsWith("RUPD$_")) { //Created by materialized view log tables using primary keys. Not available for DDL operations.
                                       return true;
                                   } else //noinspection HardCodedStringLiteral
                                       if (example.getName().startsWith("WM$_")) { //Workspace Manager backup tables.
                                           return true;
                                       } else //noinspection HardCodedStringLiteral
                                           if ("CREATE$JAVA$LOB$TABLE".equals(example.getName())) { //This table contains the name of the Java object, the date it was loaded, and has a BLOB column to store the Java object.
                                               return true;
                                           } else //noinspection HardCodedStringLiteral
                                               if ("JAVA$CLASS$MD5$TABLE".equals(example.getName())) { //This is a hash table that tracks the loading of Java objects into a schema.
                                                   return true;
                                               } else //noinspection HardCodedStringLiteral
                                                   if (example.getName().startsWith("ISEQ$$_")) { //System-generated sequence
                                                       return true;
                                                   } else //noinspection HardCodedStringLiteral
                                                       if (example.getName().startsWith("USLOG$")) { //for update materialized view
                                                           return true;
                                                       } else if (example.getName().startsWith("SYS_FBA")) { //for Flashback tables
                                                           return true;
                                                       }
       }
       return super.isSystemObject(example);
   }
   @Override
   public boolean supportsAutoIncrement() {
       // Oracle supports Identity beginning with version 12c
       boolean isAutoIncrementSupported = false;
       try {
           if (getDatabaseMajorVersion() >= 12) {
               isAutoIncrementSupported = true;
           }
       } catch (DatabaseException ex) {
           isAutoIncrementSupported = false;
       }
       return isAutoIncrementSupported;
   }
   @Override
   public boolean supportsRestrictForeignKeys() {
       return false;
   }
   @Override
   public int getDataTypeMaxParameters(String dataTypeName) {
       //noinspection HardCodedStringLiteral
       if ("BINARY_FLOAT".equals(dataTypeName.toUpperCase())) {
           return 0;
       }
       //noinspection HardCodedStringLiteral
       if ("BINARY_DOUBLE".equals(dataTypeName.toUpperCase())) {
           return 0;
       }
       return super.getDataTypeMaxParameters(dataTypeName);
   }
   public String getSystemTableWhereClause(String tableNameColumn) {
       List<String> clauses = new ArrayList<String>(Arrays.asList("BIN$",
               "AQ$",
               "DR$",
               "SYS_IOT_OVER",
               "MLOG$_",
               "RUPD$_",
               "WM$_",
               "ISEQ$$_",
               "USLOG$",
               "SYS_FBA"));
       for (int i = 0;i<clauses.size(); i++) {
           clauses.set(i, tableNameColumn+" NOT LIKE '"+clauses.get(i)+"%'");
       }
       return "("+ StringUtil.join(clauses, " AND ") + ")";
   }
   @Override
   public boolean jdbcCallsCatalogsSchemas() {
       return true;
   }
   public Set<String> getUserDefinedTypes() {
       if (userDefinedTypes == null) {
           userDefinedTypes = new HashSet<>();
           if ((getConnection() != null) && !(getConnection() instanceof OfflineConnection)) {
               try {
                   try {
                       //noinspection HardCodedStringLiteral
                       userDefinedTypes.addAll(Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForList(new RawSqlStatement("SELECT DISTINCT TYPE_NAME FROM ALL_TYPES"), String.class));
                   } catch (DatabaseException e) { //fall back to USER_TYPES if the user cannot see ALL_TYPES
                       //noinspection HardCodedStringLiteral
                       userDefinedTypes.addAll(Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForList(new RawSqlStatement("SELECT TYPE_NAME FROM USER_TYPES"), String.class));
                   }
               } catch (DatabaseException e) {
                   //ignore error
               }
           }
       }
       return userDefinedTypes;
   }
   @Override
   public String generateDatabaseFunctionValue(DatabaseFunction databaseFunction) {
       //noinspection HardCodedStringLiteral
       if ((databaseFunction != null) && "current_timestamp".equalsIgnoreCase(databaseFunction.toString())) {
           return databaseFunction.toString();
       }
       if ((databaseFunction instanceof SequenceNextValueFunction) || (databaseFunction instanceof
               SequenceCurrentValueFunction)) {
           String quotedSeq = super.generateDatabaseFunctionValue(databaseFunction);
           // replace "myschema.my_seq".nextval with "myschema"."my_seq".nextval
           return quotedSeq.replaceFirst("\"([^\\.\"]+)\\.([^\\.\"]+)\"", "\"$1\".\"$2\"");
       }
       return super.generateDatabaseFunctionValue(databaseFunction);
   }
   @Override
   public ValidationErrors validate() {
       ValidationErrors errors = super.validate();
       DatabaseConnection connection = getConnection();
       if ((connection == null) || (connection instanceof OfflineConnection)) {
           //noinspection HardCodedStringLiteral
           Scope.getCurrentScope().getLog(getClass()).info("Cannot validate offline database");
           return errors;
       }
       if (!canAccessDbaRecycleBin()) {
           errors.addWarning(getDbaRecycleBinWarning());
       }
       return errors;
   }
   public String getDbaRecycleBinWarning() {
       //noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,
       // HardCodedStringLiteral
       //noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
       return "Liquibase needs to access the DBA_RECYCLEBIN table so we can automatically handle the case where " +
               "constraints are deleted and restored. Since Oracle doesn't properly restore the original table names " +
               "referenced in the constraint, we use the information from the DBA_RECYCLEBIN to automatically correct this" +
               " issue.\n" +
               "\n" +
               "The user you used to connect to the database (" + getConnection().getConnectionUserName() +
               ") needs to have \"SELECT ON SYS.DBA_RECYCLEBIN\" permissions set before we can perform this operation. " +
               "Please run the following SQL to set the appropriate permissions, and try running the command again.\n" +
               "\n" +
               "     GRANT SELECT ON SYS.DBA_RECYCLEBIN TO " + getConnection().getConnectionUserName() + ";";
   }
   public boolean canAccessDbaRecycleBin() {
       if (canAccessDbaRecycleBin == null) {
           DatabaseConnection connection = getConnection();
           if ((connection == null) || (connection instanceof OfflineConnection)) {
               return false;
           }
           Statement statement = null;
           try {
               statement = ((JdbcConnection) connection).createStatement();
               @SuppressWarnings("HardCodedStringLiteral") ResultSet resultSet = statement.executeQuery("select 1 from dba_recyclebin where 0=1");
               resultSet.close(); //don't need to do anything with the result set, just make sure statement ran.
               this.canAccessDbaRecycleBin = true;
           } catch (Exception e) {
               //noinspection HardCodedStringLiteral
               if ((e instanceof SQLException) && e.getMessage().startsWith("ORA-00942")) { //ORA-00942: table or view does not exist
                   this.canAccessDbaRecycleBin = false;
               } else {
                   //noinspection HardCodedStringLiteral
                   Scope.getCurrentScope().getLog(getClass()).warning("Cannot check dba_recyclebin access", e);
                   this.canAccessDbaRecycleBin = false;
               }
           } finally {
               JdbcUtils.close(null, statement);
           }
       }
       return canAccessDbaRecycleBin;
   }
   @Override
   public boolean supportsNotNullConstraintNames() {
       return true;
   }
   public boolean isValidOracleIdentifier(String identifier, Class<? extends DatabaseObject> type) {
       if ((identifier == null) || (identifier.length() < 1)) {
           return false;
       }
       if (!identifier.matches("^(i?)[A-Z][A-Z0-9\\$\\_\\#]*$")) {
           return false;
       }
       return (identifier.length() <= LONG_IDENTIFIERS_LEGNTH);
   }
   public int getIdentifierMaximumLength() {
       try {
           if (getDatabaseMajorVersion() < ORACLE_12C_MAJOR_VERSION) {
               return SHORT_IDENTIFIERS_LENGTH;
           } else if ((getDatabaseMajorVersion() == ORACLE_12C_MAJOR_VERSION) && (getDatabaseMinorVersion() <= 1)) {
               return SHORT_IDENTIFIERS_LENGTH;
           } else {
               return LONG_IDENTIFIERS_LEGNTH;
           }
       } catch (DatabaseException ex) {
           throw new UnexpectedLiquibaseException("Cannot determine the Oracle database version number", ex);
       }
   }
}
  • 在「liquibase.datatype.core」包内创建 BooleanType 类,完整代码如下。
package liquibase.datatype.core;
import java.util.Locale;
import liquibase.change.core.LoadDataChange;
import liquibase.change.core.LoadDataChange.LOAD_DATA_TYPE;
import liquibase.database.Database;
import liquibase.database.core.*;
import liquibase.datatype.DataTypeInfo;
import liquibase.datatype.DatabaseDataType;
import liquibase.datatype.LiquibaseDataType;
import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.statement.DatabaseFunction;
import liquibase.util.StringUtil;
@DataTypeInfo(
       name = "boolean",
       aliases = {"java.sql.Types.BOOLEAN", "java.lang.Boolean", "bit", "bool"},
       minParameters = 0,
       maxParameters = 0,
       priority = 1
)
public class BooleanType extends LiquibaseDataType {
   public BooleanType() {
   }
   @Override
   public DatabaseDataType toDatabaseDataType(Database database) {
       String originalDefinition = StringUtil.trimToEmpty(getRawDefinition());
       if ((database instanceof Firebird3Database)) {
           return new DatabaseDataType("BOOLEAN");
       }
       if ((database instanceof AbstractDb2Database) || (database instanceof FirebirdDatabase)) {
           return new DatabaseDataType("SMALLINT");
       } else if (database instanceof MSSQLDatabase) {
           return new DatabaseDataType(database.escapeDataTypeName("bit"));
       } else if (database instanceof MySQLDatabase) {
           if (originalDefinition.toLowerCase(Locale.US).startsWith("bit")) {
               return new DatabaseDataType("BIT", getParameters());
           }
           return new DatabaseDataType("BIT", 1);
       } else if (database instanceof OracleDatabase) {
           return new DatabaseDataType("NUMBER", 1);
       } else if ((database instanceof SybaseASADatabase) || (database instanceof SybaseDatabase)) {
           return new DatabaseDataType("BIT");
       } else if (database instanceof DerbyDatabase) {
           if (((DerbyDatabase) database).supportsBooleanDataType()) {
               return new DatabaseDataType("BOOLEAN");
           } else {
               return new DatabaseDataType("SMALLINT");
           }
       } else if (database.getClass().isAssignableFrom(DB2Database.class)) {
           if (((DB2Database) database).supportsBooleanDataType()) {
               return new DatabaseDataType("BOOLEAN");
           } else {
               return new DatabaseDataType("SMALLINT");
           }
       } else if (database instanceof HsqlDatabase) {
           return new DatabaseDataType("BOOLEAN");
       } else if (database instanceof PostgresDatabase) {
           if (originalDefinition.toLowerCase(Locale.US).startsWith("bit")) {
               return new DatabaseDataType("BIT", getParameters());
           }
       } else if(database instanceof KingbaseDatabase) {
           return new DatabaseDataType("bit");
       }
       return super.toDatabaseDataType(database);
   }
   @Override
   public String objectToSql(Object value, Database database) {
       if ((value == null) || "null".equals(value.toString().toLowerCase(Locale.US))) {
           return null;
       }
       String returnValue;
       if (value instanceof String) {
           value = ((String) value).replaceAll("'", "");
           if ("true".equals(((String) value).toLowerCase(Locale.US)) || "1".equals(value) || "b'1'".equals(((String) value).toLowerCase(Locale.US)) || "t".equals(((String) value).toLowerCase(Locale.US)) || ((String) value).toLowerCase(Locale.US).equals(this.getTrueBooleanValue(database).toLowerCase(Locale.US))) {
               returnValue = this.getTrueBooleanValue(database);
           } else if ("false".equals(((String) value).toLowerCase(Locale.US)) || "0".equals(value) || "b'0'".equals(
                   ((String) value).toLowerCase(Locale.US)) || "f".equals(((String) value).toLowerCase(Locale.US)) || ((String) value).toLowerCase(Locale.US).equals(this.getFalseBooleanValue(database).toLowerCase(Locale.US))) {
               returnValue = this.getFalseBooleanValue(database);
           } else {
               throw new UnexpectedLiquibaseException("Unknown boolean value: " + value);
           }
       } else if (value instanceof Long) {
           if (Long.valueOf(1).equals(value)) {
               returnValue = this.getTrueBooleanValue(database);
           } else {
               returnValue = this.getFalseBooleanValue(database);
           }
       } else if (value instanceof Number) {
           if (value.equals(1) || "1".equals(value.toString()) || "1.0".equals(value.toString())) {
               returnValue = this.getTrueBooleanValue(database);
           } else {
               returnValue = this.getFalseBooleanValue(database);
           }
       } else if (value instanceof DatabaseFunction) {
           return value.toString();
       } else if (value instanceof Boolean) {
           if (((Boolean) value)) {
               returnValue = this.getTrueBooleanValue(database);
           } else {
               returnValue = this.getFalseBooleanValue(database);
           }
       } else {
           throw new UnexpectedLiquibaseException("Cannot convert type " + value.getClass() + " to a boolean value");
       }
       return returnValue;
   }
   protected boolean isNumericBoolean(Database database) {
       if (database instanceof DerbyDatabase) {
           return !((DerbyDatabase) database).supportsBooleanDataType();
       } else if (database.getClass().isAssignableFrom(DB2Database.class)) {
           return !((DB2Database) database).supportsBooleanDataType();
       }
       return (database instanceof Db2zDatabase) || (database instanceof DB2Database) || (database instanceof FirebirdDatabase) || (database instanceof
               MSSQLDatabase) || (database instanceof MySQLDatabase) || (database instanceof OracleDatabase) ||
               (database instanceof SQLiteDatabase) || (database instanceof SybaseASADatabase) || (database instanceof
               SybaseDatabase) || (database instanceof KingbaseDatabase);
   }
   public String getFalseBooleanValue(Database database) {
       if (isNumericBoolean(database)) {
           return "0";
       }
       if (database instanceof InformixDatabase) {
           return "'f'";
       }
       return "FALSE";
   }
   public String getTrueBooleanValue(Database database) {
       if (isNumericBoolean(database)) {
           return "1";
       }
       if (database instanceof InformixDatabase) {
           return "'t'";
       }
       return "TRUE";
   }
   @Override
   public LOAD_DATA_TYPE getLoadTypeName() {
       return LOAD_DATA_TYPE.BOOLEAN;
   }
}
  • 编译当前整个工程。在 common-flow 的 target 文件夹中,找到前两步添加的 KingbaseDatabase 和 BooleanType 类的编译后 class 文件。
  • 定位 liquibase-core-4.9.1.jar 的所在目录,并用解压工具手动解压该 jar 文件。
  • 将之前在 common-flow 包中编译成功的两个文件 KingbaseDatabase.class 和 BooleanType.class 拷贝到上一步解压的 liquibase-core-4.9.1 子目录内,两个目标文件的具体位置见下图。
  • 修改 liquibase-core-4.9.1/META-INF/services/liquibase.database.Database 文件,并添加以下配置。
  • 切记要在 liquibase-core-4.9.1 目录内执行打包命令,再将打包后的 liquibase-core-4.3.5.jar 移出至上级目录,并覆盖原有文件。具体操作见下图。
  • 回到开发环境,手动删除 common-flow 包内的两个源文件 KingbaseDatabase.java 和 BooleanType.java。最后重新编译整个工程,即可引用前面手动打包的 liquibase-core-4.9.1.jar。

人大金仓驱动导入

  • 下载人大金仓的 JDBC 驱动
  • 执行 Maven 命令,将上一步下载的 jar,手动导入到本地仓库。
# 将/Users/xxx/Downloads/jdbc-x86,改为实际下载路径。
mvn install:install-file -Dfile=/Users/xxxx/Downloads/jdbc-x86/kingbase8-8.6.0.jar -DgroupId=com.kingbase -DartifactId=kingbase8 -Dversion=8.6.0 -Dpackaging=jar

生成器客户端Oracle驱动

Windows

  • 下载 Oracle 最新版客户端连接驱动 instantclient-basiclite-windows 和 instantclient-basic-windows。
  • 解压下载的驱动到本地目录,如 C:\oracle。
  • 添加环境变量。OCI_INC_DIR = C:\oracle\instantclient_21_3\sdk\include 和 OCI_LIB_DIR = C:\oracle\instantclient_21_3\sdk\lib\msvc
  • 重启后生效。

Mac

  • 下载Oracle最新版客户端连接驱动 [Download]。
  • 将解压后的文件,全部拷贝到 /usr/local/lib 目录下。

部分代码生成合并

橙单支持部分代码生成的功能,以便于大家进行生成后工程的代码合并。即可以选定需要生成的表单、数据源、字典和工作流等,生成器将单独生成与之相关前后端代码和数据库脚本。其中多租户工程的部分代码生成,一定要非常注意数据库脚本的执行,更多详情可参考下面的具体明细部分。

这里我们只是列出代码合并过程中的注意事项和建议,具体操作大家可以自行调整。

数据源

  • 我们将根据具体的选择条目,生成相关的代码,生成后的代码目录结构和文件名,均与全部生成时完全一致,如果新生成的文件在原有工程中并不存在,可以直接替换,如下图。
  • 如果指定的数据源中包含多对多关联,除了该数据源相关代码会被生成之外,多对多中间表的相关代码也会被生成,见下图。
  • 由于数据源关联从表也是数据源,因此这些关联从表的数据源需要单独选择才会被生成,如下图。
  • 选择数据源时,不仅仅会生成后台Java代码,还会生成与之相关的前端工程代码,如数据源接口访问的代码文件,见下图。

数据字典

  • 选择字典生成的时候,如果是常量字典,可以生成该字典对应的常量类文件,字典表字典和数据源字典,则会生成对应的 Java 代码,见下图。
  • 选择字典生成时,还会生成前端相应的代码,不同的字典类型处理方式不同,如果是数据源字典,会按照数据源的方式去生成。对于常量字典,并没有挑出哪些是本次选择的字典,因此需要手动合并。

工作流

  • 选择部分生成工作流、表单、数据源和字典时,在生成代码文件的同时,还会生成数据库的脚本文件,其中 rollback-upms-data-script.sql 文件是删除全部自动生成的权限数据脚本,init-upms-data-script.sql 则是重新加载最新的权限数据脚本。由于自动生成的权限部分,主键值均保持不变,因此可以先执行 rollback-upms-data-script.sql 脚本,再执行 init-upms-data-script.sql,重新加载最新数据。此操作不会删除用户自己手动创建的任何权限相关数据。不过出于保险起见,建议执行操作前,先备份一下数据库。
  • 工作流部分生成,可以根据用户的选择,生成与该工作流关联的表单、数据源和字典。被选择的表单、数据源和字典相关的前后端代码,均会被生成。具体合并方式,上面已经介绍过了,这里不再重复了。唯一的差异是,工作流还会生成工作流数据的插入脚本 partial-flow-script.sql,在工作流表所在的数据库中,执行该脚本,如下图。

多租户的坑

  • 多租户也是支持表单、数据源和字典的部分生成。生成后的代码与非多租户工程无异,只是会多出两个适用于多租户的租户关联权限脚本 init-tenant-admin-data.sql 和 rollback-tenant-admin-data.sql,功能也是删除并重新插入全部生成后的权限数据。该文件同时包含租户管理和租户运营平台中的权限脚本数据,如果你本次部分生成的所有项目,都是位于 tenant-admin 服务中,那么只需执行这两个文件中的 zz_sys 开头的脚本数据,一定不要轻易执行 zz_sys_tenant 开头的脚本,后者为租户运营所需的权限数据,特别是已经创建了新的租户之后,就一定不要执行 zz_sys_tenant 的数据操作。因为租户的权限数据,是需要实时同步的,对于这种情况,只能在前端页面,手动逐条添加了。

再执行多租户部分生成的数据库脚本时,请务必先备份原有数据库数据,然后再确认数据更新是否正确。

最后的总结,多租户部分生成中产生的前后端代码,如果原有工程中不存在该代码文件,可以直接对号入座的 copy,如果存在,就需要手动合并,此种多为前端代码,如路由表、字典表管理页面等。重中之重是数据库脚本的执行,一定要非常慎重。

数据库迁移

目前橙单已支持 MySQL、PostgreSQL、Oracle、SQL Server、达梦、人大金仓和华为高斯,如果开发者用户希望迁移到其他类型的数据库,如华为高斯等,可以参考该小节。这里我们以 MySQL 迁移到 PostgreSQL 为例。

  • 修改 common-core 目录下的 pom.xml,移除之前对原有数据库驱动的依赖,新增对新数据库驱动的依赖。
<!-- 移除下面对原有数据库驱动的依赖,如MySQL -->
<dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <scope>runtime</scope>
</dependency>
<!-- 新增对新数据库驱动的依赖,如PostgreSQL -->
<dependency>
   <groupId>org.postgresql</groupId>
   <artifactId>postgresql</artifactId>
   <scope>runtime</scope>
</dependency>
  • 修改 PageHelper 分页组件的配置项。在当前工程的配置文件中搜索「pagehelper:」,注意后面有冒号,然后进行如下修改。
pagehelper:
  # helperDialect的配置值,从原有数据库类型改为新类型,如这里从msyql改为postgresql
  helperDialect: postgresql
  reasonable: true
  supportMethodsArguments: false
  params: count=countSql
  • 修改 common-core 的配置项,在当前工程的配置文件中搜索「common-core:」,注意后面有冒号,然后进行如下修改。
common-core:
  # databaseType的配置值,从原有数据库类型改为新类型,如这里从msyql改为postgresql
  databaseType: postgresql
  • 修改数据源连接池的配置项,从原有数据库驱动类型改为新类型。此类配置项可能存在多处,需要根据实际情况全部进行修改。如下面的例子。
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      url: jdbc:postgresql://localhost:5432/zzdemo-online?currentSchema=public
      username: postgres
      password: 123456
      driver-class-name: org.postgresql.Driver
  • 数据库表迁移,将橙单默认生成的内置表和用户自行导入的业务表,从原有类型的数据库中,导入到新的目标类型数据库中。这里有些注意事项和迁移建议。
  1. 在业务表中,尽量使用比较通用的字段,比如 CHAR、VARCHAT、TIMESTAMP、INT 这种,尽量减少个性化字段类型,除非必要,我们建议尽量少使用 BIT,会给整个迁移过程带来很多麻烦。
  2. 在使用橙单时,你的所有数据表,一定是位于橙单支持的数据库中,否则是无法导入到橙单进行配置的,如果要迁移到新目标类型的数据库,可以考虑使用 Navicat 等工具进行异构类型的数据库表结构和数据的迁移。
  • 在所有实体对象对应的 Mapper.xml 中,修改个性化 SQL。主要包含批量插入、数据库内置函数等。需要注意的是,这类问题会存在多处,需要全部进行修改。

批量插入。

<!-- 橙单的方法定义都比较标准,可以考虑搜索insertList,这里是MySQL和PostgreSQL对批量插入的语法,如果目标类型的数据库与此相同,那就不用修改了,否则需要改为符合目标数据库SQL语法的代码。 -->
<insert id="insertList">
    INSERT INTO zz_flow_entry_publish_variable VALUES
    <foreach collection="list" index="index" item="item" separator="," >
          (#{item.variableId},
          #{item.entryPublishId},
          #{item.variableName},
          #{item.showName},
          #{item.variableType},
          #{item.bindDatasourceId},
          #{item.bindRelationId},
          #{item.bindColumnId},
          #{item.builtin})
    </foreach>
</insert>

模糊搜索。

<sql id="inputFilterRef">
    <if test="paperFilter != null">
        <if test="paperFilter.searchString != null and paperFilter.searchString != ''">
        <bind name = "safePaperSearchString" value = "'%' + paperFilter.searchString + '%'" />
            <!-- 修改下面CONCAT和IFNULL内置方法,改为目标数据库中对等功能的方法。 -->
            AND CONCAT(IFNULL(zz_paper.paper_name,''), IFNULL(zz_paper.description,'')) LIKE #{safePaperSearchString}
        </if>
    </if>
</sql>
  • 修改 MyGroupParam.java 中的代码,如下图。

  • 对于微服务工程,修改 BaseController.java 中的代码,如下图。

  • 如果支持在线表单模块,需要为新的数据库提供 JdbcConfig 和 DataSourceProvider 的子类,目前橙单已经为 MySQL、PostgreSQL、Oracle、达梦和人大金仓数据库,提供了相应的实现,具体见下图。
  • 如果支持工作流,先暂时修改工作流的配置项,以便在服务启动时,可以在目标数据库中,自动创建工作流所需的数据库脚本。正常启动后,推荐将下面的 database-schema-update 配置项,再次改回之前的 false。如下图。

结语

赠人玫瑰,手有余香,感谢您的支持和关注,选择橙单,效率乘三,收入翻番。