Преглед на файлове

feat: 添加数据库管理模块/数据库连接/数据库目录

wanggaokun преди 1 година
родител
ревизия
6ff71ec93e
променени са 87 файла, в които са добавени 7364 реда и са изтрити 8 реда
  1. 1 0
      km-admin/src/main/java/com/km/web/controller/AuthController.java
  2. 4 0
      km-admin/src/main/resources/application.yml
  3. 5 0
      km-common/km-common-core/pom.xml
  4. 5 0
      km-common/km-common-core/src/main/java/com/km/common/core/exception/ServiceException.java
  5. 19 0
      km-common/km-common-db/pom.xml
  6. 123 0
      km-common/km-common-db/src/main/java/com/km/common/db/Utils/DDLFormatterUtils.java
  7. 231 0
      km-common/km-common-db/src/main/java/com/km/common/db/Utils/DataSourceUtils.java
  8. 127 0
      km-common/km-common-db/src/main/java/com/km/common/db/Utils/DatabaseAwareUtils.java
  9. 60 0
      km-common/km-common-db/src/main/java/com/km/common/db/Utils/ExamineUtils.java
  10. 186 0
      km-common/km-common-db/src/main/java/com/km/common/db/Utils/GenerateSqlUtils.java
  11. 1029 0
      km-common/km-common-db/src/main/java/com/km/common/db/Utils/ObjectCastUtils.java
  12. 20 0
      km-common/km-common-db/src/main/java/com/km/common/db/annotation/Product.java
  13. 83 0
      km-common/km-common-db/src/main/java/com/km/common/db/constant/DbConstants.java
  14. 19 0
      km-common/km-common-db/src/main/java/com/km/common/db/entity/CloseableDataSource.java
  15. 26 0
      km-common/km-common-db/src/main/java/com/km/common/db/entity/GlobalParamConfigProperties.java
  16. 117 0
      km-common/km-common-db/src/main/java/com/km/common/db/entity/InvisibleDataSource.java
  17. 87 0
      km-common/km-common-db/src/main/java/com/km/common/db/entity/JarClassLoader.java
  18. 44 0
      km-common/km-common-db/src/main/java/com/km/common/db/entity/PatternMapper.java
  19. 66 0
      km-common/km-common-db/src/main/java/com/km/common/db/entity/ResultSetWrapper.java
  20. 30 0
      km-common/km-common-db/src/main/java/com/km/common/db/entity/SourceDataSourceProperties.java
  21. 38 0
      km-common/km-common-db/src/main/java/com/km/common/db/entity/TargetDataSourceProperties.java
  22. 93 0
      km-common/km-common-db/src/main/java/com/km/common/db/entity/WrapCommonDataSource.java
  23. 120 0
      km-common/km-common-db/src/main/java/com/km/common/db/entity/WrapHikariDataSource.java
  24. 28 0
      km-common/km-common-db/src/main/java/com/km/common/db/enums/CaseConvertEnum.java
  25. 29 0
      km-common/km-common-db/src/main/java/com/km/common/db/enums/DbProductTableEnum.java
  26. 341 0
      km-common/km-common-db/src/main/java/com/km/common/db/enums/DbProductTypeEnum.java
  27. 24 0
      km-common/km-common-db/src/main/java/com/km/common/db/enums/DbTableIndexEnum.java
  28. 40 0
      km-common/km-common-db/src/main/java/com/km/common/db/enums/SyncOptionEnum.java
  29. 85 0
      km-common/km-common-db/src/main/java/com/km/common/db/provider/AbstractCommonProvider.java
  30. 37 0
      km-common/km-common-db/src/main/java/com/km/common/db/provider/AbstractFactoryProvider.java
  31. 320 0
      km-common/km-common-db/src/main/java/com/km/common/db/provider/AbstractMetadataProvider.java
  32. 38 0
      km-common/km-common-db/src/main/java/com/km/common/db/provider/AutoCastTableDataSynchronizeProvider.java
  33. 51 0
      km-common/km-common-db/src/main/java/com/km/common/db/provider/ColumnValueDataMapTable.java
  34. 138 0
      km-common/km-common-db/src/main/java/com/km/common/db/provider/DefaultTableDataQueryProvider.java
  35. 267 0
      km-common/km-common-db/src/main/java/com/km/common/db/provider/DefaultTableDataSynchronizeProvider.java
  36. 86 0
      km-common/km-common-db/src/main/java/com/km/common/db/provider/DefaultTableDataWriteProvider.java
  37. 47 0
      km-common/km-common-db/src/main/java/com/km/common/db/provider/DefaultTableManageProvider.java
  38. 31 0
      km-common/km-common-db/src/main/java/com/km/common/db/provider/IDefaultTransformProvider.java
  39. 170 0
      km-common/km-common-db/src/main/java/com/km/common/db/provider/IMetadataProvider.java
  40. 88 0
      km-common/km-common-db/src/main/java/com/km/common/db/provider/IProductFactoryProvider.java
  41. 19 0
      km-common/km-common-db/src/main/java/com/km/common/db/provider/IProductFeatures.java
  42. 16 0
      km-common/km-common-db/src/main/java/com/km/common/db/provider/IRecordTransformProvider.java
  43. 74 0
      km-common/km-common-db/src/main/java/com/km/common/db/provider/ITableDataQueryProvider.java
  44. 62 0
      km-common/km-common-db/src/main/java/com/km/common/db/provider/ITableDataSynchronizeProvider.java
  45. 27 0
      km-common/km-common-db/src/main/java/com/km/common/db/provider/ITableDataWriteProvider.java
  46. 25 0
      km-common/km-common-db/src/main/java/com/km/common/db/provider/ITableManageProvider.java
  47. 46 0
      km-common/km-common-db/src/main/java/com/km/common/db/provider/MappedTransformProvider.java
  48. 76 0
      km-common/km-common-db/src/main/java/com/km/common/db/provider/ProductProviderFactory.java
  49. 23 0
      km-common/km-common-db/src/main/java/com/km/common/db/provider/SchemaTableColumnTuple.java
  50. 50 0
      km-common/km-common-db/src/main/java/com/km/common/db/schema/ColumnDescription.java
  51. 480 0
      km-common/km-common-db/src/main/java/com/km/common/db/schema/ColumnMetaData.java
  52. 23 0
      km-common/km-common-db/src/main/java/com/km/common/db/schema/IndexDescription.java
  53. 17 0
      km-common/km-common-db/src/main/java/com/km/common/db/schema/IndexFieldMeta.java
  54. 19 0
      km-common/km-common-db/src/main/java/com/km/common/db/schema/SchemaTableData.java
  55. 20 0
      km-common/km-common-db/src/main/java/com/km/common/db/schema/SchemaTableMeta.java
  56. 32 0
      km-common/km-common-db/src/main/java/com/km/common/db/schema/TableDescription.java
  57. 223 0
      km-common/km-common-db/src/main/java/com/km/common/db/service/DefaultMetadataService.java
  58. 159 0
      km-common/km-common-db/src/main/java/com/km/common/db/service/IMetadataService.java
  59. 1 0
      km-common/pom.xml
  60. 6 0
      km-modules/km-db/pom.xml
  61. 322 0
      km-modules/km-db/src/main/java/com/km/db/Utils/JdbcUrlUtils.java
  62. 43 0
      km-modules/km-db/src/main/java/com/km/db/config/DbConfig.java
  63. 30 0
      km-modules/km-db/src/main/java/com/km/db/config/data/DbPropertiesConfiguration.java
  64. 42 0
      km-modules/km-db/src/main/java/com/km/db/config/data/DbPropertySourceFactory.java
  65. 63 0
      km-modules/km-db/src/main/java/com/km/db/config/data/DbTaskExecutorConfig.java
  66. 132 0
      km-modules/km-db/src/main/java/com/km/db/config/register/ProductRegisterAutoConfiguration.java
  67. 43 0
      km-modules/km-db/src/main/java/com/km/db/controller/DatabaseConnectionController.java
  68. 74 0
      km-modules/km-db/src/main/java/com/km/db/controller/MetaDataController.java
  69. 13 1
      km-modules/km-db/src/main/java/com/km/db/domain/DatabaseConnection.java
  70. 22 0
      km-modules/km-db/src/main/java/com/km/db/domain/vo/DatabaseTypeDetailVo.java
  71. 22 0
      km-modules/km-db/src/main/java/com/km/db/domain/vo/DatabaseTypeDriverVo.java
  72. 20 0
      km-modules/km-db/src/main/java/com/km/db/domain/vo/DbConnectionNameVo.java
  73. 25 0
      km-modules/km-db/src/main/java/com/km/db/domain/vo/MetadataColumnDetailVo.java
  74. 18 0
      km-modules/km-db/src/main/java/com/km/db/domain/vo/MetadataSchemaDetailVo.java
  75. 25 0
      km-modules/km-db/src/main/java/com/km/db/domain/vo/MetadataTableDetailVo.java
  76. 18 0
      km-modules/km-db/src/main/java/com/km/db/domain/vo/MetadataTableInfoVo.java
  77. 21 0
      km-modules/km-db/src/main/java/com/km/db/domain/vo/SchemaTableDataVo.java
  78. 51 0
      km-modules/km-db/src/main/java/com/km/db/product/dm/DmFactoryProvider.java
  79. 11 0
      km-modules/km-db/src/main/java/com/km/db/product/dm/DmFeatures.java
  80. 194 0
      km-modules/km-db/src/main/java/com/km/db/product/dm/DmMetadataQueryProvider.java
  81. 29 0
      km-modules/km-db/src/main/java/com/km/db/product/dm/DmTableDataWriteProvider.java
  82. 22 0
      km-modules/km-db/src/main/java/com/km/db/product/oracle/OracleTableManageProvider.java
  83. 90 0
      km-modules/km-db/src/main/java/com/km/db/service/DriverLoadService.java
  84. 23 0
      km-modules/km-db/src/main/java/com/km/db/service/IDatabaseConnectionService.java
  85. 101 7
      km-modules/km-db/src/main/java/com/km/db/service/impl/DatabaseConnectionServiceImpl.java
  86. 158 0
      km-modules/km-db/src/main/java/com/km/db/service/impl/MetaDataService.java
  87. 1 0
      km-modules/km-db/src/main/resources/META-INF/services/db.providers

+ 1 - 0
km-admin/src/main/java/com/km/web/controller/AuthController.java

@@ -3,6 +3,7 @@ package com.km.web.controller;
 import cn.dev33.satoken.annotation.SaIgnore;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
 import com.km.common.core.constant.UserConstants;
 import com.km.common.core.core.domain.CommonResult;
 import com.km.common.core.core.domain.model.LoginBody;

+ 4 - 0
km-admin/src/main/resources/application.yml

@@ -321,3 +321,7 @@ websocket:
   path: /resource/websocket
   # 设置访问源地址
   allowedOrigins: '*'
+db:
+  configuration:
+    drivers-base-path: D:/code/dbswitch/drivers
+

+ 5 - 0
km-common/km-common-core/pom.xml

@@ -115,6 +115,11 @@
             <artifactId>hutool-core</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-json</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>cn.hutool</groupId>
             <artifactId>hutool-http</artifactId>

+ 5 - 0
km-common/km-common-core/src/main/java/com/km/common/core/exception/ServiceException.java

@@ -51,6 +51,11 @@ public final class ServiceException extends RuntimeException {
         this.msgParams = msgParams;
     }
 
+    public ServiceException(String msg, Object... msgParams) {
+        this.message = msg;
+        this.msgParams = msgParams;
+    }
+
     @Override
     public String getMessage() {
         if(StrUtil.isNotBlank(getMessage())){

+ 19 - 0
km-common/km-common-db/pom.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>com.km</groupId>
+        <artifactId>km-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>km-common-db</artifactId>
+    <description>km-common-db</description>
+    <dependencies>
+        <dependency>
+            <groupId>com.km</groupId>
+            <artifactId>km-common-core</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 123 - 0
km-common/km-common-db/src/main/java/com/km/common/db/Utils/DDLFormatterUtils.java

@@ -0,0 +1,123 @@
+package com.km.common.db.Utils;
+
+import lombok.experimental.UtilityClass;
+
+import java.util.Locale;
+import java.util.StringTokenizer;
+
+/**
+ * @Description: DDL的SQL语句格式化(hibernate)
+ *
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+@UtilityClass
+public class DDLFormatterUtils {
+
+    public static String format(String sql) {
+        if (null == sql || sql.isEmpty()) {
+            return sql;
+        }
+        if (sql.toLowerCase(Locale.ROOT).startsWith("create table")) {
+            return formatCreateTable(sql);
+        } else if (sql.toLowerCase(Locale.ROOT).startsWith("alter table")) {
+            return formatAlterTable(sql);
+        } else if (sql.toLowerCase(Locale.ROOT).startsWith("comment on")) {
+            return formatCommentOn(sql);
+        } else {
+            return "\n    " + sql;
+        }
+    }
+
+    private static String formatCommentOn(String sql) {
+        final StringBuilder result = new StringBuilder(60).append("    ");
+        final StringTokenizer tokens = new StringTokenizer(sql, " '[]\"", true);
+
+        boolean quoted = false;
+        while (tokens.hasMoreTokens()) {
+            final String token = tokens.nextToken();
+            result.append(token);
+            if (isQuote(token)) {
+                quoted = !quoted;
+            } else if (!quoted) {
+                if ("is".equals(token)) {
+                    result.append("\n       ");
+                }
+            }
+        }
+
+        return result.toString();
+    }
+
+    private static String formatAlterTable(String sql) {
+        final StringBuilder result = new StringBuilder(60).append("    ");
+        final StringTokenizer tokens = new StringTokenizer(sql, " (,)'[]\"", true);
+
+        boolean quoted = false;
+        while (tokens.hasMoreTokens()) {
+            final String token = tokens.nextToken();
+            if (isQuote(token)) {
+                quoted = !quoted;
+            } else if (!quoted) {
+                if (isBreak(token)) {
+                    result.append("\n        ");
+                }
+            }
+            result.append(token);
+        }
+
+        return result.toString();
+    }
+
+    private static String formatCreateTable(String sql) {
+        final StringBuilder result = new StringBuilder(60).append("    ");
+        final StringTokenizer tokens = new StringTokenizer(sql, "(,)'[]\"", true);
+
+        int depth = 0;
+        boolean quoted = false;
+        while (tokens.hasMoreTokens()) {
+            final String token = tokens.nextToken();
+            if (isQuote(token)) {
+                quoted = !quoted;
+                result.append(token);
+            } else if (quoted) {
+                result.append(token);
+            } else {
+                if (")".equals(token)) {
+                    depth--;
+                    if (depth == 0) {
+                        result.append("\n    ");
+                    }
+                }
+                result.append(token);
+                if (",".equals(token) && depth == 1) {
+                    result.append("\n       ");
+                }
+                if ("(".equals(token)) {
+                    depth++;
+                    if (depth == 1) {
+                        result.append("\n        ");
+                    }
+                }
+            }
+        }
+
+        return result.toString();
+    }
+
+    private static boolean isBreak(String token) {
+        return "drop".equals(token) ||
+            "add".equals(token) ||
+            "references".equals(token) ||
+            "foreign".equals(token) ||
+            "on".equals(token);
+    }
+
+    private static boolean isQuote(String tok) {
+        return "\"".equals(tok) ||
+            "`".equals(tok) ||
+            "]".equals(tok) ||
+            "[".equals(tok) ||
+            "'".equals(tok);
+    }
+}

+ 231 - 0
km-common/km-common-db/src/main/java/com/km/common/db/Utils/DataSourceUtils.java

@@ -0,0 +1,231 @@
+package com.km.common.db.Utils;
+
+import cn.hutool.core.util.ClassLoaderUtil;
+import com.km.common.db.entity.CloseableDataSource;
+import com.km.common.db.entity.InvisibleDataSource;
+import com.km.common.db.entity.JarClassLoader;
+import com.km.common.db.entity.SourceDataSourceProperties;
+import com.km.common.db.entity.TargetDataSourceProperties;
+import com.km.common.db.entity.WrapCommonDataSource;
+import com.km.common.db.entity.WrapHikariDataSource;
+import com.zaxxer.hikari.HikariDataSource;
+import lombok.experimental.UtilityClass;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.sql.DataSource;
+import java.net.URLClassLoader;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @Description: DataSourceUtils
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+@Slf4j
+@UtilityClass
+public class DataSourceUtils {
+
+    public static final int MAX_THREAD_COUNT = 10;
+    public static final int MAX_TIMEOUT_MS = 60000;
+
+    private static final Map<String, URLClassLoader> CLASS_LOADER_MAP = new ConcurrentHashMap<>();
+
+    /**
+     * 创建于指定数据库连接描述符的连接池
+     *
+     * @param properties 数据库连接描述符
+     * @return HikariDataSource连接池
+     */
+    public static CloseableDataSource createSourceDataSource(SourceDataSourceProperties properties) {
+        Properties parameters = new Properties();
+        HikariDataSource ds = new HikariDataSource();
+        ds.setPoolName("The_Source_DB_Connection");
+        ds.setJdbcUrl(properties.getUrl());
+        if (properties.getDriverClassName().contains("oracle")) {
+            ds.setConnectionTestQuery("SELECT 'Hello' from DUAL");
+            // https://blog.csdn.net/qq_20960159/article/details/78593936
+            System.getProperties().setProperty("oracle.jdbc.J2EE13Compliant", "true");
+            // Oracle在通过jdbc连接的时候需要添加一个参数来设置是否获取注释
+            parameters.put("remarksReporting", "true");
+        } else if (properties.getDriverClassName().contains("db2")) {
+            ds.setConnectionTestQuery("SELECT 1 FROM SYSIBM.SYSDUMMY1");
+        } else if (properties.getDriverClassName().contains("mongodb")) {
+            ds.setConnectionTestQuery("use admin;");
+        } else if (!ds.getJdbcUrl().contains("jdbc:jest://")) {
+            ds.setConnectionTestQuery("SELECT 1");
+        }
+        ds.setMaximumPoolSize(MAX_THREAD_COUNT);
+        ds.setMinimumIdle(MAX_THREAD_COUNT);
+        ds.setMaxLifetime(properties.getMaxLifeTime());
+        ds.setConnectionTimeout(properties.getConnectionTimeout());
+        ds.setIdleTimeout(MAX_TIMEOUT_MS);
+
+        URLClassLoader urlClassLoader = createURLClassLoader(
+            properties.getDriverPath(),
+            properties.getDriverClassName());
+        InvisibleDataSource dataSource = createInvisibleDataSource(
+            urlClassLoader,
+            properties.getUrl(),
+            properties.getDriverClassName(),
+            properties.getUsername(),
+            properties.getPassword(),
+            parameters
+        );
+        ds.setDataSource(dataSource);
+
+        return new WrapHikariDataSource(ds, urlClassLoader);
+    }
+
+    /**
+     * 创建于指定数据库连接描述符的连接池
+     *
+     * @param properties 数据库连接描述符
+     * @return HikariDataSource连接池
+     */
+    public static CloseableDataSource createTargetDataSource(TargetDataSourceProperties properties) {
+        HikariDataSource ds = new HikariDataSource();
+        ds.setPoolName("The_Target_DB_Connection");
+        ds.setJdbcUrl(properties.getUrl());
+        if (properties.getDriverClassName().contains("oracle")) {
+            ds.setConnectionTestQuery("SELECT 'Hello' from DUAL");
+        } else if (properties.getDriverClassName().contains("db2")) {
+            ds.setConnectionTestQuery("SELECT 1 FROM SYSIBM.SYSDUMMY1");
+        } else if (properties.getDriverClassName().contains("mongodb")) {
+            ds.setConnectionTestQuery("use admin;");
+        } else if (!ds.getJdbcUrl().contains("jdbc:jest://")) {
+            ds.setConnectionTestQuery("SELECT 1");
+        }
+        ds.setMaximumPoolSize(MAX_THREAD_COUNT);
+        ds.setMinimumIdle(MAX_THREAD_COUNT);
+        ds.setMaxLifetime(properties.getMaxLifeTime());
+        ds.setConnectionTimeout(properties.getConnectionTimeout());
+        ds.setIdleTimeout(MAX_TIMEOUT_MS);
+
+        URLClassLoader urlClassLoader = createURLClassLoader(
+            properties.getDriverPath(),
+            properties.getDriverClassName());
+        InvisibleDataSource dataSource = createInvisibleDataSource(
+            urlClassLoader,
+            properties.getUrl(),
+            properties.getDriverClassName(),
+            properties.getUsername(),
+            properties.getPassword(),
+            new Properties()
+        );
+
+        // 如果是Greenplum数据库,这里需要关闭会话的查询优化器
+        if (properties.getDriverClassName().contains("postgresql")) {
+            String versionString = executeStringReturnedSql(dataSource, "SELECT version()");
+            if (Objects.nonNull(versionString) && versionString.contains("Greenplum")) {
+                log.info(
+                    "#### Target database is Greenplum Cluster, Close Optimizer now: set optimizer to 'off' ");
+                ds.setConnectionInitSql("set optimizer to 'off'");
+            }
+        }
+        ds.setDataSource(dataSource);
+        return new WrapHikariDataSource(ds, urlClassLoader);
+    }
+
+    public static CloseableDataSource createCommonDataSource(
+        String jdbcUrl,
+        String driverClass,
+        String driverPath,
+        String username,
+        String password) {
+        URLClassLoader urlClassLoader = createURLClassLoader(
+            driverPath,
+            driverClass);
+        InvisibleDataSource dataSource = createInvisibleDataSource(
+            urlClassLoader,
+            jdbcUrl,
+            driverClass,
+            username,
+            password,
+            new Properties()
+        );
+        return new WrapCommonDataSource(dataSource, urlClassLoader);
+    }
+
+    public static boolean supportConcurrentWrite(TargetDataSourceProperties properties) {
+        return !properties.getDriverClassName().contains("sqlite")
+            && !properties.getUrl().contains("jdbc:sqlite:");
+    }
+
+    private static InvisibleDataSource createInvisibleDataSource(
+        ClassLoader cl,
+        String jdbcUrl,
+        String driverClass,
+        String username,
+        String password,
+        Properties properties) {
+        return new InvisibleDataSource(
+            cl,
+            jdbcUrl,
+            driverClass,
+            username,
+            password,
+            properties);
+    }
+
+    private static URLClassLoader createURLClassLoader(
+        String driverPath, String driverClass) {
+        ExamineUtils.checkArgument(
+            StringUtils.isNoneBlank(driverPath),
+            "Invalid driver path,can not be empty!");
+        ExamineUtils.checkArgument(
+            StringUtils.isNoneBlank(driverClass),
+            "Invalid driver class,can not be empty!");
+        ClassLoader parent = driverClass.contains("postgresql")
+            ? ClassLoaderUtil.getContextClassLoader()
+            : ClassLoaderUtil.getSystemClassLoader().getParent();
+        URLClassLoader loader = getOrCreateClassLoader(driverPath, parent);
+        try {
+            Class<?> clazz = loader.loadClass(driverClass);
+            clazz.getConstructor().newInstance();
+            return loader;
+        } catch (Exception e) {
+            log.error("Could not load class : {} from driver path: {}", driverClass, driverPath, e);
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static URLClassLoader getOrCreateClassLoader(
+        String path, ClassLoader parent) {
+        URLClassLoader urlClassLoader = CLASS_LOADER_MAP.get(path);
+        if (null == urlClassLoader) {
+            synchronized (DataSourceUtils.class) {
+                urlClassLoader = CLASS_LOADER_MAP.get(path);
+                if (null == urlClassLoader) {
+                    log.info("Create jar classLoader from path: {}", path);
+                    urlClassLoader = new JarClassLoader(path, parent);
+                    CLASS_LOADER_MAP.put(path, urlClassLoader);
+                }
+            }
+        }
+        return urlClassLoader;
+    }
+
+    private static String executeStringReturnedSql(
+        DataSource dataSource, String sql) {
+        try (Connection connection = dataSource.getConnection()) {
+            try (Statement statement = connection.createStatement()) {
+                try (ResultSet resultSet = statement.executeQuery(sql)) {
+                    if (resultSet.next()) {
+                        return resultSet.getString(1);
+                    }
+                    return null;
+                }
+            }
+        } catch (SQLException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}

+ 127 - 0
km-common/km-common-db/src/main/java/com/km/common/db/Utils/DatabaseAwareUtils.java

@@ -0,0 +1,127 @@
+package com.km.common.db.Utils;
+
+import com.km.common.db.enums.DbProductTypeEnum;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @Description: 数据库类型识别工具类
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+public final class DatabaseAwareUtils {
+
+    private static final Map<String, DbProductTypeEnum> productNameMap;
+
+    private static final Map<String, DbProductTypeEnum> driverNameMap;
+
+    static {
+        productNameMap = new HashMap<>();
+        driverNameMap = new HashMap<>();
+
+        productNameMap.put("Microsoft SQL Server", DbProductTypeEnum.SQLSERVER);
+        productNameMap.put("DM DBMS", DbProductTypeEnum.DM);
+        productNameMap.put("KingbaseES", DbProductTypeEnum.KINGBASE);
+        productNameMap.put("Apache Hive", DbProductTypeEnum.HIVE);
+        productNameMap.put("MySQL", DbProductTypeEnum.MYSQL);
+        productNameMap.put("MariaDB", DbProductTypeEnum.MARIADB);
+        productNameMap.put("Oracle", DbProductTypeEnum.ORACLE);
+        productNameMap.put("PostgreSQL", DbProductTypeEnum.POSTGRESQL);
+        productNameMap.put("Highgo", DbProductTypeEnum.HIGHGO);
+        productNameMap.put("DB2 for Unix/Windows", DbProductTypeEnum.DB2);
+        productNameMap.put("Hive", DbProductTypeEnum.HIVE);
+        productNameMap.put("SQLite", DbProductTypeEnum.SQLITE3);
+        productNameMap.put("OSCAR", DbProductTypeEnum.OSCAR);
+        productNameMap.put("GBase", DbProductTypeEnum.GBASE8A);
+        productNameMap.put("Adaptive Server Enterprise", DbProductTypeEnum.SYBASE);
+        productNameMap.put("ClickHouse", DbProductTypeEnum.CLICKHOUSE);
+
+        driverNameMap.put("MySQL Connector Java", DbProductTypeEnum.MYSQL);
+        driverNameMap.put("MariaDB Connector/J", DbProductTypeEnum.MARIADB);
+        driverNameMap.put("Oracle JDBC driver", DbProductTypeEnum.ORACLE);
+        driverNameMap.put("PostgreSQL JDBC Driver", DbProductTypeEnum.POSTGRESQL);
+        driverNameMap.put("Kingbase8 JDBC Driver", DbProductTypeEnum.KINGBASE);
+        driverNameMap.put("IBM Data Server Driver for JDBC and SQLJ", DbProductTypeEnum.DB2);
+        driverNameMap.put("dm.jdbc.driver.DmDriver", DbProductTypeEnum.DM);
+        driverNameMap.put("Hive JDBC", DbProductTypeEnum.HIVE);
+        driverNameMap.put("SQLite JDBC", DbProductTypeEnum.SQLITE3);
+        driverNameMap.put("OSCAR JDBC DRIVER", DbProductTypeEnum.OSCAR);
+        driverNameMap.put("GBase JDBC Driver", DbProductTypeEnum.GBASE8A);
+        driverNameMap.put("jConnect (TM) for JDBC (TM)", DbProductTypeEnum.SYBASE);
+        driverNameMap.put("ClickHouse JDBC Driver", DbProductTypeEnum.CLICKHOUSE);
+    }
+
+    /**
+     * 获取数据库的产品名
+     *
+     * @param dataSource 数据源
+     * @return 数据库产品名称字符串
+     */
+    public static DbProductTypeEnum getProductTypeByDataSource(DataSource dataSource) {
+        try (Connection connection = dataSource.getConnection()) {
+            String productName = connection.getMetaData().getDatabaseProductName();
+            String driverName = connection.getMetaData().getDriverName();
+            if (driverNameMap.containsKey(driverName)) {
+                DbProductTypeEnum productType = driverNameMap.get(driverName);
+                if (productType == DbProductTypeEnum.POSTGRESQL) {
+                    String url = connection.getMetaData().getURL();
+                    if (null != url && url.contains("jdbc:opengauss:")) {
+                        return DbProductTypeEnum.OPENGAUSS;
+                    }
+                    if (null != url && url.contains("jdbc:highgo:")) {
+                        return DbProductTypeEnum.HIGHGO;
+                    }
+                }
+                return productType;
+            }
+
+            DbProductTypeEnum type = productNameMap.get(productName);
+            if (null != type) {
+                return type;
+            }
+            String url = connection.getMetaData().getURL();
+            if (null != url && url.contains("mongodb://")) {
+                return DbProductTypeEnum.MONGODB;
+            }
+            if (null != url && url.contains("jest://")) {
+                return DbProductTypeEnum.ELASTICSEARCH;
+            }
+            throw new IllegalStateException("Unable to detect database type from data source instance");
+        } catch (SQLException se) {
+            throw new RuntimeException(se);
+        }
+    }
+
+    /**
+     * 检查MySQL数据库表的存储引擎是否为Innodb
+     *
+     * @param schemaName schema名
+     * @param tableName  table名
+     * @param dataSource 数据源
+     * @return 为Innodb存储引擎时返回True, 否在为false
+     */
+    public static boolean isMysqlInnodbStorageEngine(String schemaName, String tableName,
+                                                     DataSource dataSource) {
+        String sql = "SELECT count(*) as total FROM information_schema.tables "
+            + "WHERE table_schema=? AND table_name=? AND ENGINE='InnoDB'";
+        try (Connection connection = dataSource.getConnection();
+             PreparedStatement ps = connection.prepareStatement(sql)) {
+            ps.setString(1, schemaName);
+            ps.setString(2, tableName);
+            try (ResultSet rs = ps.executeQuery()) {
+                if (rs.next()) {
+                    return rs.getInt(1) > 0;
+                }
+            }
+            return false;
+        } catch (SQLException se) {
+            throw new RuntimeException(se);
+        }
+    }
+}

+ 60 - 0
km-common/km-common-db/src/main/java/com/km/common/db/Utils/ExamineUtils.java

@@ -0,0 +1,60 @@
+package com.km.common.db.Utils;
+
+import cn.hutool.core.lang.Assert;
+import jakarta.annotation.Nullable;
+import lombok.experimental.UtilityClass;
+
+import java.util.Collection;
+
+/**
+ * @Description: ExamineUtils
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+@UtilityClass
+public final class ExamineUtils {
+
+    public static void checkNotNull(Object object, String elem) {
+        Assert.notNull(object, "The '%s' can't be null", elem);
+    }
+
+    public static void checkNotNull(Object object, String elem, String owner) {
+        Assert.notNull(object,
+            "The '%s' of '%s' can't be null",
+            elem, owner);
+    }
+
+    public static void checkNotEmpty(Collection<?> collection, String elem) {
+        Assert.isTrue(!collection.isEmpty(), "The '%s' can't be empty", elem);
+    }
+
+    public static void checkNotEmpty(Collection<?> collection, String elem, String owner) {
+        Assert.isTrue(!collection.isEmpty(),
+            "The '%s' of '%s' can't be empty",
+            elem, owner);
+    }
+
+    public static void checkArgument(boolean expression,
+                                     @Nullable String message,
+                                     @Nullable Object... args) {
+        Assert.isTrue(expression, message, args);
+    }
+
+    public static void checkArgumentNotNull(Object object,
+                                            @Nullable String message,
+                                            @Nullable Object... args) {
+        Assert.isTrue(object != null, message, args);
+    }
+
+    public static void checkState(boolean expression,
+                                  @Nullable String message,
+                                  @Nullable Object... args) {
+        Assert.state(expression, message, args);
+    }
+
+    public static void check(boolean expression, String message, Object... args) {
+        if (!expression) {
+            throw new RuntimeException(String.format(message, args));
+        }
+    }
+}

+ 186 - 0
km-common/km-common-db/src/main/java/com/km/common/db/Utils/GenerateSqlUtils.java

@@ -0,0 +1,186 @@
+package com.km.common.db.Utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.IdUtil;
+import com.km.common.db.constant.DbConstants;
+import com.km.common.db.enums.DbProductTableEnum;
+import com.km.common.db.enums.DbProductTypeEnum;
+import com.km.common.db.provider.IMetadataProvider;
+import com.km.common.db.schema.ColumnDescription;
+import com.km.common.db.schema.ColumnMetaData;
+import com.km.common.db.schema.TableDescription;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @Description: 拼接SQL工具类
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+public class GenerateSqlUtils {
+
+    private static final boolean HIVE_USE_CTAS = false;
+
+    public static String getDDLCreateTableSQL(
+        IMetadataProvider provider,
+        List<ColumnDescription> fieldNames,
+        List<String> primaryKeys,
+        String schemaName,
+        String tableName,
+        boolean autoIncr) {
+        return getDDLCreateTableSQL(
+            provider,
+            fieldNames,
+            primaryKeys,
+            schemaName,
+            tableName,
+            false,
+            null,
+            autoIncr,
+            Collections.emptyMap());
+    }
+
+    public static String getDDLCreateTableSQL(
+        IMetadataProvider provider,
+        List<ColumnDescription> fieldNames,
+        List<String> primaryKeys,
+        String schemaName,
+        String tableName,
+        boolean withRemarks,
+        String tableRemarks,
+        boolean autoIncr,
+        Map<String, String> tblProperties) {
+        DbProductTypeEnum type = provider.getProductType();
+        StringBuilder sb = new StringBuilder();
+        List<String> pks = fieldNames.stream()
+            .filter((cd) -> primaryKeys.contains(cd.getFieldName()))
+            .map((cd) -> cd.getFieldName())
+            .collect(Collectors.toList());
+
+        sb.append(DbConstants.CREATE_TABLE);
+        // if(ifNotExist && !type.isLikeOracle()) {
+        // sb.append( Const.IF_NOT_EXISTS );
+        // }
+        sb.append(provider.getQuotedSchemaTableCombination(schemaName, tableName));
+        sb.append("(");
+
+        for (int i = 0; i < fieldNames.size(); i++) {
+            if (i > 0) {
+                sb.append(", ");
+            } else {
+                sb.append("  ");
+            }
+
+            ColumnMetaData v = fieldNames.get(i).getMetaData();
+            sb.append(provider.getFieldDefinition(v, pks, autoIncr, false, withRemarks));
+        }
+
+        if (!pks.isEmpty() && !type.isLikeHive()) {
+            String pk = provider.getPrimaryKeyAsString(pks);
+            sb.append(", PRIMARY KEY (").append(pk).append(")");
+        }
+
+        sb.append(")");
+        if (type.isLikeGbase8a()) {
+            sb.append("ENGINE=EXPRESS DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin");
+            if (withRemarks && StringUtils.isNotBlank(tableRemarks)) {
+                sb.append(String.format(" COMMENT='%s' ", tableRemarks.replace("'", "\\'")));
+            }
+        } else if (type.isLikeMysql()) {
+            sb.append("ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin");
+            if (withRemarks && StringUtils.isNotBlank(tableRemarks)) {
+                sb.append(String.format(" COMMENT='%s' ", tableRemarks.replace("'", "\\'")));
+            }
+        } else if (type.isLikeHive()) {
+            if (null != tblProperties && !tblProperties.isEmpty()) {
+                List<String> kvProperties = new ArrayList<>();
+                tblProperties.forEach((k, v) -> kvProperties.add(String.format("\t\t'%s' = '%s'", k, v)));
+                sb.append(DbConstants.CR);
+                sb.append("STORED BY 'org.apache.hive.storage.jdbc.JdbcStorageHandler'");
+                sb.append(DbConstants.CR);
+                sb.append("TBLPROPERTIES (");
+                sb.append(kvProperties.stream().collect(Collectors.joining(",\n")));
+                sb.append(")");
+            } else {
+                sb.append(DbConstants.CR);
+                sb.append("STORED AS ORC");
+            }
+        } else if (type.isClickHouse()) {
+            sb.append("ENGINE=MergeTree");
+            if (CollectionUtils.isEmpty(pks)) {
+                sb.append(DbConstants.CR);
+                sb.append("ORDER BY tuple()");
+            }
+            if (withRemarks && StringUtils.isNotBlank(tableRemarks)) {
+                //sb.append(Constants.CR);
+                //sb.append(String.format("COMMENT='%s' ", tableRemarks.replace("'", "\\'")));
+            }
+        }
+
+        return DDLFormatterUtils.format(sb.toString());
+    }
+
+    public static List<String> getDDLCreateTableSQL(
+        IMetadataProvider provider,
+        List<ColumnDescription> fieldNames,
+        List<String> primaryKeys,
+        String schemaName,
+        String tableName,
+        String tableRemarks,
+        boolean autoIncr,
+        Map<String, String> tblProperties) {
+        DbProductTypeEnum productType = provider.getProductType();
+        if (productType.isLikeHive()) {
+            List<String> sqlLists = new ArrayList<>();
+            String tmpTableName = "tmp_" + IdUtil.simpleUUID();
+            String createTableSql = getDDLCreateTableSQL(provider, fieldNames, primaryKeys, schemaName,
+                tmpTableName, true, tableRemarks, autoIncr, tblProperties);
+            sqlLists.add(createTableSql);
+            if (HIVE_USE_CTAS) {
+                String createAsTableSql = String.format("CREATE TABLE `%s`.`%s` STORED AS ORC AS (SELECT * FROM `%s`.`%s`)",
+                    schemaName, tableName, schemaName, tmpTableName);
+                sqlLists.add(createAsTableSql);
+            } else {
+                String createAsTableSql = getDDLCreateTableSQL(provider, fieldNames, primaryKeys, schemaName,
+                    tableName, true, tableRemarks, autoIncr, null);
+                sqlLists.add(createAsTableSql);
+                String selectColumns = fieldNames.stream()
+                    .map(s -> String.format("`%s`", s.getFieldName()))
+                    .collect(Collectors.joining(","));
+                String insertIntoSql = String.format("INSERT INTO `%s`.`%s` SELECT %s FROM `%s`.`%s`",
+                    schemaName, tableName, selectColumns, schemaName, tmpTableName);
+                sqlLists.add(insertIntoSql);
+            }
+            String dropTmpTableSql = String.format("DROP TABLE IF EXISTS `%s`.`%s`", schemaName, tmpTableName);
+            sqlLists.add(dropTmpTableSql);
+            return sqlLists;
+        } else if (productType.noCommentStatement()) {
+            String createTableSql = getDDLCreateTableSQL(provider, fieldNames, primaryKeys, schemaName,
+                tableName, true, tableRemarks, autoIncr, tblProperties);
+            return Arrays.asList(createTableSql);
+        } else {
+            String createTableSql = getDDLCreateTableSQL(provider, fieldNames, primaryKeys, schemaName,
+                tableName, true, tableRemarks, autoIncr, tblProperties);
+            TableDescription td = new TableDescription();
+            td.setSchemaName(schemaName);
+            td.setTableName(tableName);
+            td.setRemarks(tableRemarks);
+            td.setTableType(DbProductTableEnum.TABLE.name());
+            List<String> results = provider.getTableColumnCommentDefinition(td, fieldNames);
+            if (CollectionUtils.isEmpty(results)) {
+//                results = Lists.newArrayList(createTableSql);
+                results = CollUtil.toList(createTableSql);
+            } else {
+                results.addFirst(createTableSql);
+            }
+            return results;
+        }
+    }
+}

+ 1029 - 0
km-common/km-common-db/src/main/java/com/km/common/db/Utils/ObjectCastUtils.java

@@ -0,0 +1,1029 @@
+package com.km.common.db.Utils;
+
+import cn.hutool.core.codec.Base64;
+import cn.hutool.json.JSONUtil;
+import lombok.experimental.UtilityClass;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectOutputStream;
+import java.lang.reflect.Method;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.sql.Types;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.util.Collection;
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * @Description: ObjectCastUtils
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+@Slf4j
+@UtilityClass
+public class ObjectCastUtils {
+
+    /**
+     * 将任意类型转换为java.lang.Byte类型
+     *
+     * @param in 任意类型的对象实例
+     * @return java.lang.Byte类型
+     */
+    public static Byte castToByte(final Object in) {
+        if (in instanceof Number) {
+            return ((Number) in).byteValue();
+        } else if (in instanceof java.util.Date) {
+            return Long.valueOf(((java.util.Date) in).getTime()).byteValue();
+        } else if (in instanceof String) {
+            try {
+                return Byte.parseByte(in.toString());
+            } catch (NumberFormatException e) {
+                throw new RuntimeException(
+                    String.format("无法将java.lang.String类型转换为java.lang.Byte类型:%s", e.getMessage()));
+            }
+        } else if (in instanceof Character) {
+            try {
+                return Byte.parseByte(in.toString());
+            } catch (NumberFormatException e) {
+                throw new RuntimeException(
+                    String.format("无法将java.lang.Character类型转换为java.lang.Byte类型:%s", e.getMessage()));
+            }
+        } else if (in instanceof java.sql.Clob) {
+            try {
+                return Byte.parseByte(clob2Str((java.sql.Clob) in));
+            } catch (NumberFormatException e) {
+                throw new RuntimeException(
+                    String.format("无法将java.sql.Clob类型转换为java.lang.Byte类型:%s", e.getMessage()));
+            }
+        } else if (in instanceof Boolean) {
+            return (Boolean) in ? (byte) 1 : (byte) 0;
+        }
+
+        return null;
+    }
+
+
+    public static byte[] castToByteArray(final Object in) {
+        if (in instanceof byte[]) {
+            return (byte[]) in;
+        } else if (in instanceof java.util.Date) {
+            return in.toString().getBytes();
+        } else if (in instanceof java.sql.Blob) {
+            return blob2Bytes((java.sql.Blob) in);
+        } else if (in instanceof java.lang.String || in instanceof java.lang.Character) {
+            return in.toString().getBytes();
+        } else if (in instanceof java.sql.Clob) {
+            return clob2Str((java.sql.Clob) in).getBytes();
+        } else {
+            try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
+                 ObjectOutputStream oos = new ObjectOutputStream(bos)) {
+                oos.writeObject(in);
+                oos.flush();
+                return bos.toByteArray();
+            } catch (Exception e) {
+                log.error("Field value convert from {} to byte[] failed:", in.getClass().getName(), e);
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    /**
+     * 将任意类型转换为java.lang.Short类型
+     *
+     * @param in 任意类型的对象实例
+     * @return java.lang.Short类型
+     */
+    public static Short castToShort(final Object in) {
+        if (in instanceof Number) {
+            return ((Number) in).shortValue();
+        } else if (in instanceof Byte) {
+            return (short) (((byte) in) & 0xff);
+        } else if (in instanceof java.util.Date) {
+            return (short) ((java.util.Date) in).getTime();
+        } else if (in instanceof java.util.Calendar) {
+            return (short) ((java.util.Calendar) in).getTime().getTime();
+        } else if (in instanceof LocalDateTime) {
+            return (short) java.sql.Timestamp.valueOf((LocalDateTime) in).getTime();
+        } else if (in instanceof java.time.OffsetDateTime) {
+            return (short) java.sql.Timestamp.valueOf(((java.time.OffsetDateTime) in).toLocalDateTime())
+                .getTime();
+        } else if (in instanceof String || in instanceof Character) {
+            try {
+                String s = in.toString().trim();
+                if (s.equalsIgnoreCase("true")) {
+                    return Short.valueOf((short) 1);
+                } else if (s.equalsIgnoreCase("false")) {
+                    return Short.valueOf((short) 0);
+                } else {
+                    return Short.parseShort(s);
+                }
+            } catch (NumberFormatException e) {
+                throw new RuntimeException(
+                    String.format("无法将java.lang.String类型转换为java.lang.Short类型:%s", e.getMessage()));
+            }
+        } else if (in instanceof java.sql.Clob) {
+            try {
+                String s = clob2Str((java.sql.Clob) in).trim();
+                if (s.equalsIgnoreCase("true")) {
+                    return Short.valueOf((short) 1);
+                } else if (s.equalsIgnoreCase("false")) {
+                    return Short.valueOf((short) 0);
+                } else {
+                    return Short.parseShort(s);
+                }
+            } catch (NumberFormatException e) {
+                throw new RuntimeException(
+                    String.format("无法将java.sql.Clob类型转换为java.lang.Short类型:%s", e.getMessage()));
+            }
+        } else if (in instanceof Boolean) {
+            return (Boolean) in ? (short) 1 : (short) 0;
+        }
+
+        return null;
+    }
+
+    /**
+     * 将任意类型转换为java.lang.Integer类型
+     *
+     * @param in 任意类型的对象实例
+     * @return java.lang.Integer类型
+     */
+    public static Integer castToInteger(final Object in) {
+        if (in instanceof Number) {
+            return ((Number) in).intValue();
+        } else if (in instanceof Byte) {
+            return (((byte) in) & 0xff);
+        } else if (in instanceof java.util.Date) {
+            return (int) ((java.util.Date) in).getTime();
+        } else if (in instanceof java.util.Calendar) {
+            return (int) ((java.util.Calendar) in).getTime().getTime();
+        } else if (in instanceof LocalDateTime) {
+            return (int) java.sql.Timestamp.valueOf((LocalDateTime) in).getTime();
+        } else if (in instanceof java.time.OffsetDateTime) {
+            return (int) java.sql.Timestamp.valueOf(((java.time.OffsetDateTime) in).toLocalDateTime())
+                .getTime();
+        } else if (in instanceof String || in instanceof Character) {
+            try {
+                String s = in.toString().trim();
+                if (s.equalsIgnoreCase("true")) {
+                    return Integer.valueOf(1);
+                } else if (s.equalsIgnoreCase("false")) {
+                    return Integer.valueOf(0);
+                } else {
+                    return Integer.parseInt(s);
+                }
+            } catch (NumberFormatException e) {
+                throw new RuntimeException(
+                    String.format("无法将java.lang.String类型转换为java.lang.Integer类型:%s", e.getMessage()));
+            }
+        } else if (in instanceof java.sql.Clob) {
+            try {
+                String s = clob2Str((java.sql.Clob) in).trim();
+                if (s.equalsIgnoreCase("true")) {
+                    return Integer.valueOf(1);
+                } else if (s.equalsIgnoreCase("false")) {
+                    return Integer.valueOf(0);
+                } else {
+                    return Integer.parseInt(s);
+                }
+            } catch (NumberFormatException e) {
+                throw new RuntimeException(
+                    String.format("无法将java.sql.Clob类型转换为java.lang.Integer类型:%s", e.getMessage()));
+            }
+        } else if (in instanceof Boolean) {
+            return (Boolean) in ? (int) 1 : (int) 0;
+        }
+
+        return null;
+    }
+
+    /**
+     * 将任意类型转换为java.lang.Long类型
+     *
+     * @param in 任意类型的对象实例
+     * @return java.lang.Long类型
+     */
+    public static Long castToLong(final Object in) {
+        if (in instanceof Number) {
+            return ((Number) in).longValue();
+        } else if (in instanceof Byte) {
+            return (long) (((byte) in) & 0xff);
+        } else if (in instanceof java.util.Date) {
+            return ((java.util.Date) in).getTime();
+        } else if (in instanceof java.util.Calendar) {
+            return ((java.util.Calendar) in).getTime().getTime();
+        } else if (in instanceof LocalDateTime) {
+            return java.sql.Timestamp.valueOf((LocalDateTime) in).getTime();
+        } else if (in instanceof java.time.OffsetDateTime) {
+            return java.sql.Timestamp.valueOf(((java.time.OffsetDateTime) in).toLocalDateTime())
+                .getTime();
+        } else if (in instanceof String || in instanceof Character) {
+            try {
+                String s = in.toString().trim();
+                if (s.equalsIgnoreCase("true")) {
+                    return Long.valueOf(1);
+                } else if (s.equalsIgnoreCase("false")) {
+                    return Long.valueOf(0);
+                } else {
+                    return Long.parseLong(s);
+                }
+            } catch (NumberFormatException e) {
+                throw new RuntimeException(
+                    String.format("无法将java.lang.String类型转换为java.lang.Long类型:%s", e.getMessage()));
+            }
+        } else if (in instanceof java.sql.Clob) {
+            try {
+                String s = clob2Str((java.sql.Clob) in).trim();
+                if (s.equalsIgnoreCase("true")) {
+                    return Long.valueOf(1);
+                } else if (s.equalsIgnoreCase("false")) {
+                    return Long.valueOf(0);
+                } else {
+                    return Long.parseLong(s);
+                }
+            } catch (NumberFormatException e) {
+                throw new RuntimeException(
+                    String.format("无法将java.sql.Clob类型转换为java.lang.Long类型:%s", e.getMessage()));
+            }
+        } else if (in instanceof Boolean) {
+            return (Boolean) in ? (long) 1 : (long) 0;
+        }
+
+        return null;
+    }
+
+    /**
+     * 将任意类型转换为java.lang.Number类型
+     *
+     * @param in 任意类型的对象实例
+     * @return java.lang.Number类型
+     */
+    public static Number castToNumeric(final Object in) {
+        if (in instanceof Number) {
+            return (Number) in;
+        } else if (in instanceof java.util.Date) {
+            return ((java.util.Date) in).getTime();
+        } else if (in instanceof java.util.Calendar) {
+            return ((java.util.Calendar) in).getTime().getTime();
+        } else if (in instanceof LocalDateTime) {
+            return java.sql.Timestamp.valueOf((LocalDateTime) in).getTime();
+        } else if (in instanceof java.time.OffsetDateTime) {
+            return java.sql.Timestamp.valueOf(((java.time.OffsetDateTime) in).toLocalDateTime())
+                .getTime();
+        } else if (in instanceof String || in instanceof Character) {
+            try {
+                String s = in.toString().trim();
+                if (s.equalsIgnoreCase("true")) {
+                    return Integer.valueOf(1);
+                } else if (s.equalsIgnoreCase("false")) {
+                    return Integer.valueOf(0);
+                } else {
+                    return new BigDecimal(s);
+                }
+            } catch (NumberFormatException e) {
+                throw new RuntimeException(
+                    String.format("无法将java.lang.String类型转换为java.lang.Number类型:%s", e.getMessage()));
+            }
+        } else if (in instanceof java.sql.Clob) {
+            try {
+                String s = clob2Str((java.sql.Clob) in).trim();
+                if (s.equalsIgnoreCase("true")) {
+                    return Integer.valueOf(1);
+                } else if (s.equalsIgnoreCase("false")) {
+                    return Integer.valueOf(0);
+                } else {
+                    return new BigDecimal(s);
+                }
+            } catch (NumberFormatException e) {
+                throw new RuntimeException(
+                    String.format("无法将java.sql.Clob类型转换为java.lang.Number类型:%s", e.getMessage()));
+            }
+        } else if (in instanceof Boolean) {
+            return (Boolean) in ? (long) 1 : (long) 0;
+        }
+
+        return null;
+    }
+
+    /**
+     * 将任意类型转换为java.lang.Float类型
+     *
+     * @param in 任意类型的对象实例
+     * @return java.lang.Float类型
+     */
+    public static Float castToFloat(final Object in) {
+        if (in instanceof Number) {
+            return ((Number) in).floatValue();
+        } else if (in instanceof java.util.Date) {
+            return (float) ((java.util.Date) in).getTime();
+        } else if (in instanceof java.util.Calendar) {
+            return (float) ((java.util.Calendar) in).getTime().getTime();
+        } else if (in instanceof LocalDateTime) {
+            return (float) java.sql.Timestamp.valueOf((LocalDateTime) in).getTime();
+        } else if (in instanceof java.time.OffsetDateTime) {
+            return (float) java.sql.Timestamp.valueOf(((java.time.OffsetDateTime) in).toLocalDateTime())
+                .getTime();
+        } else if (in instanceof String || in instanceof Character) {
+            try {
+                String s = in.toString().trim();
+                if (s.equalsIgnoreCase("true")) {
+                    return Float.valueOf(1);
+                } else if (s.equalsIgnoreCase("false")) {
+                    return Float.valueOf(0);
+                } else {
+                    return Float.parseFloat(s);
+                }
+            } catch (NumberFormatException e) {
+                throw new RuntimeException(
+                    String.format("无法将java.lang.String类型转换为java.lang.Float类型:%s", e.getMessage()));
+            }
+        } else if (in instanceof java.sql.Clob) {
+            try {
+                String s = clob2Str((java.sql.Clob) in).trim();
+                if (s.equalsIgnoreCase("true")) {
+                    return Float.valueOf(1);
+                } else if (s.equalsIgnoreCase("false")) {
+                    return Float.valueOf(0);
+                } else {
+                    return Float.parseFloat(s);
+                }
+            } catch (NumberFormatException e) {
+                throw new RuntimeException(
+                    String.format("无法将java.sql.Clob类型转换为java.lang.Float类型:%s", e.getMessage()));
+            }
+        } else if (in instanceof Boolean) {
+            return (Boolean) in ? 1f : 0f;
+        }
+
+        return null;
+    }
+
+    /**
+     * 将任意类型转换为java.lang.Double类型
+     *
+     * @param in 任意类型的对象实例
+     * @return java.lang.Double类型
+     */
+    public static Double castToDouble(final Object in) {
+        if (in instanceof Number) {
+            return ((Number) in).doubleValue();
+        } else if (in instanceof java.util.Date) {
+            return (double) ((java.util.Date) in).getTime();
+        } else if (in instanceof java.util.Calendar) {
+            return (double) ((java.util.Calendar) in).getTime().getTime();
+        } else if (in instanceof LocalDateTime) {
+            return (double) java.sql.Timestamp.valueOf((LocalDateTime) in).getTime();
+        } else if (in instanceof java.time.OffsetDateTime) {
+            return (double) java.sql.Timestamp.valueOf(((java.time.OffsetDateTime) in).toLocalDateTime())
+                .getTime();
+        } else if (in instanceof String || in instanceof Character) {
+            try {
+                String s = in.toString().trim();
+                if (s.equalsIgnoreCase("true")) {
+                    return Double.valueOf(1);
+                } else if (s.equalsIgnoreCase("false")) {
+                    return Double.valueOf(0);
+                } else {
+                    return Double.parseDouble(s);
+                }
+            } catch (NumberFormatException e) {
+                throw new RuntimeException(
+                    String.format("无法将将java.lang.String类型转换为java.lang.Double类型:%s", e.getMessage()));
+            }
+        } else if (in instanceof java.sql.Clob) {
+            try {
+                String s = clob2Str((java.sql.Clob) in).trim();
+                if (s.equalsIgnoreCase("true")) {
+                    return Double.valueOf(1);
+                } else if (s.equalsIgnoreCase("false")) {
+                    return Double.valueOf(0);
+                } else {
+                    return Double.parseDouble(s);
+                }
+            } catch (NumberFormatException e) {
+                throw new RuntimeException(
+                    String.format("无法将java.sql.Clob类型转换为java.lang.Double类型:%s", e.getMessage()));
+            }
+        } else if (in instanceof Boolean) {
+            return (Boolean) in ? 1d : 0d;
+        }
+
+        return null;
+    }
+
+    /**
+     * 将任意类型转换为java.time.LocalDate类型
+     *
+     * @param in 任意类型的对象实例
+     * @return java.time.LocalDate类型
+     */
+    public static LocalDate castToLocalDate(final Object in) {
+        if (in instanceof java.sql.Time) {
+            java.sql.Time date = (java.sql.Time) in;
+            LocalDate localDate = Instant.ofEpochMilli(date.getTime()).atZone(ZoneId.systemDefault())
+                .toLocalDate();
+            return localDate;
+        } else if (in instanceof java.sql.Timestamp) {
+            java.sql.Timestamp t = (java.sql.Timestamp) in;
+            LocalDateTime localDateTime = LocalDateTime.ofInstant(t.toInstant(), ZoneId.systemDefault());
+            return localDateTime.toLocalDate();
+        } else if (in instanceof java.util.Date) {
+            java.util.Date date = (java.util.Date) in;
+            LocalDate localDate = Instant.ofEpochMilli(date.getTime()).atZone(ZoneId.systemDefault())
+                .toLocalDate();
+            return localDate;
+        } else if (in instanceof java.util.Calendar) {
+            java.sql.Date date = new java.sql.Date(((java.util.Calendar) in).getTime().getTime());
+            LocalDate localDate = Instant.ofEpochMilli(date.getTime()).atZone(ZoneId.systemDefault())
+                .toLocalDate();
+            return localDate;
+        } else if (in instanceof LocalDate) {
+            return (LocalDate) in;
+        } else if (in instanceof LocalTime) {
+            return LocalDate.MIN;
+        } else if (in instanceof LocalDateTime) {
+            return ((LocalDateTime) in).toLocalDate();
+        } else if (in instanceof java.time.OffsetDateTime) {
+            return ((java.time.OffsetDateTime) in).toLocalDate();
+        } else if (in.getClass().getName().equals("oracle.sql.TIMESTAMP")) {
+            Class<?> clz = in.getClass();
+            try {
+                Method m = clz.getMethod("timestampValue");
+                java.sql.Timestamp date = (java.sql.Timestamp) m.invoke(in);
+                LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+                return localDate;
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        } else if (in.getClass().getName().equals("microsoft.sql.DateTimeOffset")) {
+            Class<?> clz = in.getClass();
+            try {
+                Method m = clz.getMethod("getTimestamp");
+                java.sql.Timestamp t = (java.sql.Timestamp) m.invoke(in);
+                LocalDateTime localDateTime = LocalDateTime
+                    .ofInstant(t.toInstant(), ZoneId.systemDefault());
+                return localDateTime.toLocalDate();
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        } else if (in instanceof String || in instanceof Character) {
+            try {
+                java.sql.Time date = java.sql.Time.valueOf(in.toString());
+                LocalDate localDate = Instant.ofEpochMilli(date.getTime()).atZone(ZoneId.systemDefault())
+                    .toLocalDate();
+                return localDate;
+            } catch (IllegalArgumentException e) {
+                throw new RuntimeException(
+                    String.format("无法将java.lang.String类型转换为java.sql.Time类型:%s", e.getMessage()));
+            }
+        } else if (in instanceof java.sql.Clob) {
+            try {
+                java.sql.Time date = java.sql.Time.valueOf(clob2Str((java.sql.Clob) in));
+                LocalDate localDate = Instant.ofEpochMilli(date.getTime()).atZone(ZoneId.systemDefault())
+                    .toLocalDate();
+                return localDate;
+            } catch (NumberFormatException e) {
+                throw new RuntimeException(
+                    String.format("无法将java.sql.Clob类型转换为java.sql.Time类型:%s", e.getMessage()));
+            }
+        } else if (in instanceof Number) {
+            java.sql.Timestamp t = new java.sql.Timestamp(((Number) in).longValue());
+            LocalDateTime localDateTime = LocalDateTime.ofInstant(t.toInstant(), ZoneId.systemDefault());
+            return localDateTime.toLocalDate();
+        }
+
+        return null;
+    }
+
+    /**
+     * 将任意类型转换为java.time.LocalTime类型
+     *
+     * @param in 任意类型的对象实例
+     * @return java.time.LocalDate类型
+     */
+    public static LocalTime castToLocalTime(final Object in) {
+        if (in instanceof java.sql.Time) {
+            java.sql.Time date = (java.sql.Time) in;
+            LocalTime localTime = Instant.ofEpochMilli(date.getTime()).atZone(ZoneId.systemDefault())
+                .toLocalTime();
+            return localTime;
+        } else if (in instanceof java.sql.Timestamp) {
+            java.sql.Timestamp t = (java.sql.Timestamp) in;
+            LocalDateTime localDateTime = LocalDateTime.ofInstant(t.toInstant(), ZoneId.systemDefault());
+            return localDateTime.toLocalTime();
+        } else if (in instanceof java.util.Date) {
+            return LocalTime.of(0, 0, 0);
+        } else if (in instanceof java.util.Calendar) {
+            java.sql.Date date = new java.sql.Date(((java.util.Calendar) in).getTime().getTime());
+            LocalDateTime localDateTime = Instant.ofEpochMilli(date.getTime())
+                .atZone(ZoneId.systemDefault())
+                .toLocalDateTime();
+            return localDateTime.toLocalTime();
+        } else if (in instanceof LocalDate) {
+            return LocalTime.of(0, 0, 0);
+        } else if (in instanceof LocalTime) {
+            return (LocalTime) in;
+        } else if (in instanceof LocalDateTime) {
+            return ((LocalDateTime) in).toLocalTime();
+        } else if (in instanceof java.time.OffsetDateTime) {
+            return ((java.time.OffsetDateTime) in).toLocalTime();
+        } else if (in.getClass().getName().equals("oracle.sql.TIMESTAMP")) {
+            Class<?> clz = in.getClass();
+            try {
+                Method m = clz.getMethod("timestampValue");
+                java.sql.Timestamp date = (java.sql.Timestamp) m.invoke(in);
+                LocalDateTime localDateTime = date.toInstant().atZone(ZoneId.systemDefault())
+                    .toLocalDateTime();
+                return localDateTime.toLocalTime();
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        } else if (in.getClass().getName().equals("microsoft.sql.DateTimeOffset")) {
+            Class<?> clz = in.getClass();
+            try {
+                Method m = clz.getMethod("getTimestamp");
+                java.sql.Timestamp t = (java.sql.Timestamp) m.invoke(in);
+                LocalDateTime localDateTime = LocalDateTime
+                    .ofInstant(t.toInstant(), ZoneId.systemDefault());
+                return localDateTime.toLocalTime();
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        } else if (in instanceof String || in instanceof Character) {
+            try {
+                java.sql.Time date = java.sql.Time.valueOf(in.toString());
+                return LocalTime.ofSecondOfDay(date.getTime());
+            } catch (IllegalArgumentException e) {
+                throw new RuntimeException(
+                    String.format("无法将java.lang.String类型转换为java.sql.Time类型:%s", e.getMessage()));
+            }
+        } else if (in instanceof java.sql.Clob) {
+            try {
+                java.sql.Time date = java.sql.Time.valueOf(clob2Str((java.sql.Clob) in));
+                return LocalTime.ofSecondOfDay(date.getTime());
+            } catch (NumberFormatException e) {
+                throw new RuntimeException(
+                    String.format("无法将java.sql.Clob类型转换为java.sql.Time类型:%s", e.getMessage()));
+            }
+        } else if (in instanceof Number) {
+            java.sql.Timestamp t = new java.sql.Timestamp(((Number) in).longValue());
+            LocalDateTime localDateTime = LocalDateTime.ofInstant(t.toInstant(), ZoneId.systemDefault());
+            return localDateTime.toLocalTime();
+        }
+
+        return null;
+    }
+
+    /**
+     * 将任意类型转换为java.time.LocalDateTime类型
+     *
+     * @param in 任意类型的对象实例
+     * @return java.time.LocalDateTime类型
+     */
+    public static LocalDateTime castToLocalDateTime(final Object in) {
+        if (in instanceof java.sql.Timestamp) {
+            java.sql.Timestamp t = (java.sql.Timestamp) in;
+            LocalDateTime localDateTime = LocalDateTime.ofInstant(t.toInstant(), ZoneId.systemDefault());
+            return localDateTime;
+        } else if (in instanceof java.sql.Date) {
+            java.sql.Date date = (java.sql.Date) in;
+            LocalDate localDate = date.toLocalDate();
+            LocalTime localTime = LocalTime.of(0, 0, 0);
+            LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime);
+            return localDateTime;
+        } else if (in instanceof java.sql.Time) {
+            java.sql.Time date = (java.sql.Time) in;
+            java.sql.Timestamp t = new java.sql.Timestamp(date.getTime());
+            LocalDateTime localDateTime = LocalDateTime.ofInstant(t.toInstant(), ZoneId.systemDefault());
+            return localDateTime;
+        } else if (in instanceof java.util.Date) {
+            java.sql.Timestamp t = new java.sql.Timestamp(((java.util.Date) in).getTime());
+            LocalDateTime localDateTime = LocalDateTime.ofInstant(t.toInstant(), ZoneId.systemDefault());
+            return localDateTime;
+        } else if (in instanceof java.util.Calendar) {
+            java.sql.Timestamp t = new java.sql.Timestamp(((java.util.Calendar) in).getTime().getTime());
+            LocalDateTime localDateTime = LocalDateTime.ofInstant(t.toInstant(), ZoneId.systemDefault());
+            return localDateTime;
+        } else if (in instanceof LocalDate) {
+            LocalDate localDate = (LocalDate) in;
+            LocalTime localTime = LocalTime.of(0, 0, 0);
+            LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime);
+            return localDateTime;
+        } else if (in instanceof LocalTime) {
+            LocalDate localDate = LocalDate.MIN;
+            LocalTime localTime = (LocalTime) in;
+            LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime);
+            return localDateTime;
+        } else if (in instanceof LocalDateTime) {
+            return (LocalDateTime) in;
+        } else if (in instanceof java.time.OffsetDateTime) {
+            return ((java.time.OffsetDateTime) in).toLocalDateTime();
+        } else if (in.getClass().getName().equals("oracle.sql.TIMESTAMP")) {
+            Class<?> clz = in.getClass();
+            try {
+                Method m = clz.getMethod("timestampValue");
+                java.sql.Timestamp t = (java.sql.Timestamp) m.invoke(in);
+                LocalDateTime localDateTime = LocalDateTime
+                    .ofInstant(t.toInstant(), ZoneId.systemDefault());
+                return localDateTime;
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        } else if (in.getClass().getName().equals("microsoft.sql.DateTimeOffset")) {
+            Class<?> clz = in.getClass();
+            try {
+                Method m = clz.getMethod("getTimestamp");
+                java.sql.Timestamp t = (java.sql.Timestamp) m.invoke(in);
+                LocalDateTime localDateTime = LocalDateTime
+                    .ofInstant(t.toInstant(), ZoneId.systemDefault());
+                return localDateTime;
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        } else if (in instanceof String || in instanceof Character) {
+            try {
+                java.sql.Timestamp t = java.sql.Timestamp.valueOf(in.toString());
+                LocalDateTime localDateTime = LocalDateTime
+                    .ofInstant(t.toInstant(), ZoneId.systemDefault());
+                return localDateTime;
+            } catch (IllegalArgumentException e) {
+                throw new RuntimeException(
+                    String.format("无法将java.lang.String类型转换为java.sql.TimeStamp类型:%s", e.getMessage()));
+            }
+        } else if (in instanceof java.sql.Clob) {
+            try {
+                java.sql.Timestamp t = java.sql.Timestamp.valueOf(clob2Str((java.sql.Clob) in));
+                LocalDateTime localDateTime = LocalDateTime
+                    .ofInstant(t.toInstant(), ZoneId.systemDefault());
+                return localDateTime;
+            } catch (NumberFormatException e) {
+                throw new RuntimeException(
+                    String.format("无法将java.sql.Clob类型转换为java.sql.TimeStamp类型:%s", e.getMessage()));
+            }
+        } else if (in instanceof Number) {
+            java.sql.Timestamp t = new java.sql.Timestamp(((Number) in).longValue());
+            LocalDateTime localDateTime = LocalDateTime.ofInstant(t.toInstant(), ZoneId.systemDefault());
+            return localDateTime;
+        }
+
+        return null;
+    }
+
+    /**
+     * 将任意类型转换为java.time.LocalDateTime类型
+     *
+     * @param in 任意类型的对象实例
+     * @return java.sql.Timestamp类型
+     */
+    public static Timestamp castToTimestamp(final Object in) {
+        if (in instanceof java.sql.Timestamp) {
+            return (java.sql.Timestamp) in;
+        } else if (in instanceof java.sql.Date) {
+            java.sql.Date date = (java.sql.Date) in;
+            LocalDate localDate = date.toLocalDate();
+            LocalTime localTime = LocalTime.of(0, 0, 0);
+            LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime);
+            return Timestamp.valueOf(localDateTime);
+        } else if (in instanceof java.sql.Time) {
+            java.sql.Time date = (java.sql.Time) in;
+            return new java.sql.Timestamp(date.getTime());
+        } else if (in instanceof java.util.Date) {
+            return new java.sql.Timestamp(((java.util.Date) in).getTime());
+        } else if (in instanceof java.util.Calendar) {
+            return new java.sql.Timestamp(((java.util.Calendar) in).getTime().getTime());
+        } else if (in instanceof LocalDate) {
+            LocalDate localDate = (LocalDate) in;
+            LocalTime localTime = LocalTime.of(0, 0, 0);
+            LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime);
+            return Timestamp.valueOf(localDateTime);
+        } else if (in instanceof LocalTime) {
+            LocalDate localDate = LocalDate.MIN;
+            LocalTime localTime = (LocalTime) in;
+            LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime);
+            return Timestamp.valueOf(localDateTime);
+        } else if (in instanceof LocalDateTime) {
+            return Timestamp.valueOf((LocalDateTime) in);
+        } else if (in instanceof java.time.OffsetDateTime) {
+            return Timestamp.valueOf(((java.time.OffsetDateTime) in).toLocalDateTime());
+        } else if (in.getClass().getName().equals("oracle.sql.TIMESTAMP")) {
+            Class<?> clz = in.getClass();
+            try {
+                Method m = clz.getMethod("timestampValue");
+                java.sql.Timestamp t = (java.sql.Timestamp) m.invoke(in);
+                LocalDateTime localDateTime = LocalDateTime
+                    .ofInstant(t.toInstant(), ZoneId.systemDefault());
+                return Timestamp.valueOf(localDateTime);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        } else if (in.getClass().getName().equals("microsoft.sql.DateTimeOffset")) {
+            Class<?> clz = in.getClass();
+            try {
+                Method m = clz.getMethod("getTimestamp");
+                java.sql.Timestamp t = (java.sql.Timestamp) m.invoke(in);
+                LocalDateTime localDateTime = LocalDateTime
+                    .ofInstant(t.toInstant(), ZoneId.systemDefault());
+                return Timestamp.valueOf(localDateTime);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        } else if (in instanceof String || in instanceof Character) {
+            try {
+                java.sql.Timestamp t = java.sql.Timestamp.valueOf(in.toString());
+                LocalDateTime localDateTime = LocalDateTime
+                    .ofInstant(t.toInstant(), ZoneId.systemDefault());
+                return Timestamp.valueOf(localDateTime);
+            } catch (IllegalArgumentException e) {
+                throw new RuntimeException(
+                    String.format("无法将java.lang.String类型转换为java.sql.TimeStamp类型:%s", e.getMessage()));
+            }
+        } else if (in instanceof java.sql.Clob) {
+            try {
+                java.sql.Timestamp t = java.sql.Timestamp.valueOf(clob2Str((java.sql.Clob) in));
+                LocalDateTime localDateTime = LocalDateTime
+                    .ofInstant(t.toInstant(), ZoneId.systemDefault());
+                return Timestamp.valueOf(localDateTime);
+            } catch (NumberFormatException e) {
+                throw new RuntimeException(
+                    String.format("无法将java.sql.Clob类型转换为java.sql.TimeStamp类型:%s", e.getMessage()));
+            }
+        } else if (in instanceof Number) {
+            java.sql.Timestamp t = new java.sql.Timestamp(((Number) in).longValue());
+            LocalDateTime localDateTime = LocalDateTime.ofInstant(t.toInstant(), ZoneId.systemDefault());
+            return Timestamp.valueOf(localDateTime);
+        }
+
+        return null;
+    }
+
+    /**
+     * 将任意类型转换为Boolean类型
+     *
+     * @param in 任意类型的对象实例
+     * @return Boolean类型
+     */
+    public static Boolean castToBoolean(final Object in) {
+        if (in instanceof Boolean) {
+            return (Boolean) in;
+        } else if (in instanceof Number) {
+            return ((Number) in).intValue() != 0;
+        } else if (in instanceof String || in instanceof Character) {
+            try {
+                return Boolean.parseBoolean(in.toString());
+            } catch (IllegalArgumentException e) {
+                throw new RuntimeException(
+                    String.format("无法将java.lang.String类型转换为java.lang.Boolean类型:%s", e.getMessage()));
+            }
+        } else if (in instanceof java.sql.Clob) {
+            try {
+                return Boolean.parseBoolean(clob2Str((java.sql.Clob) in));
+            } catch (NumberFormatException e) {
+                throw new RuntimeException(
+                    String.format("无法将java.sql.Clob类型转换为java.lang.Boolean类型:%s", e.getMessage()));
+            }
+        }
+
+        return null;
+    }
+
+    public static byte[] blob2Bytes(java.sql.Blob blob) {
+        try (java.io.InputStream inputStream = blob.getBinaryStream()) {
+            try (java.io.BufferedInputStream is = new java.io.BufferedInputStream(inputStream)) {
+                byte[] bytes = new byte[(int) blob.length()];
+                int len = bytes.length;
+                int offset = 0;
+                int read = 0;
+                while (offset < len && (read = is.read(bytes, offset, len - offset)) >= 0) {
+                    offset += read;
+                }
+                return bytes;
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static String clob2Str(java.sql.Clob clob) {
+        try (java.io.Reader is = clob.getCharacterStream()) {
+            java.io.BufferedReader reader = new java.io.BufferedReader(is);
+            StringBuffer sb = new StringBuffer();
+            char[] buffer = new char[4096];
+            for (int i = reader.read(buffer); i > 0; i = reader.read(buffer)) {
+                sb.append(buffer, 0, i);
+            }
+            return sb.toString();
+        } catch (SQLException | java.io.IOException e) {
+            log.warn("Field Value convert from java.sql.Clob to java.lang.String failed:", e);
+            return null;
+        }
+    }
+
+    public static String castToString(final Object in) {
+        if (in instanceof java.lang.Character) {
+            return in.toString();
+        } else if (in instanceof java.lang.String) {
+            return in.toString();
+        } else if (in instanceof java.lang.Character) {
+            return in.toString();
+        } else if (in instanceof java.sql.Clob) {
+            return clob2Str((java.sql.Clob) in);
+        } else if (in instanceof java.sql.Blob) {
+            return Base64.encode(blob2Bytes((java.sql.Blob) in));
+        } else if (in instanceof java.lang.Number) {
+            return in.toString();
+        } else if (in instanceof java.sql.RowId) {
+            return in.toString();
+        } else if (in instanceof java.lang.Boolean) {
+            return in.toString();
+        } else if (in instanceof java.util.Date) {
+            return in.toString();
+        } else if (in instanceof java.time.LocalDate) {
+            return in.toString();
+        } else if (in instanceof java.time.LocalTime) {
+            return in.toString();
+        } else if (in instanceof java.time.LocalDateTime) {
+            return in.toString();
+        } else if (in instanceof java.time.OffsetDateTime) {
+            return in.toString();
+        } else if (in instanceof java.sql.SQLXML) {
+            return in.toString();
+        } else if (in instanceof java.sql.Array) {
+            return in.toString();
+        } else if (in instanceof java.util.UUID) {
+            return in.toString();
+        } else if ("org.postgresql.util.PGobject".equals(in.getClass().getName())) {
+            return in.toString();
+        } else if ("org.postgresql.jdbc.PgSQLXML".equals(in.getClass().getName())) {
+            try {
+                Class<?> clz = in.getClass();
+                Method getString = clz.getMethod("getString");
+                return getString.invoke(in).toString();
+            } catch (Exception e) {
+                return "";
+            }
+        } else if (in.getClass().getName().equals("oracle.sql.INTERVALDS")) {
+            return in.toString();
+        } else if (in.getClass().getName().equals("oracle.sql.INTERVALYM")) {
+            return in.toString();
+        } else if (in.getClass().getName().equals("oracle.sql.TIMESTAMPLTZ")) {
+            return in.toString();
+        } else if (in.getClass().getName().equals("oracle.sql.TIMESTAMPTZ")) {
+            return in.toString();
+        } else if (in.getClass().getName().equals("oracle.sql.BFILE")) {
+            Class<?> clz = in.getClass();
+            try {
+                Method methodFileExists = clz.getMethod("fileExists");
+                boolean exists = (boolean) methodFileExists.invoke(in);
+                if (!exists) {
+                    return "";
+                }
+
+                Method methodOpenFile = clz.getMethod("openFile");
+                methodOpenFile.invoke(in);
+
+                try {
+                    Method methodCharacterStreamValue = clz.getMethod("getBinaryStream");
+                    java.io.InputStream is = (java.io.InputStream) methodCharacterStreamValue.invoke(in);
+
+                    String line;
+                    StringBuilder sb = new StringBuilder();
+
+                    java.io.BufferedReader br = new java.io.BufferedReader(new java.io.InputStreamReader(is));
+                    while ((line = br.readLine()) != null) {
+                        sb.append(line);
+                    }
+
+                    return sb.toString();
+                } finally {
+                    Method methodCloseFile = clz.getMethod("closeFile");
+                    methodCloseFile.invoke(in);
+                }
+            } catch (java.lang.reflect.InvocationTargetException ex) {
+                log.warn("Error for handle oracle.sql.BFILE: ", ex);
+                return "";
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        } else if (in.getClass().getName().equals("microsoft.sql.DateTimeOffset")) {
+            return in.toString();
+        } else if (in instanceof byte[]) {
+            return new String((byte[]) in);
+        } else if (in instanceof Map) {
+            return JSONUtil.toJsonStr(in);
+        } else if(in instanceof Collection){
+            return JSONUtil.toJsonStr(in);
+        }
+
+        return null != in ? in.toString() : null;
+    }
+
+    public static String objectToString(final Object in) {
+        String v = in.toString();
+        String a = in.getClass().getName() + "@" + Integer.toHexString(in.hashCode());
+        if (a.length() == v.length() && StringUtils.equals(a, v)) {
+            throw new UnsupportedOperationException("Unsupported convert "
+                + in.getClass().getName() + " to java.lang.String");
+        }
+
+        return v;
+    }
+
+    public static Object castByJdbcType(int jdbcType, Object value) {
+        switch (jdbcType) {
+            case Types.BIT:
+            case Types.TINYINT:
+                return convert(value, ObjectCastUtils::castToByte);
+            case Types.SMALLINT:
+                return convert(value, ObjectCastUtils::castToShort);
+            case Types.INTEGER:
+                return convert(value, ObjectCastUtils::castToInteger);
+            case Types.BIGINT:
+                return convert(value, ObjectCastUtils::castToLong);
+            case Types.NUMERIC:
+            case Types.DECIMAL:
+                return convert(value, ObjectCastUtils::castToNumeric);
+            case Types.FLOAT:
+            case Types.REAL:
+                return convert(value, ObjectCastUtils::castToFloat);
+            case Types.DOUBLE:
+                return convert(value, ObjectCastUtils::castToDouble);
+            case Types.BOOLEAN:
+                return convert(value, ObjectCastUtils::castToBoolean);
+            case Types.TIME:
+                return convert(value, ObjectCastUtils::castToLocalTime);
+            case Types.DATE:
+                return convert(value, ObjectCastUtils::castToLocalDate);
+            case Types.TIMESTAMP:
+                return convert(value, ObjectCastUtils::castToTimestamp);
+            case Types.BINARY:
+            case Types.VARBINARY:
+            case Types.BLOB:
+            case Types.LONGVARBINARY:
+                return convert(value, ObjectCastUtils::castToByteArray);
+            case Types.CHAR:
+            case Types.NCHAR:
+            case Types.VARCHAR:
+            case Types.LONGVARCHAR:
+            case Types.NVARCHAR:
+            case Types.LONGNVARCHAR:
+            case Types.CLOB:
+            case Types.NCLOB:
+            case Types.NULL:
+            case Types.OTHER:
+            default:
+                return convert(value, ObjectCastUtils::castToString);
+        }
+    }
+
+    private static Object convert(Object value, Function<Object, Object> func) {
+        try {
+            return func.apply(value);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    public static Object castByDetermine(final Object in) {
+        if (null == in) {
+            return null;
+        }
+
+        if (in instanceof BigInteger) {
+            return ((BigInteger) in).longValue();
+        } else if (in instanceof BigDecimal) {
+            BigDecimal decimal = (BigDecimal) in;
+            return decimal.doubleValue();
+        } else if (in instanceof java.sql.Clob) {
+            return clob2Str((java.sql.Clob) in);
+        } else if (in instanceof java.sql.Array
+            || in instanceof java.sql.SQLXML) {
+            try {
+                return objectToString(in);
+            } catch (Exception e) {
+                log.warn("Unsupported type for convert {} to java.lang.String", in.getClass().getName());
+                return null;
+            }
+        } else if (in instanceof java.sql.Blob) {
+            try {
+                return blob2Bytes((java.sql.Blob) in);
+            } catch (Exception e) {
+                log.warn("Unsupported type for convert {} to byte[] ", in.getClass().getName());
+                return null;
+            }
+        } else if (in instanceof java.sql.Struct) {
+            log.warn("Unsupported type for convert {} to java.lang.String", in.getClass().getName());
+            return null;
+        }
+
+        return in;
+    }
+}

+ 20 - 0
km-common/km-common-db/src/main/java/com/km/common/db/annotation/Product.java

@@ -0,0 +1,20 @@
+package com.km.common.db.annotation;
+
+import com.km.common.db.enums.DbProductTypeEnum;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @Description: Product
+ *
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Product {
+    DbProductTypeEnum value();
+}

+ 83 - 0
km-common/km-common-db/src/main/java/com/km/common/db/constant/DbConstants.java

@@ -0,0 +1,83 @@
+package com.km.common.db.constant;
+
+import java.io.File;
+import java.nio.file.FileSystems;
+
+/**
+ * @Description: Constants
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+public final class DbConstants {
+    /**
+     * What's the file systems file separator on this operating system?
+     */
+    public static final String FILE_SEPARATOR = FileSystems.getDefault().getSeparator();
+
+    /**
+     * What's the path separator on this operating system?
+     */
+    public static final String PATH_SEPARATOR = File.pathSeparator;
+
+    /**
+     * CR: operating systems specific Carriage Return
+     */
+    public static final String CR = System.lineSeparator();
+
+    /**
+     * DOSCR: MS-DOS specific Carriage Return
+     */
+    public static final String DOSCR = "\n\r";
+
+    /**
+     * An empty ("") String.
+     */
+    public static final String EMPTY_STRING = "";
+
+    /**
+     * The Java runtime version
+     */
+    public static final String JAVA_VERSION = System.getProperty("java.vm.version");
+
+    /**
+     * Create Table Statement Prefix String
+     */
+    public static final String CREATE_TABLE = "CREATE TABLE ";
+
+    /**
+     * Drop Table Statement Prefix String
+     */
+    public static final String DROP_TABLE = "DROP TABLE ";
+
+    /**
+     * Constant Keyword String
+     */
+    public static final String IF_NOT_EXISTS = "IF NOT EXISTS ";
+
+    /**
+     * Constant Keyword String
+     */
+    public static final String IF_EXISTS = "IF EXISTS ";
+
+    public static final int CLOB_LENGTH = 9999999;
+
+    /**
+     * Default jdbc query timeout seconds
+     */
+    public static Integer DEFAULT_QUERY_TIMEOUT_SECONDS = 60 * 60;
+
+    /**
+     * default jdbc fetch-size value
+     */
+    public static int DEFAULT_FETCH_SIZE = 1000;
+
+    /**
+     * minimum jdbc fetch-size value
+     */
+    public static int MINIMUM_FETCH_SIZE = 100;
+
+    /**
+     * database product module SPI file path name
+     */
+    public static final String SPI_FILE = "META-INF/services/db.providers";
+}

+ 19 - 0
km-common/km-common-db/src/main/java/com/km/common/db/entity/CloseableDataSource.java

@@ -0,0 +1,19 @@
+package com.km.common.db.entity;
+
+import javax.sql.DataSource;
+import java.io.Closeable;
+
+/**
+ * @Description: CloseableDataSource
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+public interface CloseableDataSource extends DataSource, Closeable {
+    String getJdbcUrl();
+
+    String getDriverClass();
+
+    String getUserName();
+
+    String getPassword();
+}

+ 26 - 0
km-common/km-common-db/src/main/java/com/km/common/db/entity/GlobalParamConfigProperties.java

@@ -0,0 +1,26 @@
+package com.km.common.db.entity;
+
+import lombok.Data;
+
+/**
+ * @Description: GlobalParamConfigProperties
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+@Data
+public class GlobalParamConfigProperties {
+
+    private int channelQueueSize;
+
+    private int writeThreadNum;
+
+    public GlobalParamConfigProperties() {
+        this.channelQueueSize = 100;
+        this.writeThreadNum = getDefaultWriteThreadNum();
+    }
+
+    private int getDefaultWriteThreadNum() {
+        int availableProcessorCount = Runtime.getRuntime().availableProcessors();
+        return Math.min(Math.max(4, availableProcessorCount), 8);
+    }
+}

+ 117 - 0
km-common/km-common-db/src/main/java/com/km/common/db/entity/InvisibleDataSource.java

@@ -0,0 +1,117 @@
+package com.km.common.db.entity;
+
+import lombok.Getter;
+
+import javax.sql.DataSource;
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.Driver;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.logging.Logger;
+
+/**
+ * @Description: InvisibleDataSource
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+@Getter
+public class InvisibleDataSource implements DataSource {
+
+    private final ClassLoader classLoader;
+    private final String jdbcUrl;
+    private final String driverClassName;
+    private final Properties properties;
+
+    public InvisibleDataSource(ClassLoader cl, String jdbcUrl, String driverClassName, String username,
+                               String password, Properties properties) {
+        this.classLoader = Objects.requireNonNull(cl, "parameter invalid for empty class loader");
+        this.jdbcUrl = Objects.requireNonNull(jdbcUrl, "parameter invalid for empty jdbc url");
+        this.driverClassName = Objects.requireNonNull(driverClassName, "parameter invalid for empty driver class name");
+        this.properties = Objects.requireNonNull(properties, "parameter invalid for properties ");
+
+        if (username != null) {
+            this.properties.put("user", properties.getProperty("user", username));
+        }
+        if (password != null) {
+            this.properties.put("password", properties.getProperty("password", password));
+        }
+    }
+
+    @Override
+    public Connection getConnection() throws SQLException {
+        return doGetConnection(properties);
+    }
+
+    @Override
+    public Connection getConnection(String username, String password) throws SQLException {
+        final Properties cloned = (Properties) properties.clone();
+        if (username != null) {
+            cloned.put("user", username);
+            if (cloned.containsKey("username")) {
+                cloned.put("username", username);
+            }
+        }
+        if (password != null) {
+            cloned.put("password", password);
+        }
+
+        return doGetConnection(cloned);
+    }
+
+    private Connection doGetConnection(Properties properties) throws SQLException {
+        Driver driver = null;
+        try {
+            Class<?> driverType = Class.forName(driverClassName, true, classLoader);
+            driver = (Driver) driverType.getDeclaredConstructor().newInstance();
+        } catch (Exception e) {
+            throw new SQLException("Invalid driver class name [" + driverClassName + "]. Cause: " + e);
+        }
+
+        Connection connection = driver.connect(jdbcUrl, properties);
+        if (null == connection) {
+            throw new SQLException(
+                "Maybe invalid driver class name [" + driverClassName + "] or url [" + jdbcUrl + "]");
+        }
+
+        return connection;
+    }
+
+    @Override
+    public <T> T unwrap(Class<T> iface) throws SQLException {
+        throw new SQLFeatureNotSupportedException();
+    }
+
+    @Override
+    public boolean isWrapperFor(Class<?> iface) throws SQLException {
+        return false;
+    }
+
+    @Override
+    public PrintWriter getLogWriter() throws SQLException {
+        throw new SQLFeatureNotSupportedException();
+    }
+
+    @Override
+    public void setLogWriter(PrintWriter out) throws SQLException {
+        throw new SQLFeatureNotSupportedException();
+    }
+
+    @Override
+    public void setLoginTimeout(int seconds) throws SQLException {
+        DriverManager.setLoginTimeout(seconds);
+    }
+
+    @Override
+    public int getLoginTimeout() throws SQLException {
+        return DriverManager.getLoginTimeout();
+    }
+
+    @Override
+    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+        throw new SQLFeatureNotSupportedException();
+    }
+}

+ 87 - 0
km-common/km-common-db/src/main/java/com/km/common/db/entity/JarClassLoader.java

@@ -0,0 +1,87 @@
+package com.km.common.db.entity;
+
+import com.km.common.db.Utils.ExamineUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * @Description: JarClassLoader
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+public class JarClassLoader extends URLClassLoader {
+
+    public JarClassLoader(String path, ClassLoader parent) {
+        this(new String[]{path}, parent);
+    }
+
+    public JarClassLoader(String[] paths, ClassLoader parent) {
+        super(getURLs(paths), parent);
+    }
+
+    private static URL[] getURLs(String[] paths) {
+        ExamineUtils.checkArgument(
+            null != paths && 0 != paths.length,
+            "jar file path is empty.");
+        List<String> dirs = new ArrayList<>();
+        assert paths != null;
+        for (String path : paths) {
+            dirs.add(path);
+            collectDirs(path, dirs);
+        }
+        List<URL> urls = new ArrayList<>();
+        for (String path : dirs) {
+            urls.addAll(doGetURLs(path));
+        }
+        if (urls.isEmpty()) {
+            throw new RuntimeException("No jar file found from path :"
+                + String.join(",", paths) + "!");
+        }
+        return urls.toArray(new URL[0]);
+    }
+
+    private static void collectDirs(String path, List<String> collector) {
+        if (StringUtils.isEmpty(path)) {
+            return;
+        }
+        File current = new File(path);
+        if (!current.exists() || !current.isDirectory()) {
+            return;
+        }
+        for (File child : Objects.requireNonNull(current.listFiles())) {
+            if (!child.isDirectory()) {
+                continue;
+            }
+            collector.add(child.getAbsolutePath());
+            collectDirs(child.getAbsolutePath(), collector);
+        }
+    }
+
+    private static List<URL> doGetURLs(final String path) {
+        ExamineUtils.checkArgument(StringUtils.isNotBlank(path), "jar path is empty.");
+        File jarPath = new File(path);
+        if (!jarPath.exists() || !jarPath.isDirectory()) {
+            return Collections.emptyList();
+        }
+        FileFilter jarFilter = name -> name.getName().endsWith(".jar");
+        File[] allJars = new File(path).listFiles(jarFilter);
+        assert allJars != null;
+        List<URL> jarUrls = new ArrayList<>(allJars.length);
+        for (File allJar : allJars) {
+            try {
+                jarUrls.add(allJar.toURI().toURL());
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+        return jarUrls;
+    }
+}

+ 44 - 0
km-common/km-common-db/src/main/java/com/km/common/db/entity/PatternMapper.java

@@ -0,0 +1,44 @@
+package com.km.common.db.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Objects;
+
+/**
+ * @Description: 基于正则表达式的批评替换实体定义
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+@AllArgsConstructor
+@Data
+public class PatternMapper {
+    private String fromPattern;
+    private String toValue;
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        PatternMapper that = (PatternMapper) o;
+        return fromPattern.equals(that.fromPattern) && toValue.equals(that.toValue);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(fromPattern, toValue);
+    }
+
+    @Override
+    public String toString() {
+        return "PatternMapper{" +
+            "fromPattern='" + fromPattern + '\'' +
+            ", toValue='" + toValue + '\'' +
+            '}';
+    }
+}

+ 66 - 0
km-common/km-common-db/src/main/java/com/km/common/db/entity/ResultSetWrapper.java

@@ -0,0 +1,66 @@
+package com.km.common.db.entity;
+
+import lombok.Builder;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+/**
+ * @Description: JDBC连接及结果集实体参数定义类
+ *
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+@Slf4j
+public class ResultSetWrapper implements AutoCloseable {
+
+    private boolean isAutoCommit;
+    @Getter
+    private Connection connection;
+    @Getter
+    private Statement statement;
+    @Getter
+    private ResultSet resultSet;
+
+
+    @Builder
+    public ResultSetWrapper(ResultSet resultSet, Statement statement, Connection connection) {
+        this.resultSet = resultSet;
+        this.statement = statement;
+        this.connection = connection;
+        try {
+            this.isAutoCommit = connection.getAutoCommit();
+        } catch (SQLException e) {
+            log.warn("Jdbc Connection getAutoCommit() failed, set default true, error: {}", e.getMessage());
+            this.isAutoCommit = false;
+        }
+    }
+
+    @Override
+    public void close() {
+        try {
+            connection.setAutoCommit(isAutoCommit);
+        } catch (SQLException e) {
+            log.warn("Jdbc Connection setAutoCommit() failed, error: {}", e.getMessage());
+        }
+        try {
+            resultSet.close();
+        } catch (SQLException e) {
+            log.warn("Jdbc ResultSet close() failed, error: {}", e.getMessage());
+        }
+        try {
+            statement.close();
+        } catch (SQLException e) {
+            log.warn("Jdbc Statement close() failed, error: {}", e.getMessage());
+        }
+        try {
+            connection.close();
+        } catch (SQLException e) {
+            log.warn("Jdbc Connection close() failed, error: {}", e.getMessage());
+        }
+    }
+}

+ 30 - 0
km-common/km-common-db/src/main/java/com/km/common/db/entity/SourceDataSourceProperties.java

@@ -0,0 +1,30 @@
+package com.km.common.db.entity;
+
+import lombok.Data;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @Description: 源端参数配置
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+@Data
+public class SourceDataSourceProperties {
+    private String url;
+    private String driverClassName;
+    private String username;
+    private String password;
+    private String driverPath;
+    private Long connectionTimeout = TimeUnit.SECONDS.toMillis(60);
+    private Long maxLifeTime = TimeUnit.MINUTES.toMillis(60);
+
+    private Integer fetchSize = 5000;
+    private String sourceSchema = "";
+    private String tableType = "TABLE";
+    private String sourceIncludes = "";
+    private String sourceExcludes = "";
+    private List<PatternMapper> regexTableMapper;
+    private List<PatternMapper> regexColumnMapper;
+}

+ 38 - 0
km-common/km-common-db/src/main/java/com/km/common/db/entity/TargetDataSourceProperties.java

@@ -0,0 +1,38 @@
+package com.km.common.db.entity;
+
+import com.km.common.db.enums.CaseConvertEnum;
+import com.km.common.db.enums.SyncOptionEnum;
+import lombok.Data;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @Description: TargetDataSourceProperties
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+@Data
+public class TargetDataSourceProperties {
+
+    private String url;
+    private String driverClassName;
+    private String username;
+    private String password;
+    private String driverPath;
+
+    private Long connectionTimeout = TimeUnit.SECONDS.toMillis(60);
+    private Long maxLifeTime = TimeUnit.MINUTES.toMillis(30);
+
+    private String targetSchema = "";
+    private CaseConvertEnum tableNameCase = CaseConvertEnum.NONE;
+    private CaseConvertEnum columnNameCase = CaseConvertEnum.NONE;
+    private Boolean targetDrop = Boolean.TRUE;
+    private Boolean onlyCreate = Boolean.FALSE;
+    private Boolean changeDataSync = Boolean.FALSE;
+    private Boolean createTableAutoIncrement = Boolean.FALSE;
+    private Boolean writerEngineInsert = Boolean.FALSE;
+
+    private String beforeSqlScripts;
+    private String afterSqlScripts;
+    private SyncOptionEnum targetSyncOption = SyncOptionEnum.INSERT_UPDATE_DELETE;
+}

+ 93 - 0
km-common/km-common-db/src/main/java/com/km/common/db/entity/WrapCommonDataSource.java

@@ -0,0 +1,93 @@
+package com.km.common.db.entity;
+
+import java.io.PrintWriter;
+import java.net.URLClassLoader;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.util.Objects;
+import java.util.logging.Logger;
+
+/**
+ * @Description: WrapCommonDataSource
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+public class WrapCommonDataSource implements CloseableDataSource {
+
+    private InvisibleDataSource commonDataSource;
+    private URLClassLoader urlClassLoader;
+
+    public WrapCommonDataSource(InvisibleDataSource commonDataSource, URLClassLoader urlClassLoader) {
+        this.commonDataSource = Objects.requireNonNull(commonDataSource);
+        this.urlClassLoader = Objects.requireNonNull(urlClassLoader);
+    }
+
+    @Override
+    public String getJdbcUrl() {
+        return commonDataSource.getJdbcUrl();
+    }
+
+    @Override
+    public String getDriverClass() {
+        return commonDataSource.getDriverClassName();
+    }
+
+    @Override
+    public String getUserName() {
+        return commonDataSource.getProperties().getProperty("user");
+    }
+
+    @Override
+    public String getPassword() {
+        return commonDataSource.getProperties().getProperty("password");
+    }
+
+    @Override
+    public Connection getConnection() throws SQLException {
+        return commonDataSource.getConnection();
+    }
+
+    @Override
+    public Connection getConnection(String username, String password) throws SQLException {
+        return commonDataSource.getConnection(username, password);
+    }
+
+    @Override
+    public <T> T unwrap(Class<T> iface) throws SQLException {
+        return commonDataSource.unwrap(iface);
+    }
+
+    @Override
+    public boolean isWrapperFor(Class<?> iface) throws SQLException {
+        return commonDataSource.isWrapperFor(iface);
+    }
+
+    @Override
+    public PrintWriter getLogWriter() throws SQLException {
+        return commonDataSource.getLogWriter();
+    }
+
+    @Override
+    public void setLogWriter(PrintWriter out) throws SQLException {
+        commonDataSource.setLogWriter(out);
+    }
+
+    @Override
+    public void setLoginTimeout(int seconds) throws SQLException {
+        commonDataSource.setLoginTimeout(seconds);
+    }
+
+    @Override
+    public int getLoginTimeout() throws SQLException {
+        return commonDataSource.getLoginTimeout();
+    }
+
+    @Override
+    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+        return commonDataSource.getParentLogger();
+    }
+
+    public void close() {
+    }
+}

+ 120 - 0
km-common/km-common-db/src/main/java/com/km/common/db/entity/WrapHikariDataSource.java

@@ -0,0 +1,120 @@
+package com.km.common.db.entity;
+
+import com.zaxxer.hikari.HikariDataSource;
+import lombok.extern.slf4j.Slf4j;
+
+import javax.sql.DataSource;
+import java.io.PrintWriter;
+import java.net.URLClassLoader;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.util.Objects;
+import java.util.logging.Logger;
+
+/**
+ * @Description: WrapHikariDataSource
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+@Slf4j
+public class WrapHikariDataSource implements CloseableDataSource {
+
+    private HikariDataSource hikariDataSource;
+    private URLClassLoader urlClassLoader;
+
+    public WrapHikariDataSource(HikariDataSource hikariDataSource, URLClassLoader urlClassLoader) {
+        this.hikariDataSource = Objects.requireNonNull(hikariDataSource);
+        this.urlClassLoader = Objects.requireNonNull(urlClassLoader);
+    }
+
+    @Override
+    public String getJdbcUrl() {
+        DataSource dataSource = hikariDataSource.getDataSource();
+        if (null != dataSource && dataSource instanceof InvisibleDataSource) {
+            InvisibleDataSource invisibleDataSource = (InvisibleDataSource) dataSource;
+            return invisibleDataSource.getJdbcUrl();
+        }
+
+        return hikariDataSource.getJdbcUrl();
+    }
+
+    @Override
+    public String getDriverClass() {
+        DataSource dataSource = hikariDataSource.getDataSource();
+        if (null != dataSource && dataSource instanceof InvisibleDataSource) {
+            InvisibleDataSource invisibleDataSource = (InvisibleDataSource) dataSource;
+            return invisibleDataSource.getDriverClassName();
+        }
+        return hikariDataSource.getDriverClassName();
+    }
+
+    @Override
+    public String getUserName() {
+        DataSource dataSource = hikariDataSource.getDataSource();
+        if (null != dataSource && dataSource instanceof InvisibleDataSource) {
+            InvisibleDataSource invisibleDataSource = (InvisibleDataSource) dataSource;
+            return invisibleDataSource.getProperties().getProperty("user");
+        }
+        return hikariDataSource.getUsername();
+    }
+
+    @Override
+    public String getPassword() {
+        DataSource dataSource = hikariDataSource.getDataSource();
+        if (null != dataSource && dataSource instanceof InvisibleDataSource) {
+            InvisibleDataSource invisibleDataSource = (InvisibleDataSource) dataSource;
+            return invisibleDataSource.getProperties().getProperty("password");
+        }
+        return hikariDataSource.getPassword();
+    }
+
+    @Override
+    public Connection getConnection() throws SQLException {
+        return hikariDataSource.getConnection();
+    }
+
+    @Override
+    public Connection getConnection(String username, String password) throws SQLException {
+        return hikariDataSource.getConnection(username, password);
+    }
+
+    @Override
+    public <T> T unwrap(Class<T> iface) throws SQLException {
+        return hikariDataSource.unwrap(iface);
+    }
+
+    @Override
+    public boolean isWrapperFor(Class<?> iface) throws SQLException {
+        return hikariDataSource.isWrapperFor(iface);
+    }
+
+    @Override
+    public PrintWriter getLogWriter() throws SQLException {
+        return hikariDataSource.getLogWriter();
+    }
+
+    @Override
+    public void setLogWriter(PrintWriter out) throws SQLException {
+        hikariDataSource.setLogWriter(out);
+    }
+
+    @Override
+    public void setLoginTimeout(int seconds) throws SQLException {
+        hikariDataSource.setLoginTimeout(seconds);
+    }
+
+    @Override
+    public int getLoginTimeout() throws SQLException {
+        return hikariDataSource.getLoginTimeout();
+    }
+
+    @Override
+    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+        return hikariDataSource.getParentLogger();
+    }
+
+    public void close() {
+        hikariDataSource.close();
+    }
+}

+ 28 - 0
km-common/km-common-db/src/main/java/com/km/common/db/enums/CaseConvertEnum.java

@@ -0,0 +1,28 @@
+package com.km.common.db.enums;
+
+/**
+ * @Description: 处理大小写转换的枚举类
+ *
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+public enum CaseConvertEnum {
+    NONE(s -> s),
+    UPPER(String::toUpperCase),
+    LOWER(String::toLowerCase),
+    ;
+
+    private Converter function;
+
+    CaseConvertEnum(Converter function) {
+        this.function = function;
+    }
+
+    public String convert(String name) {
+        return function.convert(name);
+    }
+
+    interface Converter {
+        String convert(String s);
+    }
+}

+ 29 - 0
km-common/km-common-db/src/main/java/com/km/common/db/enums/DbProductTableEnum.java

@@ -0,0 +1,29 @@
+package com.km.common.db.enums;
+
+/**
+ * @Description: DbProductTableEnum
+ *
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+public enum DbProductTableEnum {
+    /**
+     * 物理表
+     */
+    TABLE(0),
+
+    /**
+     * 视图表
+     */
+    VIEW(1);
+
+    private int index;
+
+    DbProductTableEnum(int idx) {
+        this.index = idx;
+    }
+
+    public int getIndex() {
+        return index;
+    }
+}

+ 341 - 0
km-common/km-common-db/src/main/java/com/km/common/db/enums/DbProductTypeEnum.java

@@ -0,0 +1,341 @@
+package com.km.common.db.enums;
+
+import cn.hutool.core.util.StrUtil;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * @Description: 数据库产品类型
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 8:54
+ */
+@Getter
+@AllArgsConstructor
+public enum DbProductTypeEnum {
+    /**
+     * MySQL数据库类型
+     */
+    MYSQL(1, "`", "mysql", "com.mysql.jdbc.Driver", 3306,
+        "/* ping */ SELECT 1",
+        "jdbc:mysql://",
+        new String[]{"jdbc:mysql://{host}[:{port}]/[{database}][\\?{params}]"},
+        "jdbc:mysql://172.17.2.10:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai&tinyInt1isBit=false&rewriteBatchedStatements=true&useCompression=true"),
+
+    /**
+     * MariaDB数据库类型
+     */
+    MARIADB(2, "`", "mariadb", "org.mariadb.jdbc.Driver", 3306,
+        "SELECT 1",
+        "jdbc:mariadb://",
+        new String[]{"jdbc:mariadb://{host}[:{port}]/[{database}][\\?{params}]"},
+        "jdbc:mariadb://172.17.2.10:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai&tinyInt1isBit=false&rewriteBatchedStatements=true&useCompression=true"),
+
+    /**
+     * Oracle数据库类型
+     */
+    ORACLE(3, "\"", "oracle", "oracle.jdbc.driver.OracleDriver", 1521,
+        "SELECT 'Hello' from DUAL",
+        "jdbc:oracle:thin:@",
+        new String[]{"jdbc:oracle:thin:@{host}:{port}:{database}",
+            "jdbc:oracle:thin:@//{host}[:{port}]/{database}"},
+        "jdbc:oracle:thin:@172.17.2.10:1521:ORCL"),
+
+    /**
+     * Microsoft SQL Server数据库类型(>=2005)
+     */
+    SQLSERVER(4, "\"", "sqlserver", "com.microsoft.sqlserver.jdbc.SQLServerDriver", 1433,
+        "SELECT 1+2 as a",
+        "jdbc:sqlserver://",
+        new String[]{"jdbc:sqlserver://{host}[:{port}][;DatabaseName={database}][;{params}]"},
+        "jdbc:sqlserver://172.17.2.10:1433;DatabaseName=test"),
+
+    /**
+     * PostgreSQL数据库类型
+     */
+    POSTGRESQL(5, "\"", "postgresql", "org.postgresql.Driver", 5432,
+        "SELECT 1",
+        "jdbc:postgresql://",
+        new String[]{"jdbc:postgresql://{host}[:{port}]/[{database}][\\?{params}]"},
+        "jdbc:postgresql://172.17.2.10:5432/test"),
+
+    /**
+     * DB2数据库类型
+     */
+    DB2(6, "\"", "db2", "com.ibm.db2.jcc.DB2Driver", 50000,
+        "SELECT 1 FROM SYSIBM.SYSDUMMY1",
+        "jdbc:db2://",
+        new String[]{"jdbc:db2://{host}:{port}/{database}[:{params}]"},
+        "jdbc:db2://172.17.2.10:50000/testdb:driverType=4;fullyMaterializeLobData=true;fullyMaterializeInputStreams=true;progressiveStreaming=2;progresssiveLocators=2;"),
+
+    /**
+     * [国产] 达梦(DM)数据库类型
+     */
+    DM(7, "\"", "dm", "dm.jdbc.driver.DmDriver", 5236,
+        "SELECT 'Hello' from DUAL",
+        "jdbc:dm://",
+        new String[]{"jdbc:dm://{host}:{port}[/{database}][\\?{params}]"},
+        "jdbc:dm://172.17.2.10:5236"),
+
+    /**
+     * [国产] 金仓(Kingbase)数据库类型
+     */
+    KINGBASE(8, "\"", "kingbase", "com.kingbase8.Driver", 54321,
+        "SELECT 1",
+        "jdbc:kingbase8://",
+        new String[]{"jdbc:kingbase8://{host}[:{port}]/[{database}][\\?{params}]"},
+        "jdbc:kingbase8://172.17.2.10:54321/test"),
+
+    /**
+     * [国产] 神通(Oscar)数据库类型
+     */
+    OSCAR(9, "\"", "oscar", "com.oscar.Driver", 2003,
+        "SELECT 1",
+        "jdbc:oscar://",
+        new String[]{"jdbc:oscar://{host}[:{port}]/[{database}][\\?{params}]"},
+        "jdbc:oscar://172.17.2.10:2003/OSCRDB"),
+
+    /**
+     * [国产] 南大通用(GBase8A)数据库类型
+     */
+    GBASE8A(10, "`", "gbase8a", "com.gbase.jdbc.Driver", 5258,
+        "/* ping */ SELECT 1",
+        "jdbc:gbase://",
+        new String[]{"jdbc:gbase://{host}[:{port}]/[{database}][\\?{params}]"},
+        "jdbc:gbase://172.17.2.10:5258/test"),
+
+    /**
+     * Highgo数据库类型:https://blog.csdn.net/weixin_39676699/article/details/134338598
+     */
+    HIGHGO(11, "\"", "highgo", "com.highgo.jdbc.Driver", 5866,
+        "SELECT 1",
+        "jdbc:highgo://",
+        new String[]{"jdbc:highgo://{host}[:{port}]/[{database}][\\?{params}]"},
+        "jdbc:highgo://172.17.2.10:5866/highgo"),
+
+    /**
+     * Sybase 数据库类型
+     */
+    SYBASE(12, "\"", "sybase", "com.sybase.jdbc4.jdbc.SybDriver", 5000,
+        "SELECT 1+2 as a",
+        "jdbc:sybase:Tds:",
+        new String[]{"jdbc:sybase:Tds:{host}[:{port}][/{database}][\\?{params}]"},
+        "jdbc:sybase:Tds:172.17.2.10:5000/test?charset=cp936"),
+
+    /**
+     * Hive 数据库类型
+     */
+    HIVE(13, "`", "hive", "org.apache.hive.jdbc.HiveDriver", 10000,
+        "SELECT 1",
+        "jdbc:hive2://",
+        new String[]{"jdbc:hive2://{host}[:{port}]/[{database}][\\?{params}]"},
+        "jdbc:hive2://172.17.2.12:10000/default"),
+
+    /**
+     * Sqlite v3数据库类型
+     */
+    // 参考文章:https://blog.csdn.net/wank1259162/article/details/104946744
+    SQLITE3(14, "\"", "sqlite3", "org.sqlite.JDBC", 0,
+        "SELECT 1",
+        "jdbc:sqlite:",
+        new String[]{"jdbc:sqlite:{file}", "jdbc:sqlite::resource:{file}"},
+        "jdbc:sqlite:/tmp/test.db"),
+
+    /**
+     * OpenGauss数据库类型
+     */
+    OPENGAUSS(15, "\"", "opengauss", "org.opengauss.Driver", 15432,
+        "SELECT 1",
+        "jdbc:opengauss://",
+        new String[]{"jdbc:opengauss://{host}[:{port}]/[{database}][\\?{params}]"},
+        "jdbc:opengauss://172.17.2.10:5866/test"),
+
+    /**
+     * ClickHouse数据库类型
+     */
+    CLICKHOUSE(16, "`", "clickhouse", "com.clickhouse.jdbc.ClickHouseDriver", 8123,
+        "SELECT 1",
+        "jdbc:clickhouse://",
+        new String[]{"jdbc:clickhouse://{host}[:{port}]/[{database}][\\?{params}]"},
+        "jdbc:clickhouse://172.17.2.10:8123/default"),
+
+    /**
+     * MongoDB数据库类型
+     */
+    MONGODB(17, "\"", "mongoDB", "com.gitee.jdbc.mongodb.JdbcDriver", 27017,
+        "use admin;",
+        "jdbc:mongodb://",
+        new String[]{"jdbc:mongodb://{host}[:{port}]/[{database}][\\?{params}]"},
+        "jdbc:mongodb://172.17.2.12:27017/admin?authSource=admin&authMechanism=SCRAM-SHA-1"),
+
+    /**
+     * ElasticSearch数据库类型
+     */
+    ELASTICSEARCH(18, "\"", "ElasticSearch", "com.gitee.jdbc.elasticsearch.JdbcDriver", 9200,
+        "",
+        "jdbc:jest://",
+        new String[]{"jdbc:jest://{host}[:{port}][\\?{params}]"},
+        "jdbc:jest://172.17.2.12:9200?useHttps=false"),
+    ;
+
+    private long id;
+    private String quote;
+    private String name;
+    private String driver;
+    private int port;
+    private String sql;
+    private String urlPrefix;
+    private String[] url;
+    private String sample;
+
+    public boolean hasDatabaseName() {
+        return !Arrays.asList(DM, SQLITE3, MYSQL, MARIADB, GBASE8A, ELASTICSEARCH).contains(this);
+    }
+
+    public boolean hasFilePath() {
+        return this == SQLITE3;
+    }
+
+    public boolean hasAddress() {
+        return this != SQLITE3;
+    }
+
+    public boolean noCommentStatement() {
+        return Arrays.asList(
+            DbProductTypeEnum.MYSQL,
+            DbProductTypeEnum.MARIADB,
+            DbProductTypeEnum.GBASE8A,
+            DbProductTypeEnum.HIVE,
+            DbProductTypeEnum.SQLITE3,
+            DbProductTypeEnum.SYBASE
+        ).contains(this);
+    }
+
+    public String quoteName(String name) {
+        return String.format("%s%s%s", quote, name, quote);
+    }
+
+    public String quoteSchemaTableName(String schema, String table) {
+        return String.format("%s%s%s.%s%s%s", quote, schema, quote, quote, table, quote);
+    }
+
+    /**
+     * 类似于PostgreSQL系列的数据库类型
+     *
+     * @return boolean
+     */
+    public boolean isLikePostgres() {
+        return this == POSTGRESQL || this == KINGBASE || this == OPENGAUSS;
+    }
+
+    /**
+     * 类似于MySQL系列的数据库类型
+     *
+     * @return boolean
+     */
+    public boolean isLikeMysql() {
+        return this == MYSQL || this == MARIADB;
+    }
+
+    /**
+     * 类似于MySQL系列的数据库类型
+     *
+     * @return boolean
+     */
+    public boolean isLikeGbase8a() {
+        return this == GBASE8A;
+    }
+
+    /**
+     * 类似于Oracle系列的数据库类型
+     *
+     * @return boolean
+     */
+    public boolean isLikeOracle() {
+        return this == ORACLE || this == DM;
+    }
+
+    /**
+     * 类似于SQL Server系列的数据库类型
+     *
+     * @return boolean
+     */
+    public boolean isLikeSqlServer() {
+        return this == SQLSERVER || this == SYBASE;
+    }
+
+    /**
+     * 类似于Hive系列的数据库类型
+     *
+     * @return boolean
+     */
+    public boolean isLikeHive() {
+        return this == HIVE;
+    }
+
+    /**
+     * 是否为MongoDB数据库类型
+     *
+     * @return boolean res
+     */
+    public boolean isMongodb() {
+        return this == MONGODB;
+    }
+
+    /**
+     * 是否为ElasticSearch数据库类型
+     *
+     * @return boolean res
+     */
+    public boolean isElasticSearch() {
+        return this == ELASTICSEARCH;
+    }
+
+    /**
+     * 是否为ClickHouse数据库类型
+     *
+     * @return boolean res
+     */
+    public boolean isClickHouse() {
+        return this == CLICKHOUSE;
+    }
+
+    /**
+     * 是否存在指定字符串名称的数据库类型
+     *
+     * @param name 字符串名称
+     * @return boolean
+     */
+    public static boolean exists(String name) {
+        return Arrays.stream(values()).anyMatch(item -> item.name().equalsIgnoreCase(name));
+    }
+
+    /**
+     * 将字符串名称转换为枚举值
+     *
+     * @param name 字符串名称
+     * @return ProductTypeEnum
+     */
+    public static DbProductTypeEnum of(String name) {
+        if (!StrUtil.isEmpty(name)) {
+            for (DbProductTypeEnum type : DbProductTypeEnum.values()) {
+                if (type.getName().equalsIgnoreCase(name)) {
+                    return type;
+                }
+            }
+        }
+        throw new IllegalArgumentException("cannot find enum name: " + name);
+    }
+
+    /**
+     * 针对SQLite数据库的URL连接串判断
+     *
+     * @param url SQLite数据库的URL连接串
+     * @return boolean
+     */
+    public static boolean isUnsupportedTargetSqlite(String url) {
+        String prefix1 = "jdbc:sqlite::resource:";
+        return url.startsWith(prefix1);
+    }
+}

+ 24 - 0
km-common/km-common-db/src/main/java/com/km/common/db/enums/DbTableIndexEnum.java

@@ -0,0 +1,24 @@
+package com.km.common.db.enums;
+
+/**
+ * @Description: 表的索引类型枚举定义
+ *
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 12:53
+ */
+public enum DbTableIndexEnum {
+
+    NORMAL("普通索引"),
+    UNIQUE("唯一索引"),
+    ;
+
+    private String description;
+
+    DbTableIndexEnum(String description) {
+        this.description = description;
+    }
+
+    public boolean isUnique() {
+        return UNIQUE == this;
+    }
+}

+ 40 - 0
km-common/km-common-db/src/main/java/com/km/common/db/enums/SyncOptionEnum.java

@@ -0,0 +1,40 @@
+package com.km.common.db.enums;
+
+/**
+ * @Description: SyncOptionEnum
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 12:55
+ */
+public enum SyncOptionEnum {
+
+    ONLY_INSERT(1),
+    ONLY_UPDATE(2),
+    INSERT_UPDATE(3),
+    ONLY_DELETE(4),
+    INSERT_DELETE(5),
+    UPDATE_DELETE(6),
+    INSERT_UPDATE_DELETE(7),
+    ;
+
+    private static int MASK_INSERT = 1;
+    private static int MASK_UPDATE = 2;
+    private static int MASK_DELETE = 4;
+
+    private int flag;
+
+    SyncOptionEnum(int flat) {
+        this.flag = flat;
+    }
+
+    public boolean callInsert() {
+        return (flag & MASK_INSERT) != 0;
+    }
+
+    public boolean callUpdate() {
+        return (flag & MASK_UPDATE) != 0;
+    }
+
+    public boolean callDelete() {
+        return (flag & MASK_DELETE) != 0;
+    }
+}

+ 85 - 0
km-common/km-common-db/src/main/java/com/km/common/db/provider/AbstractCommonProvider.java

@@ -0,0 +1,85 @@
+package com.km.common.db.provider;
+
+import com.km.common.db.Utils.ExamineUtils;
+import com.km.common.db.enums.DbProductTypeEnum;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.support.DefaultTransactionDefinition;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.Statement;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @Description: AbstractCommonProvider
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 14:29
+ */
+public abstract class AbstractCommonProvider {
+
+    private IProductFactoryProvider factoryProvider;
+    private DataSource dataSource;
+    private IProductFeatures productFeatures;
+
+    protected AbstractCommonProvider(IProductFactoryProvider factoryProvider) {
+        ExamineUtils.checkNotNull(factoryProvider, "factoryProvider");
+        this.factoryProvider = factoryProvider;
+        this.dataSource = factoryProvider.getDataSource();
+        this.productFeatures = factoryProvider.getProductFeatures();
+    }
+
+    public DbProductTypeEnum getProductType() {
+        return this.factoryProvider.getProductType();
+    }
+
+    public DataSource getDataSource() {
+        return dataSource;
+    }
+
+    public IProductFeatures getProductFeatures() {
+        return productFeatures;
+    }
+
+    protected String getTableFieldsQuerySQL(String schemaName, String tableName) {
+        DbProductTypeEnum productType = getProductType();
+        String fullTableName = productType.quoteSchemaTableName(schemaName, tableName);
+        return String.format("SELECT %s FROM %s WHERE 1=2", "*", fullTableName);
+    }
+
+    protected Map<String, Integer> getTableColumnMetaData(String schemaName, String tableName, List<String> fieldNames) {
+        DbProductTypeEnum productType = getProductType();
+        String fullTableName = productType.quoteSchemaTableName(schemaName, tableName);
+        String queryColumnStr = CollectionUtils.isEmpty(fieldNames) ? "*"
+            : productType.quoteName(StringUtils.join(fieldNames, productType.quoteName(",")));
+        String sql = String.format("SELECT %s FROM %s WHERE 1=2", queryColumnStr, fullTableName);
+
+        Map<String, Integer> columnMetaDataMap = new HashMap<>();
+        try (Connection connection = dataSource.getConnection()) {
+            try (Statement stmt = connection.createStatement();
+                 ResultSet rs = stmt.executeQuery(sql);) {
+                ResultSetMetaData rsMetaData = rs.getMetaData();
+                for (int i = 0, len = rsMetaData.getColumnCount(); i < len; i++) {
+                    columnMetaDataMap.put(rsMetaData.getColumnName(i + 1), rsMetaData.getColumnType(i + 1));
+                }
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(
+                String.format("获取表:%s.%s 的字段的元信息时失败. 请联系 DBA 核查该库、表信息.",
+                    schemaName, tableName), e);
+        }
+        return columnMetaDataMap;
+    }
+
+    protected TransactionDefinition getDefaultTransactionDefinition() {
+        DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
+        definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
+        definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
+        return definition;
+    }
+}

+ 37 - 0
km-common/km-common-db/src/main/java/com/km/common/db/provider/AbstractFactoryProvider.java

@@ -0,0 +1,37 @@
+package com.km.common.db.provider;
+
+import com.km.common.db.Utils.ExamineUtils;
+import com.km.common.db.annotation.Product;
+import com.km.common.db.enums.DbProductTypeEnum;
+
+import javax.sql.DataSource;
+import java.util.Objects;
+
+/**
+ * @Description: AbstractFactoryProvider
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 14:26
+ */
+public abstract class AbstractFactoryProvider implements IProductFactoryProvider {
+
+    private DataSource dataSource;
+
+    protected AbstractFactoryProvider(DataSource dataSource) {
+        ExamineUtils.checkNotNull(dataSource, "datasource");
+        this.dataSource = dataSource;
+    }
+
+    public DataSource getDataSource() {
+        return this.dataSource;
+    }
+
+    @Override
+    public final DbProductTypeEnum getProductType() {
+        Product annotation = getClass().getAnnotation(Product.class);
+        ExamineUtils.checkState(
+            Objects.nonNull(annotation),
+            "Should use Product annotation for class : %s",
+            getClass().getName());
+        return annotation.value();
+    }
+}

+ 320 - 0
km-common/km-common-db/src/main/java/com/km/common/db/provider/AbstractMetadataProvider.java

@@ -0,0 +1,320 @@
+package com.km.common.db.provider;
+
+import cn.hutool.core.text.StrPool;
+import com.km.common.db.enums.DbProductTypeEnum;
+import com.km.common.db.enums.DbTableIndexEnum;
+import com.km.common.db.schema.ColumnDescription;
+import com.km.common.db.schema.ColumnMetaData;
+import com.km.common.db.schema.IndexDescription;
+import com.km.common.db.schema.IndexFieldMeta;
+import com.km.common.db.schema.TableDescription;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+/**
+ * @Description: AbstractMetadataProvider
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+@Slf4j
+public abstract class AbstractMetadataProvider extends AbstractCommonProvider implements IMetadataProvider {
+
+    protected String catalogName = null;
+
+    protected AbstractMetadataProvider(IProductFactoryProvider factoryProvider) {
+        super(factoryProvider);
+    }
+
+    public void setCatalogName(String catalogName) {
+        this.catalogName = catalogName;
+    }
+
+    @Override
+    public List<String> querySchemaList(Connection connection) {
+        Set<String> ret = new LinkedHashSet<>();
+        try (ResultSet schemas = connection.getMetaData().getSchemas()) {
+            while (schemas.next()) {
+                ret.add(schemas.getString("TABLE_SCHEM"));
+            }
+            return new ArrayList<>(ret);
+        } catch (SQLException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public List<TableDescription> queryTableList(Connection connection, String schemaName) {
+        List<TableDescription> ret = new ArrayList<>();
+        Set<String> uniqueSet = new LinkedHashSet<>();
+        String[] types = new String[]{"TABLE", "VIEW"};
+        try (ResultSet tables = connection.getMetaData()
+            .getTables(catalogName, schemaName, "%", types)) {
+            while (tables.next()) {
+                String tableName = tables.getString("TABLE_NAME");
+                if (uniqueSet.contains(tableName)) {
+                    continue;
+                } else {
+                    uniqueSet.add(tableName);
+                }
+
+                TableDescription td = new TableDescription();
+                td.setSchemaName(schemaName);
+                td.setTableName(tableName);
+                td.setRemarks(tables.getString("REMARKS"));
+                td.setTableType(tables.getString("TABLE_TYPE").toUpperCase());
+                ret.add(td);
+            }
+            return ret;
+        } catch (SQLException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public TableDescription queryTableMeta(Connection connection, String schemaName,
+                                           String tableName) {
+        try (ResultSet tables = connection.getMetaData()
+            .getTables(catalogName, schemaName, tableName, new String[]{"TABLE"})) {
+            if (tables.next()) {
+                TableDescription td = new TableDescription();
+                td.setSchemaName(schemaName);
+                td.setTableName(tableName);
+                td.setRemarks(tables.getString("REMARKS"));
+                td.setTableType(tables.getString("TABLE_TYPE").toUpperCase());
+                return td;
+            }
+            return null;
+        } catch (SQLException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public String getTableDDL(Connection connection, String schemaName, String tableName) {
+        return null;
+    }
+
+    @Override
+    public List<String> queryTableColumnName(Connection connection, String schemaName,
+                                             String tableName) {
+        Set<String> columns = new LinkedHashSet<>();
+        try (ResultSet rs = connection.getMetaData()
+            .getColumns(catalogName, schemaName, tableName, null)) {
+            while (rs.next()) {
+                columns.add(rs.getString("COLUMN_NAME"));
+            }
+            return new ArrayList<>(columns);
+        } catch (SQLException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public List<ColumnDescription> queryTableColumnMeta(Connection connection, String schemaName,
+                                                        String tableName) {
+        String sql = this.getTableFieldsQuerySQL(schemaName, tableName);
+        List<ColumnDescription> ret = this.querySelectSqlColumnMeta(connection, sql);
+
+        // 补充一下注释信息
+        try (ResultSet columns = connection.getMetaData()
+            .getColumns(catalogName, schemaName, tableName, null)) {
+            while (columns.next()) {
+                String columnName = columns.getString("COLUMN_NAME");
+                String remarks = columns.getString("REMARKS");
+                for (ColumnDescription cd : ret) {
+                    if (columnName.equals(cd.getFieldName())) {
+                        cd.setRemarks(remarks);
+                    }
+                }
+            }
+            return ret;
+        } catch (SQLException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public List<String> queryTablePrimaryKeys(Connection connection, String schemaName,
+                                              String tableName) {
+        Set<String> ret = new LinkedHashSet<>();
+        try (ResultSet primaryKeys = connection.getMetaData()
+            .getPrimaryKeys(catalogName, schemaName, tableName)) {
+            while (primaryKeys.next()) {
+                ret.add(primaryKeys.getString("COLUMN_NAME"));
+            }
+            return new ArrayList<>(ret);
+        } catch (SQLException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public List<IndexDescription> queryTableIndexes(Connection connection, String schemaName, String tableName) {
+        List<IndexDescription> ret = new ArrayList<>();
+        Map<String, List<IndexFieldMeta>> indexFieldsMap = new HashMap<>();
+        Map<String, DbTableIndexEnum> indexTypesMap = new HashMap<>();
+        try (ResultSet rs = connection.getMetaData()
+            .getIndexInfo(catalogName, schemaName, tableName, false, true)) {
+            while (rs.next()) {
+                String indexName = rs.getString("INDEX_NAME");
+                Boolean nonUnique = rs.getBoolean("NON_UNIQUE");
+                String orderMethod = rs.getString("ASC_OR_DESC");
+                String columnName = rs.getString("COLUMN_NAME");
+                Integer ordinalPosition = rs.getInt("ORDINAL_POSITION");
+
+                Boolean isAscOrder = Objects.isNull(orderMethod) ? null
+                    : ("A".equals(orderMethod) ? true : "D".equals(orderMethod) ? false : null);
+                IndexFieldMeta indexFieldMeta = new IndexFieldMeta(columnName, ordinalPosition, isAscOrder);
+                if (StringUtils.isNotBlank(indexName)) {
+                    indexTypesMap.putIfAbsent(indexName,
+                            nonUnique ? DbTableIndexEnum.NORMAL : DbTableIndexEnum.UNIQUE
+                    );
+                    indexFieldsMap.computeIfAbsent(indexName, key -> new ArrayList<>()).add(indexFieldMeta);
+                }
+            }
+            if (!indexTypesMap.isEmpty()) {
+                List<String> primaryKeys = queryTablePrimaryKeys(connection, schemaName, tableName);
+                for (Map.Entry<String, DbTableIndexEnum> entry : indexTypesMap.entrySet()) {
+                    String indexName = entry.getKey();
+                    DbTableIndexEnum typeEnum = entry.getValue();
+                    List<IndexFieldMeta> indexFields = indexFieldsMap.get(indexName);
+                    if (CollectionUtils.isNotEmpty(indexFields)) {
+                        List<String> fieldList = indexFields.stream()
+                            .map(IndexFieldMeta::getFieldName)
+                            .toList();
+                        if (primaryKeys.size() == indexFields.size() && new HashSet<>(primaryKeys).containsAll(fieldList)) {
+                            continue;
+                        }
+                        ret.add(new IndexDescription(typeEnum, indexName, indexFields));
+                    }
+                }
+            }
+            return ret;
+        } catch (SQLException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public String getQuotedSchemaTableCombination(String schemaName, String tableName) {
+        return getProductType().quoteSchemaTableName(schemaName, tableName);
+    }
+
+    @Override
+    public String getFieldDefinition(ColumnMetaData v, List<String> pks, boolean useAutoInc,
+                                     boolean addCr, boolean withRemarks) {
+        throw new RuntimeException("AbstractDatabase Unimplemented!");
+    }
+
+    @Override
+    public String getPrimaryKeyAsString(List<String> pks) {
+        if (!pks.isEmpty()) {
+            DbProductTypeEnum productType = getProductType();
+            return productType.quoteName(StringUtils.join(pks, productType.quoteName(StrPool.COMMA)));
+        }
+        return StringUtils.EMPTY;
+    }
+
+    @Override
+    public List<String> getTableColumnCommentDefinition(TableDescription td,
+                                                        List<ColumnDescription> cds) {
+        throw new RuntimeException("AbstractDatabase Unimplemented!");
+    }
+
+    /**
+     * 执行写SQL操作
+     *
+     * @param sql 写SQL语句
+     */
+    protected final int executeSql(String sql) {
+        if (log.isDebugEnabled()) {
+            log.debug("Execute sql :{}", sql);
+        }
+        try (Connection connection = getDataSource().getConnection();
+             Statement st = connection.createStatement()) {
+            return st.executeUpdate(sql);
+        } catch (SQLException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    protected ColumnDescription buildColumnDescription(ResultSetMetaData m, int i) throws SQLException {
+        String name = StringUtils.defaultString(m.getColumnLabel(i), m.getColumnName(i));
+        ColumnDescription cd = new ColumnDescription();
+        cd.setFieldName(name);
+        cd.setLabelName(name);
+        cd.setFieldType(m.getColumnType(i));
+        if (0 != cd.getFieldType()) {
+            cd.setFieldTypeName(m.getColumnTypeName(i));
+            cd.setFiledTypeClassName(m.getColumnClassName(i));
+            cd.setDisplaySize(m.getColumnDisplaySize(i));
+            cd.setPrecisionSize(m.getPrecision(i));
+            cd.setScaleSize(m.getScale(i));
+            cd.setAutoIncrement(m.isAutoIncrement(i));
+            cd.setNullable(m.isNullable(i) != ResultSetMetaData.columnNoNulls);
+        } else {
+            // 处理视图中NULL as fieldName的情况
+            cd.setFieldTypeName("CHAR");
+            cd.setFiledTypeClassName(String.class.getName());
+            cd.setDisplaySize(1);
+            cd.setPrecisionSize(1);
+            cd.setScaleSize(0);
+            cd.setAutoIncrement(false);
+            cd.setNullable(true);
+        }
+
+        boolean signed = false;
+        try {
+            signed = m.isSigned(i);
+        } catch (Exception ignored) {
+            // This JDBC Driver doesn't support the isSigned method
+            // nothing more we can do here by catch the exception.
+        }
+        cd.setSigned(signed);
+        return cd;
+    }
+
+    /**************************************
+     * internal function
+     **************************************/
+    protected List<ColumnDescription> getSelectSqlColumnMeta(Connection connection, String querySQL) {
+        return getSelectSqlColumnMeta(connection, querySQL, conn -> {});
+    }
+
+    protected List<ColumnDescription> getSelectSqlColumnMeta(Connection connection, String querySQL,
+                                                             Consumer<Connection> preQueryFunc) {
+        List<ColumnDescription> ret = new ArrayList<>();
+        try (Statement st = connection.createStatement()) {
+            preQueryFunc.accept(connection);
+            try (ResultSet rs = st.executeQuery(querySQL)) {
+                ResultSetMetaData m = rs.getMetaData();
+                int columns = m.getColumnCount();
+                for (int i = 1; i <= columns; i++) {
+                    ColumnDescription cd = buildColumnDescription(m, i);
+                    cd.setProductType(getProductType());
+                    ret.add(cd);
+                }
+                return ret;
+            }
+        } catch (SQLException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}

+ 38 - 0
km-common/km-common-db/src/main/java/com/km/common/db/provider/AutoCastTableDataSynchronizeProvider.java

@@ -0,0 +1,38 @@
+package com.km.common.db.provider;
+
+import com.km.common.db.Utils.ObjectCastUtils;
+
+import java.util.List;
+
+/**
+ * @Description: AutoCastTableDataSynchronizeProvider
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+public class AutoCastTableDataSynchronizeProvider extends DefaultTableDataSynchronizeProvider {
+
+    public AutoCastTableDataSynchronizeProvider(IProductFactoryProvider factoryProvider) {
+        super(factoryProvider);
+    }
+
+    @Override
+    public long executeInsert(List<Object[]> records) {
+        records.forEach((Object[] row) -> {
+            for (int i = 0; i < row.length; ++i) {
+                row[i] = ObjectCastUtils.castByJdbcType(insertArgsType[i], row[i]);
+            }
+        });
+        return super.executeInsert(records);
+    }
+
+    @Override
+    public long executeUpdate(List<Object[]> records) {
+        records.forEach((Object[] row) -> {
+            for (int i = 0; i < row.length; ++i) {
+                //row[i] = ObjectCastUtils.castByJdbcType(updateArgsType[i], row[i]);
+                row[i] = ObjectCastUtils.castByDetermine(row[i]);
+            }
+        });
+        return super.executeUpdate(records);
+    }
+}

+ 51 - 0
km-common/km-common-db/src/main/java/com/km/common/db/provider/ColumnValueDataMapTable.java

@@ -0,0 +1,51 @@
+package com.km.common.db.provider;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @Description: IColumnValueDataMapTable
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 14:37
+ */
+public class ColumnValueDataMapTable {
+    private Map<SchemaTableColumnTuple, Map<String, String>> valueMap = new HashMap<>();
+
+    public ColumnValueDataMapTable() {
+    }
+
+    public ColumnValueDataMapTable(SchemaTableColumnTuple tuple, Map<String, String> values) {
+        valueMap.clear();
+        valueMap.put(tuple, values);
+    }
+
+    private SchemaTableColumnTuple buildTuple(String schema, String table, String column) {
+        return SchemaTableColumnTuple
+            .builder()
+            .schema(schema)
+            .table(table)
+            .column(column)
+            .build();
+    }
+
+    public boolean isEmpty() {
+        return valueMap.isEmpty();
+    }
+
+    public void put(SchemaTableColumnTuple tuple, Map<String, String> values) {
+        valueMap.put(tuple, values);
+    }
+
+    public boolean contains(String schema, String table, String column) {
+        return valueMap.containsKey(buildTuple(schema, table, column));
+    }
+
+    public Map<String, String> get(String schema, String table, String column) {
+        Map<String, String> map = valueMap.get(buildTuple(schema, table, column));
+        if (null == map) {
+            map = Collections.emptyMap();
+        }
+        return map;
+    }
+}

+ 138 - 0
km-common/km-common-db/src/main/java/com/km/common/db/provider/DefaultTableDataQueryProvider.java

@@ -0,0 +1,138 @@
+package com.km.common.db.provider;
+
+import cn.hutool.core.util.HexUtil;
+import com.km.common.db.Utils.ObjectCastUtils;
+import com.km.common.db.constant.DbConstants;
+import com.km.common.db.entity.ResultSetWrapper;
+import com.km.common.db.enums.DbProductTypeEnum;
+import com.km.common.db.schema.SchemaTableData;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @Description: DefaultTableDataQueryProvider
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 14:25
+ */
+
+@Slf4j
+public class DefaultTableDataQueryProvider extends AbstractCommonProvider
+    implements ITableDataQueryProvider {
+
+    private final int resultSetType = ResultSet.TYPE_FORWARD_ONLY;
+    private final int concurrency = ResultSet.CONCUR_READ_ONLY;
+
+    private int fetchSize = DbConstants.DEFAULT_FETCH_SIZE;
+
+    public DefaultTableDataQueryProvider(IProductFactoryProvider factoryProvider) {
+        super(factoryProvider);
+    }
+
+    @Override
+    public int getQueryFetchSize() {
+        return this.fetchSize;
+    }
+
+    @Override
+    public void setQueryFetchSize(int size) {
+        if (size < DbConstants.MINIMUM_FETCH_SIZE) {
+            throw new IllegalArgumentException(
+                "设置的批量处理行数的大小fetchSize=" + size + "不得小于" + DbConstants.MINIMUM_FETCH_SIZE);
+        }
+        this.fetchSize = size;
+    }
+
+    @Override
+    public ResultSetWrapper queryTableData(String schemaName, String tableName, List<String> fields,
+                                           List<String> orders) {
+        DbProductTypeEnum productType = getProductType();
+        StringBuilder sb = new StringBuilder("SELECT ");
+        sb.append(productType.quoteName(StringUtils.join(fields, productType.quoteName(","))));
+        sb.append(" FROM ");
+        sb.append(productType.quoteSchemaTableName(schemaName, tableName));
+        if (CollectionUtils.isNotEmpty(orders)) {
+            sb.append(" ORDER BY ");
+            sb.append(productType.quoteName(StringUtils.join(orders, productType.quoteName(","))));
+        }
+        return this.selectTableData(sb.toString(), getProductFeatures().convertFetchSize(this.fetchSize));
+    }
+
+    protected ResultSetWrapper selectTableData(String sql, int fetchSize) {
+        if (log.isDebugEnabled()) {
+            log.debug("Query table data sql :{}", sql);
+        }
+
+        try {
+            Connection connection = getDataSource().getConnection();
+            Statement statement = connection.createStatement(resultSetType, concurrency);
+            statement.setQueryTimeout(DbConstants.DEFAULT_QUERY_TIMEOUT_SECONDS);
+            statement.setFetchSize(fetchSize);
+            return ResultSetWrapper.builder()
+                .connection(connection)
+                .statement(statement)
+                .resultSet(statement.executeQuery(sql))
+                .build();
+        } catch (Throwable t) {
+            throw new RuntimeException(t);
+        }
+    }
+
+    @Override
+    public SchemaTableData queryTableData(Connection connection, String schemaName, String tableName,
+                                          int rowCount) {
+        DbProductTypeEnum productType = getProductType();
+        String fullTableName = productType.quoteSchemaTableName(schemaName, tableName);
+        String querySql = String.format("SELECT * FROM %s ", fullTableName);
+        SchemaTableData data = new SchemaTableData();
+        data.setSchemaName(schemaName);
+        data.setTableName(tableName);
+        data.setColumns(new ArrayList<>());
+        data.setRows(new ArrayList<>());
+        try (Statement st = connection.createStatement()) {
+            beforeExecuteQuery(connection, schemaName, tableName);
+            try (ResultSet rs = st.executeQuery(querySql)) {
+                ResultSetMetaData m = rs.getMetaData();
+                int count = m.getColumnCount();
+                for (int i = 1; i <= count; i++) {
+                    data.getColumns().add(m.getColumnLabel(i));
+                }
+
+                int counter = 0;
+                while (rs.next() && counter++ < rowCount) {
+                    List<Object> row = new ArrayList<>(count);
+                    for (int i = 1; i <= count; i++) {
+                        Object value = rs.getObject(i);
+                        if (value instanceof byte[]) {
+                            row.add(HexUtil.encodeHexStr((byte[]) value));
+                        } else if (value instanceof java.sql.Clob) {
+                            row.add(ObjectCastUtils.castToString(value));
+                        } else if (value instanceof java.sql.Blob) {
+                            byte[] bytes = ObjectCastUtils.castToByteArray(value);
+                            row.add(HexUtil.encodeHexStr(bytes));
+                        } else {
+                            row.add(null == value ? null : value.toString());
+                        }
+                    }
+                    data.getRows().add(row);
+                }
+
+                return data;
+            }
+        } catch (SQLException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    protected void beforeExecuteQuery(Connection connection, String schema, String table) {
+        // nothing except for hive
+    }
+}

+ 267 - 0
km-common/km-common-db/src/main/java/com/km/common/db/provider/DefaultTableDataSynchronizeProvider.java

@@ -0,0 +1,267 @@
+package com.km.common.db.provider;
+
+import cn.hutool.json.JSONUtil;
+import com.km.common.db.enums.DbProductTypeEnum;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.DataSourceTransactionManager;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionStatus;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @Description: 数据同步抽象基类
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 14:45
+ */
+@Slf4j
+public class DefaultTableDataSynchronizeProvider extends AbstractCommonProvider implements ITableDataSynchronizeProvider{
+
+    private JdbcTemplate jdbcTemplate;
+    private PlatformTransactionManager tx;
+    private Map<String, Integer> columnType;
+    protected List<String> fieldOrders;
+    protected List<String> pksOrders;
+    protected String insertStatementSql;
+    protected String updateStatementSql;
+    protected String deleteStatementSql;
+    protected int[] insertArgsType;
+    protected int[] updateArgsType;
+    protected int[] deleteArgsType;
+
+    public DefaultTableDataSynchronizeProvider(IProductFactoryProvider factoryProvider) {
+        super(factoryProvider);
+        this.jdbcTemplate = new JdbcTemplate(factoryProvider.getDataSource());
+        this.tx = new DataSourceTransactionManager(factoryProvider.getDataSource());
+        this.columnType = new HashMap<>();
+    }
+
+    @Override
+    public void prepare(String schemaName, String tableName, List<String> fieldNames, List<String> pks) {
+        if (fieldNames.isEmpty() || pks.isEmpty() || fieldNames.size() < pks.size()) {
+            throw new IllegalArgumentException("字段列表和主键列表不能为空,或者字段总个数应不小于主键总个数");
+        }
+        if (!fieldNames.containsAll(pks)) {
+            throw new IllegalArgumentException("字段列表必须包含主键列表");
+        }
+
+        this.columnType = getTableColumnMetaData(schemaName, tableName, fieldNames);
+        this.fieldOrders = new ArrayList<>(fieldNames);
+        this.pksOrders = new ArrayList<>(pks);
+
+        this.insertStatementSql = getInsertPrepareStatementSql(schemaName, tableName, fieldNames);
+        this.updateStatementSql = getUpdatePrepareStatementSql(schemaName, tableName, fieldNames, pks);
+        this.deleteStatementSql = getDeletePrepareStatementSql(schemaName, tableName, pks);
+
+        insertArgsType = new int[fieldNames.size()];
+        for (int k = 0; k < fieldNames.size(); ++k) {
+            String field = fieldNames.get(k);
+            insertArgsType[k] = this.columnType.get(field);
+        }
+
+        updateArgsType = new int[fieldNames.size()];
+        int idx = 0;
+        for (String field : fieldNames) {
+            if (!pks.contains(field)) {
+                updateArgsType[idx++] = this.columnType.get(field);
+            }
+        }
+        for (String pk : pks) {
+            updateArgsType[idx++] = this.columnType.get(pk);
+        }
+
+        deleteArgsType = new int[pks.size()];
+        for (int j = 0; j < pks.size(); ++j) {
+            String pk = pks.get(j);
+            deleteArgsType[j] = this.columnType.get(pk);
+        }
+    }
+
+    @Override
+    public long executeInsert(List<Object[]> records) {
+        TransactionStatus status = tx.getTransaction(getDefaultTransactionDefinition());
+        if (log.isDebugEnabled()) {
+            log.debug("Execute Insert SQL : {}", this.insertStatementSql);
+        }
+
+        try {
+            try {
+                jdbcTemplate.batchUpdate(this.insertStatementSql, records, this.insertArgsType);
+            } catch (Exception e) {
+                if (e instanceof java.sql.BatchUpdateException) {
+                    for (Object[] dataList : records) {
+                        try {
+                            jdbcTemplate.update(this.insertStatementSql, dataList, this.updateArgsType);
+                        } catch (Exception ex) {
+                            log.error("Failed to insert by SQL: {}, value: {}", this.insertStatementSql,
+                                JSONUtil.toJsonStr(dataList));
+                            throw ex;
+                        }
+                    }
+                } else {
+                    throw e;
+                }
+            }
+
+            tx.commit(status);
+            return records.size();
+        } catch (Exception e) {
+            tx.rollback(status);
+            throw e;
+        }
+    }
+
+    @Override
+    public long executeUpdate(List<Object[]> records) {
+        List<Object[]> dataLists = new LinkedList<>();
+        for (Object[] r : records) {
+            Object[] nr = new Object[this.fieldOrders.size()];
+            int idx = 0;
+            for (int i = 0; i < this.fieldOrders.size(); ++i) {
+                String field = this.fieldOrders.get(i);
+                if (!this.pksOrders.contains(field)) {
+                    int index = this.fieldOrders.indexOf(field);
+                    nr[idx++] = r[index];
+                }
+            }
+            for (int j = 0; j < this.pksOrders.size(); ++j) {
+                String pk = this.pksOrders.get(j);
+                int index = this.fieldOrders.indexOf(pk);
+                nr[idx++] = r[index];
+            }
+            dataLists.add(nr);
+        }
+
+        TransactionStatus status = tx.getTransaction(getDefaultTransactionDefinition());
+        if (log.isDebugEnabled()) {
+            log.debug("Execute Update SQL : {}", this.updateStatementSql);
+        }
+
+        try {
+            try {
+                jdbcTemplate.batchUpdate(this.updateStatementSql, dataLists, this.updateArgsType);
+            } catch (Exception e) {
+                if (e instanceof java.sql.BatchUpdateException) {
+                    for (Object[] dataList : dataLists) {
+                        try {
+                            jdbcTemplate.update(this.updateStatementSql, dataList, this.updateArgsType);
+                        } catch (Exception ex) {
+                            log.error("Failed to update by SQL: {}, value: {}", this.updateStatementSql,
+                                JSONUtil.toJsonStr(dataList));
+                            throw ex;
+                        }
+                    }
+                } else {
+                    throw e;
+                }
+            }
+
+            tx.commit(status);
+            return dataLists.size();
+        } catch (Exception e) {
+            tx.rollback(status);
+            throw e;
+        }
+    }
+
+    @Override
+    public long executeDelete(List<Object[]> records) {
+        List<Object[]> dataLists = new LinkedList<>();
+        for (Object[] r : records) {
+            Object[] nr = new Object[this.pksOrders.size()];
+            for (int i = 0; i < this.pksOrders.size(); ++i) {
+                String pk = this.pksOrders.get(i);
+                int index = this.fieldOrders.indexOf(pk);
+                nr[i] = r[index];
+            }
+            dataLists.add(nr);
+        }
+
+        TransactionStatus status = tx.getTransaction(getDefaultTransactionDefinition());
+        if (log.isDebugEnabled()) {
+            log.debug("Execute Delete SQL : {}", this.deleteStatementSql);
+        }
+
+        try {
+            jdbcTemplate.batchUpdate(this.deleteStatementSql, dataLists, this.deleteArgsType);
+            tx.commit(status);
+            return dataLists.size();
+        } catch (Exception e) {
+            tx.rollback(status);
+            throw e;
+        } finally {
+            dataLists.clear();
+        }
+    }
+
+    /**
+     * 生成Insert操作的SQL语句
+     *
+     * @param schemaName 模式名称
+     * @param tableName  表名称
+     * @param fieldNames 字段列表
+     * @return Insert操作的SQL语句
+     */
+    protected String getInsertPrepareStatementSql(String schemaName, String tableName,
+                                                  List<String> fieldNames) {
+        List<String> placeHolders = Collections.nCopies(fieldNames.size(), "?");
+        DbProductTypeEnum productType = getProductType();
+        String fullTableName = productType.quoteSchemaTableName(schemaName, tableName);
+        return String.format("INSERT INTO %s ( %s ) VALUES ( %s )",
+            fullTableName,
+            productType.quoteName(StringUtils.join(fieldNames, productType.quoteName(","))),
+            StringUtils.join(placeHolders, ","));
+    }
+
+    /**
+     * 生成Update操作的SQL语句
+     *
+     * @param schemaName 模式名称
+     * @param tableName  表名称
+     * @param fieldNames 字段列表
+     * @param pks        主键列表
+     * @return Update操作的SQL语句
+     */
+    protected String getUpdatePrepareStatementSql(String schemaName, String tableName,
+                                                  List<String> fieldNames, List<String> pks) {
+        DbProductTypeEnum productType = getProductType();
+        String fullTableName = productType.quoteSchemaTableName(schemaName, tableName);
+        List<String> uf = fieldNames.stream()
+            .filter(field -> !pks.contains(field))
+            .map(field -> String.format("%s=?", productType.quoteName(field)))
+            .collect(Collectors.toList());
+        List<String> uw = pks.stream()
+            .map(pk -> String.format("%s=?", productType.quoteName(pk)))
+            .collect(Collectors.toList());
+        return String.format("UPDATE %s SET %s WHERE %s",
+            fullTableName, StringUtils.join(uf, " , "),
+            StringUtils.join(uw, " AND "));
+    }
+
+    /**
+     * 生成Delete操作的SQL语句
+     *
+     * @param schemaName 模式名称
+     * @param tableName  表名称
+     * @param pks        主键列表
+     * @return Delete操作的SQL语句
+     */
+    protected String getDeletePrepareStatementSql(String schemaName, String tableName,
+                                                  List<String> pks) {
+        DbProductTypeEnum productType = getProductType();
+        String fullTableName = productType.quoteSchemaTableName(schemaName, tableName);
+        List<String> uw = pks.stream()
+            .map(pk -> String.format("%s=?", productType.quoteName(pk)))
+            .collect(Collectors.toList());
+        return String.format("DELETE FROM %s WHERE %s ",
+            fullTableName, StringUtils.join(uw, "  AND  "));
+    }
+}

+ 86 - 0
km-common/km-common-db/src/main/java/com/km/common/db/provider/DefaultTableDataWriteProvider.java

@@ -0,0 +1,86 @@
+package com.km.common.db.provider;
+
+import com.km.common.db.enums.DbProductTypeEnum;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.DataSourceTransactionManager;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionStatus;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @Description: DefaultTableDataWriteProvider
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 14:40
+ */
+@Slf4j
+public class DefaultTableDataWriteProvider extends AbstractCommonProvider
+    implements ITableDataWriteProvider{
+
+    protected JdbcTemplate jdbcTemplate;
+    protected String schemaName;
+    protected String tableName;
+    protected Map<String, Integer> columnType;
+
+    public DefaultTableDataWriteProvider(IProductFactoryProvider factoryProvider) {
+        super(factoryProvider);
+        this.jdbcTemplate = new JdbcTemplate(factoryProvider.getDataSource());
+        this.schemaName = null;
+        this.tableName = null;
+        this.columnType = null;
+    }
+
+    protected String getPrepareInsertTableSql(List<String> fieldNames) {
+        DbProductTypeEnum productType = getProductType();
+        String fullTableName = productType.quoteSchemaTableName(schemaName, tableName);
+        return String.format("INSERT INTO %s ( %s ) VALUES ( %s )",
+            fullTableName,
+            productType.quoteName(StringUtils.join(fieldNames, productType.quoteName(","))),
+            StringUtils.join(Collections.nCopies(fieldNames.size(), "?"), ","));
+    }
+
+    @Override
+    public void prepareWrite(String schemaName, String tableName, List<String> fieldNames) {
+        this.columnType = getTableColumnMetaData(schemaName, tableName, fieldNames);
+        if (this.columnType.isEmpty()) {
+            throw new RuntimeException(
+                String.format("获取表:%s.%s 的字段的元信息时失败. 请联系 DBA 核查该库、表信息.",
+                    schemaName, tableName));
+        }
+        this.schemaName = schemaName;
+        this.tableName = tableName;
+    }
+
+    @Override
+    public long write(List<String> fieldNames, List<Object[]> recordValues) {
+        if (recordValues.isEmpty()) {
+            return 0;
+        }
+        String sqlInsert = getPrepareInsertTableSql(fieldNames);
+        int[] argTypes = new int[fieldNames.size()];
+        for (int i = 0; i < fieldNames.size(); ++i) {
+            String col = fieldNames.get(i);
+            argTypes[i] = this.columnType.get(col);
+        }
+
+        PlatformTransactionManager tx = new DataSourceTransactionManager(getDataSource());
+        TransactionStatus status = tx.getTransaction(getDefaultTransactionDefinition());
+        try {
+            jdbcTemplate.batchUpdate(sqlInsert, recordValues, argTypes);
+            tx.commit(status);
+            int affectCount = recordValues.size();
+            recordValues.clear();
+            if (log.isDebugEnabled()) {
+                log.debug("{} insert data affect count: {}", getProductType(), affectCount);
+            }
+            return affectCount;
+        } catch (Exception e) {
+            tx.rollback(status);
+            throw e;
+        }
+    }
+}

+ 47 - 0
km-common/km-common-db/src/main/java/com/km/common/db/provider/DefaultTableManageProvider.java

@@ -0,0 +1,47 @@
+package com.km.common.db.provider;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+/**
+ * @Description: DefaultTableManageProvider
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 14:42
+ */
+@Slf4j
+public class DefaultTableManageProvider extends AbstractCommonProvider
+    implements ITableManageProvider{
+
+    public DefaultTableManageProvider(IProductFactoryProvider factoryProvider) {
+        super(factoryProvider);
+    }
+
+    protected final int executeSql(String sql) {
+        if (log.isDebugEnabled()) {
+            log.debug("Execute sql :{}", sql);
+        }
+        try (Connection connection = getDataSource().getConnection();
+             Statement st = connection.createStatement()) {
+            return st.executeUpdate(sql);
+        } catch (SQLException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void truncateTableData(String schemaName, String tableName) {
+        String fullTableName = getProductType().quoteSchemaTableName(schemaName, tableName);
+        String sql = String.format("TRUNCATE TABLE %s ", fullTableName);
+        this.executeSql(sql);
+    }
+
+    @Override
+    public void dropTable(String schemaName, String tableName) {
+        String fullTableName = getProductType().quoteSchemaTableName(schemaName, tableName);
+        String sql = String.format("DROP TABLE %s ", fullTableName);
+        this.executeSql(sql);
+    }
+}

+ 31 - 0
km-common/km-common-db/src/main/java/com/km/common/db/provider/IDefaultTransformProvider.java

@@ -0,0 +1,31 @@
+package com.km.common.db.provider;
+
+import java.util.List;
+
+/**
+ * @Description: IDefaultTransformProvider
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 14:35
+ */
+public class IDefaultTransformProvider implements IRecordTransformProvider{
+
+    private IProductFactoryProvider factoryProvider;
+
+    public IDefaultTransformProvider(IProductFactoryProvider factoryProvider) {
+        this.factoryProvider = factoryProvider;
+    }
+
+    protected IProductFactoryProvider getFactoryProvider() {
+        return this.factoryProvider;
+    }
+
+    @Override
+    public String getTransformerName() {
+        return this.getClass().getSimpleName();
+    }
+
+    @Override
+    public Object[] doTransform(String schema, String table, List<String> fieldNames, Object[] recordValue) {
+        return recordValue;
+    }
+}

+ 170 - 0
km-common/km-common-db/src/main/java/com/km/common/db/provider/IMetadataProvider.java

@@ -0,0 +1,170 @@
+package com.km.common.db.provider;
+
+import com.km.common.db.enums.DbProductTypeEnum;
+import com.km.common.db.schema.ColumnDescription;
+import com.km.common.db.schema.ColumnMetaData;
+import com.km.common.db.schema.IndexDescription;
+import com.km.common.db.schema.TableDescription;
+
+import java.sql.Connection;
+import java.util.List;
+
+/**
+ * @Description: IMetadataProvider元数据接口
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 13:45
+ */
+public interface IMetadataProvider {
+
+    /**
+     * 获取数据库类型
+     *
+     * @return ProductTypeEnum
+     */
+    DbProductTypeEnum getProductType();
+
+    /**
+     * 获取数据库的模式schema列表
+     *
+     * @param connection JDBC连接
+     * @return 模式名列表
+     */
+    List<String> querySchemaList(Connection connection);
+
+    /**
+     * 获取指定模式Schema内的所有表列表
+     *
+     * @param connection JDBC连接
+     * @param schemaName 模式名称
+     * @return 表及视图名列表
+     */
+    List<TableDescription> queryTableList(Connection connection, String schemaName);
+
+    /**
+     * 精确获取表或视图的元数据
+     *
+     * @param connection JDBC连接
+     * @param schemaName 模式名称
+     * @param tableName  表或视图名称
+     * @return TableDescription
+     */
+    TableDescription queryTableMeta(Connection connection, String schemaName, String tableName);
+
+    /**
+     * 获取指定物理表的DDL语句
+     *
+     * @param connection JDBC连接
+     * @param schemaName 模式名称
+     * @param tableName  表名称
+     * @return 字段元信息列表
+     */
+    String getTableDDL(Connection connection, String schemaName, String tableName);
+
+    /**
+     * 获取指定视图表的DDL语句
+     *
+     * @param connection JDBC连接
+     * @param schemaName 模式名称
+     * @param tableName  表或视图名称
+     * @return 字段元信息列表
+     */
+    String getViewDDL(Connection connection, String schemaName, String tableName);
+
+    /**
+     * 获取指定模式表的字段列表
+     *
+     * @param connection JDBC连接
+     * @param schemaName 模式名称
+     * @param tableName  表或视图名称
+     * @return 字段元信息列表
+     */
+    List<String> queryTableColumnName(Connection connection, String schemaName,
+                                      String tableName);
+
+    /**
+     * 获取指定模式表的元信息
+     *
+     * @param connection JDBC连接
+     * @param schemaName 模式名称
+     * @param tableName  表或视图名称
+     * @return 字段元信息列表
+     */
+    List<ColumnDescription> queryTableColumnMeta(Connection connection, String schemaName,
+                                                 String tableName);
+
+    /**
+     * 获取指定查询SQL的元信息
+     *
+     * @param connection JDBC连接
+     * @param sql        SQL查询语句
+     * @return 字段元信息列表
+     */
+    List<ColumnDescription> querySelectSqlColumnMeta(Connection connection, String sql);
+
+    /**
+     * 获取指定模式表的主键字段列表
+     *
+     * @param connection JDBC连接
+     * @param schemaName 模式名称
+     * @param tableName  表名称
+     * @return 主键字段名称列表
+     */
+    List<String> queryTablePrimaryKeys(Connection connection, String schemaName, String tableName);
+
+    /**
+     * 获取指定模式表的索引列表
+     *
+     * @param connection JDBC连接
+     * @param schemaName 模式名称
+     * @param tableName  表名称
+     * @return 主键字段名称列表
+     */
+    List<IndexDescription> queryTableIndexes(Connection connection, String schemaName, String tableName);
+
+    /**
+     * 测试查询SQL语句的有效性
+     *
+     * @param connection JDBC连接
+     * @param sql        待验证的SQL语句
+     */
+    void testQuerySQL(Connection connection, String sql);
+
+    /**
+     * 获取数据库的表全名
+     *
+     * @param schemaName 模式名称
+     * @param tableName  表名称
+     * @return 表全名
+     */
+    String getQuotedSchemaTableCombination(String schemaName, String tableName);
+
+    /**
+     * 获取字段列的结构定义
+     *
+     * @param v           值元数据定义
+     * @param pks         主键字段名称列表
+     * @param addCr       是否结尾换行
+     * @param useAutoInc  是否自增
+     * @param withRemarks 是否带有注释
+     * @return 字段定义字符串
+     */
+    String getFieldDefinition(ColumnMetaData v, List<String> pks, boolean useAutoInc, boolean addCr,
+                              boolean withRemarks);
+
+    /**
+     * 主键列转换为逗号分隔的字符串
+     *
+     * @param pks 主键字段列表
+     * @return 主键字段拼接串
+     */
+    String getPrimaryKeyAsString(List<String> pks);
+
+    /**
+     * 获取表和字段的注释定义
+     *
+     * @param td  表信息定义
+     * @param cds 列信息定义
+     * @return 定义字符串列表
+     */
+    List<String> getTableColumnCommentDefinition(TableDescription td, List<ColumnDescription> cds);
+}

+ 88 - 0
km-common/km-common-db/src/main/java/com/km/common/db/provider/IProductFactoryProvider.java

@@ -0,0 +1,88 @@
+package com.km.common.db.provider;
+
+import com.km.common.db.enums.DbProductTypeEnum;
+import com.km.common.db.service.IMetadataService;
+
+import javax.sql.DataSource;
+
+/**
+ * @Description: IProductFactoryProvider
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 13:50
+ */
+public interface IProductFactoryProvider {
+
+    /**
+     * 获取数据库类型
+     *
+     * @return ProductTypeEnum
+     */
+    DbProductTypeEnum getProductType();
+
+    /**
+     * 获取数据源
+     *
+     * @return DataSource
+     */
+    DataSource getDataSource();
+
+    /**
+     * 获取数据库特征
+     *
+     * @return ProductFeatures
+     */
+    IProductFeatures getProductFeatures();
+
+    /**
+     * 获取元数据查询Provider
+     *
+     * @return MetadataQueryProvider
+     */
+    IMetadataProvider createMetadataQueryProvider();
+
+    /**
+     * 获取表数据查询Provider
+     *
+     * @return TableDataQueryProvider
+     */
+    default ITableDataQueryProvider createTableDataQueryProvider() {
+        return new DefaultTableDataQueryProvider(this);
+    }
+
+    /**
+     * 获取记录转换Provider
+     *
+     * @return RecordTransformProvider
+     */
+    default IRecordTransformProvider createRecordTransformProvider() {
+        return new MappedTransformProvider(this);
+    }
+
+    /**
+     * 获取表批量写入Provider
+     *
+     * @param useInsert 是否使用insert写入(只对PG有效)
+     * @return TableWriteProvider
+     */
+    default ITableDataWriteProvider createTableDataWriteProvider(boolean useInsert) {
+        return new DefaultTableDataWriteProvider(this);
+    }
+
+    /**
+     * 获取表操作Provider
+     *
+     * @return TableManageProvider
+     */
+    default ITableManageProvider createTableManageProvider() {
+        return new DefaultTableManageProvider(this);
+    }
+
+    /**
+     * 获取数据同步Provider
+     *
+     * @return TableDataSynchronizeProvider
+     */
+    default ITableDataSynchronizeProvider createTableDataSynchronizeProvider() {
+        return new DefaultTableDataSynchronizeProvider(this);
+    }
+}

+ 19 - 0
km-common/km-common-db/src/main/java/com/km/common/db/provider/IProductFeatures.java

@@ -0,0 +1,19 @@
+package com.km.common.db.provider;
+
+/**
+ * @Description: 数据库特征定义
+ *
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 13:52
+ */
+public interface IProductFeatures {
+    /**
+     * FetchSize转换
+     *
+     * @param fetchSize fetchSize
+     * @return 转换后的fetchSize
+     */
+    default int convertFetchSize(int fetchSize) {
+        return fetchSize;
+    }
+}

+ 16 - 0
km-common/km-common-db/src/main/java/com/km/common/db/provider/IRecordTransformProvider.java

@@ -0,0 +1,16 @@
+package com.km.common.db.provider;
+
+import java.util.List;
+
+/**
+ * @Description: IRecordTransformProvider
+ *
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 14:33
+ */
+public interface IRecordTransformProvider {
+    String getTransformerName();
+
+    Object[] doTransform(String schema, String table, List<String> fieldNames, Object[] recordValue);
+
+}

+ 74 - 0
km-common/km-common-db/src/main/java/com/km/common/db/provider/ITableDataQueryProvider.java

@@ -0,0 +1,74 @@
+package com.km.common.db.provider;
+
+import com.km.common.db.entity.ResultSetWrapper;
+import com.km.common.db.enums.DbProductTypeEnum;
+import com.km.common.db.schema.SchemaTableData;
+
+import java.sql.Connection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @Description: 表数据查询接口定义
+ *
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 13:54
+ */
+public interface ITableDataQueryProvider {
+    /**
+     * 获取数据库类型
+     *
+     * @return ProductTypeEnum
+     */
+    DbProductTypeEnum getProductType();
+
+    /**
+     * 获取读取(fetch)数据的批次大小
+     *
+     * @return 批次大小
+     */
+    int getQueryFetchSize();
+
+    /**
+     * 设置读取(fetch)数据的批次大小
+     *
+     * @param size 批次大小
+     */
+    void setQueryFetchSize(int size);
+
+    /**
+     * 获取指定schema下表的结果集
+     *
+     * @param schemaName 模式名称
+     * @param tableName  表名称
+     * @param fields     字段列表
+     * @return 结果集包装对象
+     */
+    default ResultSetWrapper queryTableData(String schemaName, String tableName, List<String> fields) {
+        return queryTableData(schemaName, tableName, fields, Collections.emptyList());
+    }
+
+    /**
+     * 获取指定schema下表的按主键有序的结果集
+     *
+     * @param schemaName 模式名称
+     * @param tableName  表名称
+     * @param fields     字段列表
+     * @param orders     排序字段列表
+     * @return 结果集包装对象
+     */
+    ResultSetWrapper queryTableData(String schemaName, String tableName, List<String> fields,
+                                    List<String> orders);
+
+    /**
+     * 获取指定模式表内的数据
+     *
+     * @param connection JDBC连接
+     * @param schemaName 模式名称
+     * @param tableName  表名称
+     * @param rowCount   记录的行数
+     * @return 数据内容
+     */
+    SchemaTableData queryTableData(Connection connection, String schemaName, String tableName, int rowCount);
+
+}

+ 62 - 0
km-common/km-common-db/src/main/java/com/km/common/db/provider/ITableDataSynchronizeProvider.java

@@ -0,0 +1,62 @@
+package com.km.common.db.provider;
+
+import com.km.common.db.enums.DbProductTypeEnum;
+
+import javax.sql.DataSource;
+import java.util.List;
+
+/**
+ * @Description: 数据同步接口定义
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 14:44
+ */
+public interface ITableDataSynchronizeProvider {
+
+    /**
+     * 获取数据库类型
+     *
+     * @return 数据库类型
+     */
+    DbProductTypeEnum getProductType();
+
+    /**
+     * 批量Insert/Update/Delete预处理
+     *
+     * @param schemaName schema名称
+     * @param tableName  table名称
+     * @param fieldNames 字段列表
+     * @param pks        主键字段列表
+     */
+    void prepare(String schemaName, String tableName, List<String> fieldNames, List<String> pks);
+
+    /**
+     * 获取数据源对象
+     *
+     * @return DataSource数据源对象
+     */
+    DataSource getDataSource();
+
+    /**
+     * 批量数据Update
+     *
+     * @param records 数据记录
+     * @return 返回实际影响的记录条数
+     */
+    long executeUpdate(List<Object[]> records);
+
+    /**
+     * 批量数据Insert
+     *
+     * @param records 数据记录
+     * @return 返回实际影响的记录条数
+     */
+    long executeInsert(List<Object[]> records);
+
+    /**
+     * 批量数据Delete
+     *
+     * @param records 数据记录
+     * @return 返回实际影响的记录条数
+     */
+    long executeDelete(List<Object[]> records);
+}

+ 27 - 0
km-common/km-common-db/src/main/java/com/km/common/db/provider/ITableDataWriteProvider.java

@@ -0,0 +1,27 @@
+package com.km.common.db.provider;
+
+import java.util.List;
+
+/**
+ * @Description: ITableDataWriteProvider
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 14:40
+ */
+public interface ITableDataWriteProvider {
+    /**
+     * 批量写入预处理
+     *
+     * @param schemaName schema名称
+     * @param tableName  table名称
+     */
+    void prepareWrite(String schemaName, String tableName, List<String> fieldNames);
+
+    /**
+     * 批量数据写入
+     *
+     * @param fieldNames   字段名称列表
+     * @param recordValues 数据记录
+     * @return 返回实际写入的数据记录条数
+     */
+    long write(List<String> fieldNames, List<Object[]> recordValues);
+}

+ 25 - 0
km-common/km-common-db/src/main/java/com/km/common/db/provider/ITableManageProvider.java

@@ -0,0 +1,25 @@
+package com.km.common.db.provider;
+
+/**
+ * @Description: ITableManageProvider
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 14:42
+ */
+public interface ITableManageProvider {
+
+    /**
+     * 清除指定表的所有数据
+     *
+     * @param schemaName 模式名称
+     * @param tableName  表名称
+     */
+    void truncateTableData(String schemaName, String tableName);
+
+    /**
+     * 删除指定物理表
+     *
+     * @param schemaName 模式名称
+     * @param tableName  表名称
+     */
+    void dropTable(String schemaName, String tableName);
+}

+ 46 - 0
km-common/km-common-db/src/main/java/com/km/common/db/provider/MappedTransformProvider.java

@@ -0,0 +1,46 @@
+package com.km.common.db.provider;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * @Description: MappedTransformProvider
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 14:34
+ */
+public class MappedTransformProvider extends IDefaultTransformProvider {
+
+    private ColumnValueDataMapTable valueDataMap;
+
+    public MappedTransformProvider(IProductFactoryProvider factoryProvider) {
+        super(factoryProvider);
+        this.valueDataMap = new ColumnValueDataMapTable();
+    }
+
+    @Override
+    public String getTransformerName() {
+        return this.getClass().getSimpleName();
+    }
+
+    public void setValueDataMap(ColumnValueDataMapTable valueDataMap) {
+        this.valueDataMap = Objects.requireNonNull(valueDataMap, "valueDataMap is null");
+    }
+
+    @Override
+    public Object[] doTransform(String schema, String table, List<String> fieldNames, Object[] recordValue) {
+        if (!valueDataMap.isEmpty()) {
+            for (int i = 0; i < fieldNames.size(); ++i) {
+                String column = fieldNames.get(i);
+                Map<String, String> mapper = valueDataMap.get(schema, table, column);
+                if (null != mapper) {
+                    String origin = String.valueOf(recordValue[i]);
+                    if (mapper.containsKey(origin)) {
+                        recordValue[i] = mapper.get(origin);
+                    }
+                }
+            }
+        }
+        return recordValue;
+    }
+}

+ 76 - 0
km-common/km-common-db/src/main/java/com/km/common/db/provider/ProductProviderFactory.java

@@ -0,0 +1,76 @@
+package com.km.common.db.provider;
+
+import com.km.common.db.Utils.DatabaseAwareUtils;
+import com.km.common.db.Utils.ExamineUtils;
+import com.km.common.db.enums.DbProductTypeEnum;
+import lombok.extern.slf4j.Slf4j;
+
+import javax.sql.DataSource;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @Description: ProductProviderFactory
+ *
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 14:00
+ */
+@Slf4j
+public class ProductProviderFactory {
+
+    private static Map<DbProductTypeEnum, Class<? extends IProductFactoryProvider>> providers;
+
+    static {
+        providers = new ConcurrentHashMap<>();
+    }
+
+    public static void register(DbProductTypeEnum type, String classPath) {
+        log.info("Register product {} by subclass :{} ", type, classPath);
+        ClassLoader classLoader = ProductProviderFactory.class.getClassLoader();
+
+        Class<?> clazz = null;
+        try {
+            clazz = classLoader.loadClass(classPath);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+
+        // Check subclass
+        ExamineUtils.check(IProductFactoryProvider.class.isAssignableFrom(clazz),
+            "Class '%s' is not a subclass of " +
+                "class ProductFactoryProvider", clazz.getName());
+
+        // Check exists
+        ExamineUtils.check(!providers.containsKey(type),
+            "Exists ProductFactoryProvider: %s (%s)",
+            type.name(), providers.get(type.name()));
+
+        // Register class
+        providers.put(type, (Class) clazz);
+    }
+
+    public static IProductFactoryProvider newProvider(DbProductTypeEnum type, DataSource dataSource) {
+        Class<? extends IProductFactoryProvider> clazz = providers.get(type);
+        ExamineUtils.check(clazz != null,
+            "Not exists ProductFactoryProvider: %s", type);
+
+        assert IProductFactoryProvider.class.isAssignableFrom(clazz);
+        IProductFactoryProvider instance = null;
+        try {
+            instance = clazz.getDeclaredConstructor(DataSource.class).newInstance(dataSource);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+
+        ExamineUtils.check(type.equals(instance.getProductType()),
+            "ProductFactoryProvider with type '%s' " +
+                "can't be opened by product type '%s'",
+            instance.getProductType(), type);
+        return instance;
+    }
+
+    public static IProductFactoryProvider newProvider(DataSource dataSource) {
+        DbProductTypeEnum type = DatabaseAwareUtils.getProductTypeByDataSource(dataSource);
+        return newProvider(type, dataSource);
+    }
+}

+ 23 - 0
km-common/km-common-db/src/main/java/com/km/common/db/provider/SchemaTableColumnTuple.java

@@ -0,0 +1,23 @@
+package com.km.common.db.provider;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * @Description: Schema/Table/Column三元组定义
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 14:37
+ */
+@Data
+@Builder
+@EqualsAndHashCode
+@NoArgsConstructor
+@AllArgsConstructor
+public class SchemaTableColumnTuple {
+    private String schema;
+    private String table;
+    private String column;
+}

+ 50 - 0
km-common/km-common-db/src/main/java/com/km/common/db/schema/ColumnDescription.java

@@ -0,0 +1,50 @@
+package com.km.common.db.schema;
+
+import com.km.common.db.enums.DbProductTypeEnum;
+import lombok.Data;
+
+/**
+ * @Description: 数据库列描述符信息定义
+ *
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 12:50
+ */
+@Data
+public class ColumnDescription {
+    private String fieldName;
+    private String labelName;
+    private String fieldTypeName;
+    private String filedTypeClassName;
+    private int fieldType;
+    private int displaySize;
+    private int scaleSize;
+    private int precisionSize;
+    private boolean autoIncrement;
+    private boolean nullable;
+    private String remarks;
+    private boolean signed = false;
+    private DbProductTypeEnum productType;
+    private String defaultValue;
+
+    public ColumnDescription copy() {
+        ColumnDescription description = new ColumnDescription();
+        description.setFieldName(fieldName);
+        description.setLabelName(labelName);
+        description.setFieldTypeName(fieldTypeName);
+        description.setFiledTypeClassName(filedTypeClassName);
+        description.setFieldType(fieldType);
+        description.setDisplaySize(displaySize);
+        description.setScaleSize(scaleSize);
+        description.setPrecisionSize(precisionSize);
+        description.setAutoIncrement(autoIncrement);
+        description.setNullable(nullable);
+        description.setRemarks(remarks);
+        description.setSigned(signed);
+        description.setProductType(productType);
+        description.setDefaultValue(defaultValue);
+        return description;
+    }
+    public ColumnMetaData getMetaData() {
+        return new ColumnMetaData(this);
+    }
+}

+ 480 - 0
km-common/km-common-db/src/main/java/com/km/common/db/schema/ColumnMetaData.java

@@ -0,0 +1,480 @@
+package com.km.common.db.schema;
+
+import com.km.common.db.constant.DbConstants;
+
+/**
+ * @Description: ColumnMetaData
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 13:36
+ */
+public class ColumnMetaData {
+
+    /**
+     * Value type indicating that the value has no type set
+     */
+    public static final int TYPE_NONE = 0;
+
+    /**
+     * Value type indicating that the value contains a floating point double precision number.
+     */
+    public static final int TYPE_NUMBER = 1;
+
+    /**
+     * Value type indicating that the value contains a text String.
+     */
+    public static final int TYPE_STRING = 2;
+
+    /**
+     * Value type indicating that the value contains a Date.
+     */
+    public static final int TYPE_DATE = 3;
+
+    /**
+     * Value type indicating that the value contains a boolean.
+     */
+    public static final int TYPE_BOOLEAN = 4;
+
+    /**
+     * Value type indicating that the value contains a long integer.
+     */
+    public static final int TYPE_INTEGER = 5;
+
+    /**
+     * Value type indicating that the value contains a floating point precision number with arbitrary precision.
+     */
+    public static final int TYPE_BIGNUMBER = 6;
+
+    /**
+     * Value type indicating that the value contains an Object.
+     */
+    public static final int TYPE_SERIALIZABLE = 7;
+
+    /**
+     * Value type indicating that the value contains binary data: BLOB, CLOB, ...
+     */
+    public static final int TYPE_BINARY = 8;
+
+    /**
+     * Value type indicating that the value contains a date-time with nanosecond precision
+     */
+    public static final int TYPE_TIMESTAMP = 9;
+
+    /**
+     * Value type indicating that the value contains a time
+     */
+    public static final int TYPE_TIME = 10;
+
+    /**
+     * Value type indicating that the value contains a Internet address
+     */
+    public static final int TYPE_INET = 11;
+
+    /**
+     * The Constant typeCodes.
+     */
+    public static final String[] TYPE_CODES = new String[]{"-", "Number", "String", "Date", "Boolean",
+        "Integer", "BigNumber", "Serializable", "Binary", "Timestamp", "Time", "Internet Address",};
+
+    //////////////////////////////////////////////////////////////////////
+
+    /**
+     * Column name
+     */
+    protected String name;
+    protected int length;
+    protected int precision;
+    protected int type;
+    protected String remarks;
+    protected String defaultValue;
+
+    /**
+     * Constructor function
+     *
+     * @param desc
+     */
+    public ColumnMetaData(ColumnDescription desc) {
+        this.create(desc);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public int getLength() {
+        return length;
+    }
+
+    public void setLength(int length) {
+        this.length = length;
+    }
+
+    public int getPrecision() {
+        return precision;
+    }
+
+    public void setPrecision(int precision) {
+        this.precision = precision;
+    }
+
+    public int getType() {
+        return type;
+    }
+
+    public void setType(int type) {
+        this.type = type;
+    }
+
+    public String getRemarks() {
+        return remarks;
+    }
+
+    public void setRemarks(String remarks) {
+        this.remarks = remarks;
+    }
+
+    public String getDefaultValue() {
+        return defaultValue;
+    }
+
+    public void setDefaultValue(String defaultValue) {
+        this.defaultValue = defaultValue;
+    }
+
+    public boolean isHaveDefault() {
+        return defaultValue != null && !defaultValue.isEmpty();
+    }
+
+    /**
+     * Checks whether or not the value is a String.
+     *
+     * @return true if the value is a String.
+     */
+    public boolean isString() {
+        return type == TYPE_STRING;
+    }
+
+    /**
+     * Checks whether or not this value is a Date
+     *
+     * @return true if the value is a Date
+     */
+    public boolean isDate() {
+        return type == TYPE_DATE;
+    }
+
+    /**
+     * Checks whether or not this value is a Time
+     *
+     * @return true if the value is a Time
+     */
+    public boolean isTime() {
+        return type == TYPE_TIME;
+    }
+
+    /**
+     * Checks whether or not this value is a DateTime
+     *
+     * @return true if the value is a DateTime
+     */
+    public boolean isDateTime() {
+        return type == TYPE_TIMESTAMP;
+    }
+
+    /**
+     * Checks whether or not the value is a Big Number
+     *
+     * @return true is this value is a big number
+     */
+    public boolean isBigNumber() {
+        return type == TYPE_BIGNUMBER;
+    }
+
+    /**
+     * Checks whether or not the value is a Number
+     *
+     * @return true is this value is a number
+     */
+    public boolean isNumber() {
+        return type == TYPE_NUMBER;
+    }
+
+    /**
+     * Checks whether or not this value is a boolean
+     *
+     * @return true if this value has type boolean.
+     */
+    public boolean isBoolean() {
+        return type == TYPE_BOOLEAN;
+    }
+
+    /**
+     * Checks whether or not this value is of type Serializable
+     *
+     * @return true if this value has type Serializable
+     */
+    public boolean isSerializableType() {
+        return type == TYPE_SERIALIZABLE;
+    }
+
+    /**
+     * Checks whether or not this value is of type Binary
+     *
+     * @return true if this value has type Binary
+     */
+    public boolean isBinary() {
+        return type == TYPE_BINARY;
+    }
+
+    /**
+     * Checks whether or not this value is an Integer
+     *
+     * @return true if this value is an integer
+     */
+    public boolean isInteger() {
+        return type == TYPE_INTEGER;
+    }
+
+    /**
+     * Checks whether or not this Value is Numeric A Value is numeric if it is either of type Number or Integer
+     *
+     * @return true if the value is either of type Number or Integer
+     */
+    public boolean isNumeric() {
+        return isInteger() || isNumber() || isBigNumber();
+    }
+
+    /**
+     * Checks whether or not the specified type is either Integer or Number
+     *
+     * @param t the type to check
+     * @return true if the type is Integer or Number
+     */
+    public static final boolean isNumeric(int t) {
+        return t == TYPE_INTEGER || t == TYPE_NUMBER || t == TYPE_BIGNUMBER;
+    }
+
+    /**
+     * Return the type of a value in a textual form: "String", "Number", "Integer", "Boolean", "Date", ...
+     *
+     * @return A String describing the type of value.
+     */
+    public String getTypeDesc() {
+        return TYPE_CODES[type];
+    }
+
+    private void create(ColumnDescription desc) {
+        int length = -1;
+        int precision = -1;
+        int valtype = ColumnMetaData.TYPE_NONE;
+        int type = desc.getFieldType();
+        boolean signed = desc.isSigned();
+
+        switch (type) {
+            case java.sql.Types.CHAR:
+            case java.sql.Types.NCHAR:
+            case java.sql.Types.VARCHAR:
+            case java.sql.Types.NVARCHAR:
+                valtype = ColumnMetaData.TYPE_STRING;
+                length = desc.getDisplaySize();
+                break;
+
+            case java.sql.Types.LONGVARCHAR:
+            case java.sql.Types.LONGNVARCHAR:
+            case java.sql.Types.CLOB:
+            case java.sql.Types.NCLOB:
+            case java.sql.Types.SQLXML:
+            case java.sql.Types.ROWID:
+                valtype = ColumnMetaData.TYPE_STRING;
+                length = DbConstants.CLOB_LENGTH;
+                break;
+
+            case java.sql.Types.BIGINT:
+                // verify Unsigned BIGINT overflow!
+                //
+                if (signed) {
+                    valtype = ColumnMetaData.TYPE_INTEGER;
+                    precision = 0; // Max 9.223.372.036.854.775.807
+                    length = 15;
+                } else {
+                    valtype = ColumnMetaData.TYPE_BIGNUMBER;
+                    precision = 0; // Max 18.446.744.073.709.551.615
+                    length = 16;
+                }
+                break;
+
+            case java.sql.Types.INTEGER:
+                valtype = ColumnMetaData.TYPE_INTEGER;
+                precision = 0; // Max 2.147.483.647
+                length = 9;
+                break;
+
+            case java.sql.Types.SMALLINT:
+                valtype = ColumnMetaData.TYPE_INTEGER;
+                precision = 0; // Max 32.767
+                length = 4;
+                break;
+
+            case java.sql.Types.TINYINT:
+                valtype = ColumnMetaData.TYPE_INTEGER;
+                precision = 0; // Max 127
+                length = 2;
+                break;
+
+            case java.sql.Types.DECIMAL:
+            case java.sql.Types.DOUBLE:
+            case java.sql.Types.FLOAT:
+            case java.sql.Types.REAL:
+            case java.sql.Types.NUMERIC:
+                valtype = ColumnMetaData.TYPE_NUMBER;
+                length = desc.getPrecisionSize();
+                precision = desc.getScaleSize();
+                if (length >= 126) {
+                    length = -1;
+                }
+                if (precision >= 126) {
+                    precision = -1;
+                }
+
+                if (type == java.sql.Types.DOUBLE || type == java.sql.Types.FLOAT
+                    || type == java.sql.Types.REAL) {
+                    if (precision == 0) {
+                        if (!signed) {
+                            precision = -1; // precision is obviously incorrect if the type if
+                            // Double/Float/Real
+                        } else {
+                            length = 18;
+                            precision = 4;
+                        }
+                    }
+
+                    // If we're dealing with PostgreSQL and double precision types
+                    if ((desc.getProductType().isLikePostgres())
+                        && type == java.sql.Types.DOUBLE
+                        && precision >= 16
+                        && length >= 16) {
+                        precision = -1;
+                        length = -1;
+                    }
+
+                    // MySQL: max resolution is double precision floating point (double)
+                    // The (12,31) that is given back is not correct
+                    if (desc.getProductType().isLikeMysql()) {
+                        if (precision >= length) {
+                            precision = -1;
+                            length = -1;
+                        }
+                    }
+
+                    // If we're dealing with Hive and double/float precision types
+                    if (desc.getProductType().isLikeHive()) {
+                        if (type == java.sql.Types.DOUBLE
+                            && precision >= 15
+                            && length >= 15) {
+                            precision = 6;
+                            length = 25;
+                        }
+
+                        if (type == java.sql.Types.FLOAT
+                            && precision >= 7
+                            && length >= 7) {
+                            precision = 6;
+                            length = 25;
+                        }
+                    }
+
+                } else {
+                    if (precision == 0) {
+                        if (length <= 18 && length > 0) { // Among others Oracle is affected here.
+                            valtype = ColumnMetaData.TYPE_INTEGER; // Long can hold up to 18
+                            // significant digits
+                        } else if (length > 18) {
+                            valtype = ColumnMetaData.TYPE_BIGNUMBER;
+                        }
+                    } else { // we have a precision: keep NUMBER or change to BIGNUMBER?
+                        if (length > 15 || precision > 15) {
+                            valtype = ColumnMetaData.TYPE_BIGNUMBER;
+                        }
+                    }
+                }
+
+                if (desc.getProductType().isLikePostgres()) {
+                    // undefined size => arbitrary precision
+                    if (type == java.sql.Types.NUMERIC && length == 0 && precision == 0) {
+                        valtype = ColumnMetaData.TYPE_BIGNUMBER;
+                        length = -1;
+                        precision = -1;
+                    }
+                }
+
+                if (desc.getProductType().isLikeOracle()) {
+                    if (precision == 0 && length == 38) {
+                        valtype = ColumnMetaData.TYPE_INTEGER;
+                    }
+                    if (precision <= 0 && length <= 0) {
+                        // undefined size: BIGNUMBER,
+                        // precision on Oracle can be 38, too
+                        // big for a Number type
+                        valtype = ColumnMetaData.TYPE_BIGNUMBER;
+                        length = -1;
+                        precision = -1;
+                    }
+                }
+
+                break;
+
+            case java.sql.Types.TIMESTAMP:
+            case java.sql.Types.TIMESTAMP_WITH_TIMEZONE:
+                valtype = ColumnMetaData.TYPE_TIMESTAMP;
+                length = desc.getScaleSize();
+                break;
+
+            case java.sql.Types.DATE:
+                valtype = ColumnMetaData.TYPE_DATE;
+                break;
+
+            case java.sql.Types.TIME:
+            case java.sql.Types.TIME_WITH_TIMEZONE:
+                valtype = ColumnMetaData.TYPE_TIME;
+                break;
+
+            case java.sql.Types.BOOLEAN:
+            case java.sql.Types.BIT:
+                valtype = ColumnMetaData.TYPE_BOOLEAN;
+                break;
+
+            case java.sql.Types.BINARY:
+            case java.sql.Types.BLOB:
+            case java.sql.Types.VARBINARY:
+            case java.sql.Types.LONGVARBINARY:
+                valtype = ColumnMetaData.TYPE_BINARY;
+                precision = -1;
+                break;
+
+            default:
+                // others total 9 value, set default STRING type:
+                // java.sql.Types.JAVA_OBJECT
+                // java.sql.Types.OTHER
+                // java.sql.Types.NULL
+                // java.sql.Types.DISTINCT
+                // java.sql.Types.STRUCT
+                // java.sql.Types.ARRAY
+                // java.sql.Types.REF
+                // java.sql.Types.DATALINK
+                // java.sql.Types.REF_CURSOR
+                valtype = ColumnMetaData.TYPE_STRING;
+                length = desc.getDisplaySize();
+                break;
+        }
+
+        this.name = desc.getFieldName();
+        this.length = length;
+        this.precision = precision;
+        this.type = valtype;
+        this.remarks = desc.getRemarks();
+        this.defaultValue = desc.getDefaultValue();
+    }
+}

+ 23 - 0
km-common/km-common-db/src/main/java/com/km/common/db/schema/IndexDescription.java

@@ -0,0 +1,23 @@
+package com.km.common.db.schema;
+
+import com.km.common.db.enums.DbTableIndexEnum;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @Description: IndexDescription
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 12:51
+ */
+@AllArgsConstructor
+@Data
+public class IndexDescription {
+
+    private DbTableIndexEnum indexType;
+
+    private String indexName;
+
+    private List<IndexFieldMeta> indexFields;
+}

+ 17 - 0
km-common/km-common-db/src/main/java/com/km/common/db/schema/IndexFieldMeta.java

@@ -0,0 +1,17 @@
+package com.km.common.db.schema;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/**
+ * @Description: IndexFieldMeta
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 12:52
+ */
+@Data
+@AllArgsConstructor
+public class IndexFieldMeta {
+    private String fieldName;
+    private Integer ordinalPosition;
+    private Boolean isAscOrder;
+}

+ 19 - 0
km-common/km-common-db/src/main/java/com/km/common/db/schema/SchemaTableData.java

@@ -0,0 +1,19 @@
+package com.km.common.db.schema;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @Description: 数据库表的数据
+ *
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 12:48
+ */
+@Data
+public class SchemaTableData {
+    private String schemaName;
+    private String tableName;
+    private List<String> columns;
+    private List<List<Object>> rows;
+}

+ 20 - 0
km-common/km-common-db/src/main/java/com/km/common/db/schema/SchemaTableMeta.java

@@ -0,0 +1,20 @@
+package com.km.common.db.schema;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+
+/**
+ * @Description: SchemaTableMeta
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 12:49
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class SchemaTableMeta extends TableDescription{
+    private List<String> primaryKeys;
+    private String createSql;
+    private List<ColumnDescription> columns;
+    private List<IndexDescription> indexes;
+}

+ 32 - 0
km-common/km-common-db/src/main/java/com/km/common/db/schema/TableDescription.java

@@ -0,0 +1,32 @@
+package com.km.common.db.schema;
+
+import com.km.common.db.enums.DbProductTableEnum;
+import com.km.common.db.enums.DbProductTypeEnum;
+import lombok.Data;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @Description: TableDescription
+ *
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 11:57
+ */
+@Data
+public class TableDescription {
+    private String tableName;
+    private String schemaName;
+    private String remarks;
+
+    private DbProductTableEnum tableType;
+    public String getTableType() {
+        return tableType.name();
+    }
+    public void setTableType(String tableType) {
+        this.tableType = DbProductTableEnum.valueOf(tableType.toUpperCase());
+    }
+
+    public boolean isViewTable() {
+        return DbProductTableEnum.VIEW == tableType;
+    }
+}

+ 223 - 0
km-common/km-common-db/src/main/java/com/km/common/db/service/DefaultMetadataService.java

@@ -0,0 +1,223 @@
+package com.km.common.db.service;
+
+import com.km.common.db.Utils.GenerateSqlUtils;
+import com.km.common.db.enums.DbProductTypeEnum;
+import com.km.common.db.provider.IMetadataProvider;
+import com.km.common.db.provider.IProductFactoryProvider;
+import com.km.common.db.provider.ITableDataQueryProvider;
+import com.km.common.db.provider.ProductProviderFactory;
+import com.km.common.db.schema.ColumnDescription;
+import com.km.common.db.schema.IndexDescription;
+import com.km.common.db.schema.SchemaTableData;
+import com.km.common.db.schema.SchemaTableMeta;
+import com.km.common.db.schema.TableDescription;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @Description: DefaultMetadataService
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 13:49
+ */
+public class DefaultMetadataService implements IMetadataService{
+
+    private DataSource dataSource;
+    private IProductFactoryProvider factoryProvider;
+    private IMetadataProvider metaQueryProvider;
+    private ITableDataQueryProvider dataQueryProvider;
+
+    public DefaultMetadataService(DataSource dataSource) {
+        this.dataSource = dataSource;
+        IProductFactoryProvider factoryProvider = ProductProviderFactory
+            .newProvider(dataSource);
+        this.metaQueryProvider = factoryProvider.createMetadataQueryProvider();
+        this.dataQueryProvider = factoryProvider.createTableDataQueryProvider();
+    }
+
+    public DefaultMetadataService(DataSource dataSource, DbProductTypeEnum type) {
+        this.dataSource = dataSource;
+        this.factoryProvider = ProductProviderFactory
+            .newProvider(type, dataSource);
+        this.metaQueryProvider = factoryProvider.createMetadataQueryProvider();
+        this.dataQueryProvider = factoryProvider.createTableDataQueryProvider();
+    }
+
+    @Override
+    public void close() {
+        if (dataSource instanceof AutoCloseable) {
+            try {
+                ((AutoCloseable) dataSource).close();
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    @Override
+    public DataSource getDataSource() {
+        return this.dataSource;
+    }
+
+    @Override
+    public List<String> querySchemaList() {
+        try (Connection connection = dataSource.getConnection()) {
+            return metaQueryProvider.querySchemaList(connection);
+        } catch (SQLException se) {
+            throw new RuntimeException(se);
+        }
+    }
+
+    @Override
+    public List<TableDescription> queryTableList(String schemaName) {
+        try (Connection connection = dataSource.getConnection()) {
+            return metaQueryProvider.queryTableList(connection, schemaName);
+        } catch (SQLException se) {
+            throw new RuntimeException(se);
+        }
+    }
+
+    @Override
+    public String getTableDDL(String schemaName, String tableName) {
+        try (Connection connection = dataSource.getConnection()) {
+            return metaQueryProvider.getTableDDL(connection, schemaName, tableName);
+        } catch (SQLException se) {
+            throw new RuntimeException(se);
+        }
+    }
+
+    @Override
+    public String getTableRemark(String schemaName, String tableName) {
+        try (Connection connection = dataSource.getConnection()) {
+            TableDescription td = metaQueryProvider.queryTableMeta(connection, schemaName, tableName);
+            return null == td ? null : td.getRemarks();
+        } catch (SQLException se) {
+            throw new RuntimeException(se);
+        }
+    }
+
+    @Override
+    public String getViewDDL(String schemaName, String tableName) {
+        try (Connection connection = dataSource.getConnection()) {
+            return metaQueryProvider.getViewDDL(connection, schemaName, tableName);
+        } catch (SQLException se) {
+            throw new RuntimeException(se);
+        }
+    }
+
+    @Override
+    public List<String> queryTableColumnName(String schemaName, String tableName) {
+        try (Connection connection = dataSource.getConnection()) {
+            return metaQueryProvider.queryTableColumnName(connection, schemaName, tableName);
+        } catch (SQLException se) {
+            throw new RuntimeException(se);
+        }
+    }
+
+    @Override
+    public List<ColumnDescription> queryTableColumnMeta(String schemaName, String tableName) {
+        try (Connection connection = dataSource.getConnection()) {
+            return metaQueryProvider.queryTableColumnMeta(connection, schemaName, tableName);
+        } catch (SQLException se) {
+            throw new RuntimeException(se);
+        }
+    }
+
+    @Override
+    public List<ColumnDescription> querySqlColumnMeta(String querySql) {
+        try (Connection connection = dataSource.getConnection()) {
+            return metaQueryProvider.querySelectSqlColumnMeta(connection, querySql);
+        } catch (SQLException se) {
+            throw new RuntimeException(se);
+        }
+    }
+
+    @Override
+    public List<String> queryTablePrimaryKeys(String schemaName, String tableName) {
+        try (Connection connection = dataSource.getConnection()) {
+            return metaQueryProvider.queryTablePrimaryKeys(connection, schemaName, tableName);
+        } catch (SQLException se) {
+            throw new RuntimeException(se);
+        }
+    }
+
+    @Override
+    public List<IndexDescription> queryTableIndexes(String schemaName, String tableName) {
+        try (Connection connection = dataSource.getConnection()) {
+            return metaQueryProvider.queryTableIndexes(connection, schemaName, tableName);
+        } catch (SQLException se) {
+            throw new RuntimeException(se);
+        }
+    }
+
+    @Override
+    public SchemaTableMeta queryTableMeta(String schemaName, String tableName) {
+        SchemaTableMeta tableMeta = new SchemaTableMeta();
+
+        try (Connection connection = dataSource.getConnection()) {
+            TableDescription tableDesc = metaQueryProvider.queryTableMeta(connection, schemaName, tableName);
+            if (null == tableDesc) {
+                throw new IllegalArgumentException("Table Or View Not Exist");
+            }
+
+            List<ColumnDescription> columns = metaQueryProvider.queryTableColumnMeta(
+                connection, schemaName, tableName);
+
+            List<String> pks;
+            String createSql;
+            List<IndexDescription> indexes;
+            if (tableDesc.isViewTable()) {
+                pks = Collections.emptyList();
+                createSql = metaQueryProvider.getViewDDL(connection, schemaName, tableName);
+                indexes = Collections.emptyList();
+            } else {
+                pks = metaQueryProvider.queryTablePrimaryKeys(connection, schemaName, tableName);
+                createSql = metaQueryProvider.getTableDDL(connection, schemaName, tableName);
+                indexes = metaQueryProvider.queryTableIndexes(connection, schemaName, tableName);
+            }
+
+            tableMeta.setSchemaName(schemaName);
+            tableMeta.setTableName(tableName);
+            tableMeta.setTableType(tableDesc.getTableType());
+            tableMeta.setRemarks(tableDesc.getRemarks());
+            tableMeta.setColumns(columns);
+            tableMeta.setPrimaryKeys(pks);
+            tableMeta.setCreateSql(createSql);
+            tableMeta.setIndexes(indexes);
+
+            return tableMeta;
+        } catch (SQLException se) {
+            throw new RuntimeException(se);
+        }
+    }
+
+    @Override
+    public SchemaTableData queryTableData(String schemaName, String tableName, int rowCount) {
+        try (Connection connection = dataSource.getConnection()) {
+            return dataQueryProvider.queryTableData(connection, schemaName, tableName, rowCount);
+        } catch (SQLException se) {
+            throw new RuntimeException(se);
+        }
+    }
+
+    @Override
+    public void testQuerySql(String sql) {
+        try (Connection connection = dataSource.getConnection()) {
+            metaQueryProvider.testQuerySQL(connection, sql);
+        } catch (SQLException se) {
+            throw new RuntimeException(se);
+        }
+    }
+
+    @Override
+    public List<String> getDDLCreateTableSQL(IMetadataProvider provider,
+                                             List<ColumnDescription> fieldNames, List<String> primaryKeys, String schemaName,
+                                             String tableName, String tableRemarks, boolean autoIncr, Map<String, String> tblProperties) {
+        return GenerateSqlUtils.getDDLCreateTableSQL(
+            provider, fieldNames, primaryKeys, schemaName, tableName, tableRemarks, autoIncr, tblProperties);
+    }
+}

+ 159 - 0
km-common/km-common-db/src/main/java/com/km/common/db/service/IMetadataService.java

@@ -0,0 +1,159 @@
+package com.km.common.db.service;
+
+import com.km.common.db.provider.IMetadataProvider;
+import com.km.common.db.schema.ColumnDescription;
+import com.km.common.db.schema.IndexDescription;
+import com.km.common.db.schema.SchemaTableData;
+import com.km.common.db.schema.SchemaTableMeta;
+import com.km.common.db.schema.TableDescription;
+
+import javax.sql.DataSource;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @Description: IMetadataService
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 12:48
+ */
+public interface IMetadataService {
+    /**
+     * 资源释放
+     */
+    void close();
+
+    /**
+     * 获取数据源对象
+     *
+     * @return DataSource
+     */
+    DataSource getDataSource();
+
+    /**
+     * 获取数据库的schema模式列表
+     *
+     * @return List<String>
+     */
+    List<String> querySchemaList();
+
+    /**
+     * 获取指定Schema下所有的表列表
+     *
+     * @param schemaName 模式名称
+     * @return List<TableDescription>
+     */
+    List<TableDescription> queryTableList(String schemaName);
+
+    /**
+     * 获取物理表的DDL建表语句
+     *
+     * @param schemaName 模式名称
+     * @param tableName  表名称
+     * @return String
+     */
+    String getTableDDL(String schemaName, String tableName);
+
+    /**
+     * 获取物理表的注释
+     *
+     * @param schemaName 模式名称
+     * @param tableName  表名称
+     * @return String
+     */
+    String getTableRemark(String schemaName, String tableName);
+
+    /**
+     * 获取物理表的DDL建表语句
+     *
+     * @param schemaName 模式名称
+     * @param tableName  表名称
+     * @return String
+     */
+    String getViewDDL(String schemaName, String tableName);
+
+    /**
+     * 获取指定schema.table的字段名列表
+     *
+     * @param schemaName 模式名称
+     * @param tableName  表或视图名称
+     * @return List<String>
+     */
+    List<String> queryTableColumnName(String schemaName, String tableName);
+
+    /**
+     * 获取指定schema.table的表结构字段信息
+     *
+     * @param schemaName 模式名称
+     * @param tableName  表或视图名称
+     * @return List<ColumnDescription>
+     */
+    List<ColumnDescription> queryTableColumnMeta(String schemaName, String tableName);
+
+    /**
+     * 获取指定SQL结构字段信息
+     *
+     * @param querySql 查询的SQL语句
+     * @return @return List<ColumnDescription>
+     */
+    List<ColumnDescription> querySqlColumnMeta(String querySql);
+
+    /**
+     * 获取表的主键信息字段列表
+     *
+     * @param schemaName 模式名称
+     * @param tableName 表名称
+     * @return List<String>
+     */
+    List<String> queryTablePrimaryKeys(String schemaName, String tableName);
+
+    /**
+     * 获取表的索引列表
+     *
+     * @param schemaName 模式名称
+     * @param tableName 表名称
+     * @return
+     */
+    List<IndexDescription> queryTableIndexes(String schemaName, String tableName);
+
+    /**
+     * 测试数据库SQL查询
+     *
+     * @param sql 待查询的SQL语句
+     */
+    void testQuerySql(String sql);
+
+    /**
+     * 获取表的元数据
+     *
+     * @param schemaName 模式名称
+     * @param tableName  表名称
+     * @return SchemaTableMeta
+     */
+    SchemaTableMeta queryTableMeta(String schemaName, String tableName);
+
+    /**
+     * 获取表的数据内容
+     *
+     * @param schemaName 模式名称
+     * @param tableName  表名称
+     * @param rowCount   记录总数
+     * @return SchemaTableData
+     */
+    SchemaTableData queryTableData(String schemaName, String tableName, int rowCount);
+
+    /**
+     * 根据字段结构信息组装对应数据库的建表DDL语句
+     *
+     * @param provider      目的数据库类型
+     * @param fieldNames    字段结构信息
+     * @param primaryKeys   主键字段信息
+     * @param schemaName    模式名称
+     * @param tableName     表名称
+     * @param autoIncr      是否允许主键自增
+     * @param tblProperties 表的属性信息
+     * @return 对应数据库的DDL建表语句
+     */
+    List<String> getDDLCreateTableSQL(IMetadataProvider provider, List<ColumnDescription> fieldNames,
+                                      List<String> primaryKeys, String schemaName, String tableName, String tableRemarks,
+                                      boolean autoIncr, Map<String, String> tblProperties);
+}

+ 1 - 0
km-common/pom.xml

@@ -13,6 +13,7 @@
     <modules>
         <module>km-common-bom</module>
         <module>km-common-core</module>
+        <module>km-common-db</module>
         <module>km-common-encrypt</module>
         <module>km-common-excel</module>
         <module>km-common-job</module>

+ 6 - 0
km-modules/km-db/pom.xml

@@ -28,6 +28,12 @@
             <groupId>com.km</groupId>
             <artifactId>km-common-excel</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.km</groupId>
+            <artifactId>km-common-db</artifactId>
+            <version>1.1.0</version>
+            <scope>compile</scope>
+        </dependency>
     </dependencies>
 
 </project>

+ 322 - 0
km-modules/km-db/src/main/java/com/km/db/Utils/JdbcUrlUtils.java

@@ -0,0 +1,322 @@
+package com.km.db.Utils;
+
+import lombok.experimental.UtilityClass;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.util.function.Function;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @Description: JdbcUrl红聚类
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 15:29
+ */
+@UtilityClass
+public class JdbcUrlUtils {
+
+    public static final String PROP_HOST = "host"; //$NON-NLS-1$
+    public static final String PROP_PORT = "port"; //$NON-NLS-1$
+    public static final String PROP_DATABASE = "database"; //$NON-NLS-1$
+    public static final String PROP_SERVER = "server"; //$NON-NLS-1$
+    public static final String PROP_PARAMS = "params"; //$NON-NLS-1$
+    public static final String PROP_FOLDER = "folder"; //$NON-NLS-1$
+    public static final String PROP_FILE = "file"; //$NON-NLS-1$
+    public static final String PROP_USER = "user"; //$NON-NLS-1$
+    public static final String PROP_PASSWORD = "password"; //$NON-NLS-1$
+
+    private static String getPropertyRegex(String property) {
+        switch (property) {
+            case PROP_FOLDER:
+            case PROP_FILE:
+            case PROP_PARAMS:
+                return ".+?";
+            default:
+                return "[\\\\w\\\\-_.~]+";
+        }
+    }
+
+    private static String replaceAll(String input, String regex, Function<Matcher, String> replacer) {
+        final Matcher matcher = Pattern.compile(regex).matcher(input);
+        final StringBuffer sb = new StringBuffer();
+        while (matcher.find()) {
+            matcher.appendReplacement(sb, replacer.apply(matcher));
+        }
+        matcher.appendTail(sb);
+        return sb.toString();
+    }
+
+    public static Pattern getPattern(String sampleUrl) {
+        String pattern = sampleUrl;
+        pattern = replaceAll(pattern, "\\[(.*?)]", m -> "\\\\E(?:\\\\Q" + m.group(1) + "\\\\E)?\\\\Q");
+        pattern = replaceAll(pattern, "\\{(.*?)}",
+            m -> "\\\\E(\\?<\\\\Q" + m.group(1) + "\\\\E>" + getPropertyRegex(m.group(1)) + ")\\\\Q");
+        pattern = "^\\Q" + pattern + "\\E$";
+        return Pattern.compile(pattern);
+    }
+
+    /**
+     * 根据主机地址与端口号检查可达性
+     *
+     * @param host 主机地址
+     * @param port 端口号
+     * @return 成功返回true,否则为false
+     */
+    public static boolean reachable(String host, String port) {
+        try {
+            try (Socket socket = new Socket()) {
+                socket.connect(new InetSocketAddress(host, Integer.parseInt(port)), 1500);
+            }
+        } catch (IOException e) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * 测试代码
+     *
+     * @param args
+     */
+    public static void main(String[] args) {
+        // 1、teradata数据库
+        // jdbc:teradata://localhost/DATABASE=test,DBS_PORT=1234,CLIENT_CHARSET=EUC_CN,TMODE=TERA,CHARSET=ASCII,LOB_SUPPORT=true
+        final Matcher matcher0 = JdbcUrlUtils
+            .getPattern("jdbc:teradata://{host}/DATABASE={database},DBS_PORT={port}[,{params}]")
+            .matcher(
+                "jdbc:teradata://localhost/DATABASE=test,DBS_PORT=1234,CLIENT_CHARSET=EUC_CN,TMODE=TERA,CHARSET=ASCII,LOB_SUPPORT=true");
+        if (matcher0.matches()) {
+            System.out.println("teradata host:" + matcher0.group("host"));
+            System.out.println("teradata port:" + matcher0.group("port"));
+            System.out.println("teradata database:" + matcher0.group("database"));
+            String params = matcher0.group("params");
+            if (null != params) {
+                String[] pairs = params.split(",");
+                for (String pair : pairs) {
+                    System.out.println("teradata params:" + pair);
+                }
+            }
+        } else {
+            System.out.println("error for teradata!");
+        }
+
+        // 2、PostgreSQL数据库
+        // jdbc:postgresql://localhost:5432/dvdrental?currentSchema=test&ssl=true
+        // https://jdbc.postgresql.org/documentation/head/connect.html
+        final Matcher matcher1 = JdbcUrlUtils
+            .getPattern("jdbc:postgresql://{host}[:{port}]/[{database}][\\?{params}]")
+            .matcher("jdbc:postgresql://localhost:5432/dvdrental?currentSchema=test&ssl=true");
+        if (matcher1.matches()) {
+            System.out.println("postgresql host:" + matcher1.group("host"));
+            System.out.println("postgresql port:" + matcher1.group("port"));
+            System.out.println("postgresql database:" + matcher1.group("database"));
+            String params = matcher1.group("params");
+            if (null != params) {
+                String[] pairs = params.split("&");
+                for (String pair : pairs) {
+                    System.out.println("postgresql params:" + pair);
+                }
+            }
+        } else {
+            System.out.println("error for postgresql!");
+        }
+
+        // 3、Oracle数据库
+        // oracle sid 方式
+        final Matcher matcher2 = JdbcUrlUtils.getPattern("jdbc:oracle:thin:@{host}[:{port}]:{sid}")
+            .matcher("jdbc:oracle:thin:@localhost:1521:orcl");
+        if (matcher2.matches()) {
+            System.out.println("oracle sid host:" + matcher2.group("host"));
+            System.out.println("oracle sid port:" + matcher2.group("port"));
+            System.out.println("oracle sid name:" + matcher2.group("sid"));
+        } else {
+            System.out.println("error for oracle sid!");
+        }
+
+        // oracle service name 方式
+        final Matcher matcher2_1 = JdbcUrlUtils.getPattern("jdbc:oracle:thin:@//{host}[:{port}]/{name}")
+            .matcher("jdbc:oracle:thin:@//localhost:1521/orcl.city.com");
+        if (matcher2_1.matches()) {
+            System.out.println("oracle ServiceName host:" + matcher2_1.group("host"));
+            System.out.println("oracle ServiceName port:" + matcher2_1.group("port"));
+            System.out.println("oracle ServiceName name:" + matcher2_1.group("name"));
+        } else {
+            System.out.println("error for oracle ServiceName!");
+        }
+
+        // oracle TNSName 方式不支持
+        // jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.16.91)(PORT=1521)))(CONNECT_DATA=(SERVICE_NAME=orcl)))
+        // ..............................
+
+        // 4、MySQL数据库
+        // jdbc:mysql://172.17.2.10:3306/test?useUnicode=true&useSSL=false
+        final Matcher matcher3 = JdbcUrlUtils
+            .getPattern("jdbc:mysql://{host}[:{port}]/[{database}][\\?{params}]")
+            .matcher("jdbc:mysql://localhost:3306/test_demo?useUnicode=true&useSSL=false");
+        if (matcher3.matches()) {
+            System.out.println("mysql host:" + matcher3.group("host"));
+            System.out.println("mysql port:" + matcher3.group("port"));
+            System.out.println("mysql database:" + matcher3.group("database"));
+            String params = matcher3.group("params");
+            if (null != params) {
+                String[] pairs = params.split("&");
+                for (String pair : pairs) {
+                    System.out.println("mysql params:" + pair);
+                }
+            }
+        } else {
+            System.out.println("error for mysql!");
+        }
+
+        // 5、MariaDB数据库
+        // 同Mysql的jdbc-url
+        final Matcher matcher4 = JdbcUrlUtils
+            .getPattern("jdbc:mariadb://{host}[:{port}]/[{database}][\\?{params}]")
+            .matcher("jdbc:mariadb://localhost:3306/test_demo");
+        if (matcher4.matches()) {
+            System.out.println("mariadb host:" + matcher4.group("host"));
+            System.out.println("mariadb port:" + matcher4.group("port"));
+            System.out.println("mariadb database:" + matcher4.group("database"));
+            String params = matcher4.group("params");
+            if (null != params) {
+                String[] pairs = params.split("&");
+                for (String pair : pairs) {
+                    System.out.println("mysql params:" + pair);
+                }
+            }
+        } else {
+            System.out.println("error for mariadb!");
+        }
+
+        // 6、Microsoft SQLServer数据库
+        // jdbc:sqlserver://localhost:1433;DatabaseName=AdventureWorks;user=MyUserName;password=123456;
+        final Matcher matcher5 = JdbcUrlUtils
+            .getPattern("jdbc:sqlserver://{host}[:{port}][;DatabaseName={database}][;{params}]")
+            .matcher("jdbc:sqlserver://localhost:1433;DatabaseName=master;user=MyUserName");
+        if (matcher5.matches()) {
+            System.out.println("sqlserver host:" + matcher5.group("host"));
+            System.out.println("sqlserver port:" + matcher5.group("port"));
+            System.out.println("sqlserver database:" + matcher5.group("database"));
+            String params = matcher5.group("params");
+            if (null != params) {
+                String[] pairs = params.split(";");
+                for (String pair : pairs) {
+                    System.out.println("sqlserver params:" + pair);
+                }
+            }
+        } else {
+            System.out.println("error for sqlserver!");
+        }
+
+        // 7、人大金仓数据库
+        // 同postgresql的jdbc-url
+        final Matcher matcher6 = JdbcUrlUtils
+            .getPattern("jdbc:kingbase8://{host}[:{port}]/[{database}][\\?{params}]")
+            .matcher("jdbc:kingbase8://localhost:54321/sample");
+        if (matcher6.matches()) {
+            System.out.println("kingbase8 host:" + matcher6.group("host"));
+            System.out.println("kingbase8 port:" + matcher6.group("port"));
+            System.out.println("kingbase8 database:" + matcher6.group("database"));
+            String params = matcher6.group("params");
+            if (null != params) {
+                String[] pairs = params.split("&");
+                for (String pair : pairs) {
+                    System.out.println("mysql params:" + pair);
+                }
+            }
+        } else {
+            System.out.println("error for kingbase8!");
+        }
+
+        // 8、达梦数据库
+        // jdbc:dm://localhost:5236/user?param=hello
+        final Matcher matcher7 = JdbcUrlUtils.getPattern("jdbc:dm://{host}:{port}[/{database}][\\?{params}]")
+            .matcher("jdbc:dm://localhost:5236");
+        if (matcher7.matches()) {
+            System.out.println("dm host:" + matcher7.group("host"));
+            System.out.println("dm port:" + matcher7.group("port"));
+            System.out.println("dm database:" + matcher7.group("database"));
+            String params = matcher7.group("params");
+            if (null != params) {
+                String[] pairs = params.split("&");
+                for (String pair : pairs) {
+                    System.out.println("dm params:" + pair);
+                }
+            }
+        } else {
+            System.out.println("error for dm!");
+        }
+
+        // 9、DB2数据库
+        // jdbc:db2://localhost:50000/testdb:driverType=4;fullyMaterializeLobData=true;fullyMaterializeInputStreams=true;progressiveStreaming=2;progresssiveLocators=2;
+        final Matcher matcher8 = JdbcUrlUtils.getPattern("jdbc:db2://{host}:{port}/{database}[:{params}]")
+            .matcher("jdbc:db2://localhost:50000/testdb:driverType=4;fullyMaterializeLobData=true");
+        if (matcher8.matches()) {
+            System.out.println("db2 host:" + matcher8.group("host"));
+            System.out.println("db2 port:" + matcher8.group("port"));
+            System.out.println("db2 database:" + matcher8.group("database"));
+            String params = matcher8.group("params");
+            if (null != params) {
+                String[] pairs = params.split(";");
+                for (String pair : pairs) {
+                    System.out.println("mysql params:" + pair);
+                }
+            }
+        } else {
+            System.out.println("error for db2!");
+        }
+
+        // 10、Hive数据库
+        // jdbc:hive2://172.17.2.10:10000/test?useUnicode=true&useSSL=false
+        final Matcher matcher9 = JdbcUrlUtils
+            .getPattern("jdbc:hive2://{host}[:{port}]/[{database}][\\?{params}]")
+            .matcher("jdbc:hive2://127.0.0.1:10000/default?useUnicode=true&useSSL=false");
+        if (matcher9.matches()) {
+            System.out.println("hive host:" + matcher3.group("host"));
+            System.out.println("hive port:" + matcher3.group("port"));
+            System.out.println("hive database:" + matcher3.group("database"));
+            String params = matcher9.group("params");
+            if (null != params) {
+                String[] pairs = params.split("&");
+                for (String pair : pairs) {
+                    System.out.println("mysql params:" + pair);
+                }
+            }
+        } else {
+            System.out.println("error for hive!");
+        }
+
+        // 11、SQLite数据库
+        // jdbc:sqlite:/tmp/phone.db
+        final Matcher matcher10 = JdbcUrlUtils.getPattern("jdbc:sqlite:{file}")
+            .matcher("jdbc:sqlite:D:\\Project\\Test\\phone.db");
+        if (matcher10.matches()) {
+            System.out.println("sqlite file:" + matcher10.group("file"));
+        } else {
+            System.out.println("error for sqlite!");
+        }
+
+        // 12、mongo数据库
+        // jdbc:mongodb://127.0.0.1:27017/admin?authSource=admin&authMechanism=SCRAM-SHA-1
+        final Matcher matcher11 = JdbcUrlUtils.getPattern("jdbc:mongodb://{host}[:{port}]/[{database}][\\?{params}]")
+            .matcher("jdbc:mongodb://127.0.0.1:27017/admin?authSource=admin&authMechanism=SCRAM-SHA-1");
+        if (matcher11.matches()) {
+            System.out.println("mongodb database:" + matcher11.group("database"));
+        } else {
+            System.out.println("error for mongodb!");
+        }
+
+        // 13、ClickHouse数据库
+        // jdbc:clickhouse://127.0.0.1:8123/default
+        final Matcher matcher12 = JdbcUrlUtils.getPattern("jdbc:clickhouse://{host}[:{port}]/[{database}][\\?{params}]")
+            .matcher("jdbc:clickhouse://127.0.0.1:8123/default");
+        if (matcher12.matches()) {
+            System.out.println("clickhouse database:" + matcher12.group("database"));
+        } else {
+            System.out.println("error for clickhouse!");
+        }
+    }
+}

+ 43 - 0
km-modules/km-db/src/main/java/com/km/db/config/DbConfig.java

@@ -0,0 +1,43 @@
+package com.km.db.config;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.ibatis.mapping.DatabaseIdProvider;
+import org.apache.ibatis.mapping.VendorDatabaseIdProvider;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.io.File;
+import java.util.Properties;
+
+/**
+ * @Description: DbConfig
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 15:58
+ */
+@Configuration
+public class DbConfig {
+
+    @Value("${db.configuration.drivers-base-path}")
+    private String driversBasePath;
+
+    public String getDriversBasePath() {
+        if (StringUtils.isBlank(driversBasePath)) {
+            throw new IllegalArgumentException("Invalid configuration parameter:db.configuration.drivers-base-path");
+        }
+        if (driversBasePath.endsWith(File.separator)) {
+            return driversBasePath.substring(0, driversBasePath.length() - 1);
+        }
+        return driversBasePath;
+    }
+
+    @Bean
+    public DatabaseIdProvider getDatabaseIdProvider() {
+        DatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider();
+        Properties props = new Properties();
+        props.setProperty("PostgreSQL", "postgresql");
+        props.setProperty("MySQL", "mysql");
+        databaseIdProvider.setProperties(props);
+        return databaseIdProvider;
+    }
+}

+ 30 - 0
km-modules/km-db/src/main/java/com/km/db/config/data/DbPropertiesConfiguration.java

@@ -0,0 +1,30 @@
+package com.km.db.config.data;
+
+import com.km.common.db.entity.GlobalParamConfigProperties;
+import com.km.common.db.entity.SourceDataSourceProperties;
+import com.km.common.db.entity.TargetDataSourceProperties;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.PropertySource;
+
+/**
+ * @Description: DbPropertiesConfiguration
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "dbswitch")
+@PropertySource(
+    value = {"classpath:config.properties", "classpath:config.yml", "classpath:config.yaml"},
+    ignoreResourceNotFound = true,
+    factory = DbPropertySourceFactory.class)
+public class DbPropertiesConfiguration {
+
+    private SourceDataSourceProperties source = new SourceDataSourceProperties();
+
+    private TargetDataSourceProperties target = new TargetDataSourceProperties();
+
+    private GlobalParamConfigProperties config = new GlobalParamConfigProperties();
+}

+ 42 - 0
km-modules/km-db/src/main/java/com/km/db/config/data/DbPropertySourceFactory.java

@@ -0,0 +1,42 @@
+package com.km.db.config.data;
+
+import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
+import org.springframework.core.env.PropertiesPropertySource;
+import org.springframework.core.env.PropertySource;
+import org.springframework.core.io.support.DefaultPropertySourceFactory;
+import org.springframework.core.io.support.EncodedResource;
+
+import java.io.IOException;
+import java.util.Objects;
+import java.util.Properties;
+
+/**
+ * @Description: DbPropertySourceFactory
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+public class DbPropertySourceFactory extends DefaultPropertySourceFactory {
+
+    private static final String suffixYml = ".yml";
+    private static final String suffixYaml = ".yaml";
+
+    @Override
+    public PropertySource<?> createPropertySource(String name, EncodedResource resource)
+        throws IOException {
+        String sourceName = name != null ? name : resource.getResource().getFilename();
+        if (!resource.getResource().exists()) {
+            assert sourceName != null;
+            return new PropertiesPropertySource(sourceName, new Properties());
+        } else {
+            assert sourceName != null;
+            if (sourceName.endsWith(suffixYml) || sourceName.endsWith(suffixYaml)) {
+                YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
+                factory.setResources(resource.getResource());
+                factory.afterPropertiesSet();
+                return new PropertiesPropertySource(sourceName, Objects.requireNonNull(factory.getObject()));
+            } else {
+                return super.createPropertySource(name, resource);
+            }
+        }
+    }
+}

+ 63 - 0
km-modules/km-db/src/main/java/com/km/db/config/data/DbTaskExecutorConfig.java

@@ -0,0 +1,63 @@
+package com.km.db.config.data;
+
+import com.km.common.db.Utils.DataSourceUtils;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.task.AsyncTaskExecutor;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * @Description: DbTaskExecutorConfig
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+@Configuration("dbTaskExecutorConfig")
+public class DbTaskExecutorConfig {
+
+    public final static String TASK_EXECUTOR_READ_NAME = "tableReadExecutor";
+    public final static String TASK_EXECUTOR_WRITE_NAME = "tableWriteExecutor";
+
+    /**
+     * 创建一个异步读任务执行线程池
+     *
+     * @return ThreadPoolTaskExecutor
+     */
+    @Bean(TASK_EXECUTOR_READ_NAME)
+    public AsyncTaskExecutor createTaskReadeExecutor() {
+        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
+        taskExecutor.setCorePoolSize(DataSourceUtils.MAX_THREAD_COUNT);
+        taskExecutor.setMaxPoolSize(DataSourceUtils.MAX_THREAD_COUNT);
+        taskExecutor.setQueueCapacity(10000);
+        taskExecutor.setKeepAliveSeconds(1800);
+        taskExecutor.setDaemon(true);
+        taskExecutor.setThreadGroupName("dbswitch-reader");
+        taskExecutor.setThreadNamePrefix("dbswitch-read-");
+        taskExecutor.setBeanName(TASK_EXECUTOR_READ_NAME);
+        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+        taskExecutor.initialize();
+        return taskExecutor;
+    }
+
+    /**
+     * 创建一个异步写任务执行线程池
+     *
+     * @return ThreadPoolTaskExecutor
+     */
+    @Bean(TASK_EXECUTOR_WRITE_NAME)
+    public AsyncTaskExecutor createTaskWriteExecutor() {
+        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
+        taskExecutor.setCorePoolSize(DataSourceUtils.MAX_THREAD_COUNT);
+        taskExecutor.setMaxPoolSize(DataSourceUtils.MAX_THREAD_COUNT);
+        taskExecutor.setQueueCapacity(10000);
+        taskExecutor.setKeepAliveSeconds(1800);
+        taskExecutor.setDaemon(true);
+        taskExecutor.setThreadGroupName("dbswitch-writer");
+        taskExecutor.setThreadNamePrefix("dbswitch-write-");
+        taskExecutor.setBeanName(TASK_EXECUTOR_WRITE_NAME);
+        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+        taskExecutor.initialize();
+        return taskExecutor;
+    }
+}

+ 132 - 0
km-modules/km-db/src/main/java/com/km/db/config/register/ProductRegisterAutoConfiguration.java

@@ -0,0 +1,132 @@
+package com.km.db.config.register;
+
+import com.km.common.db.annotation.Product;
+import com.km.common.db.constant.DbConstants;
+import com.km.common.db.enums.DbProductTypeEnum;
+import com.km.common.db.provider.IProductFactoryProvider;
+import com.km.common.db.provider.ProductProviderFactory;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.BeanClassLoaderAware;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.context.annotation.Configuration;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ServiceConfigurationError;
+import java.util.Set;
+
+/**
+ * @Description: ProductRegisterAutoConfiguration
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+@Slf4j
+@Configuration
+@ConditionalOnClass(ProductProviderFactory.class)
+public class ProductRegisterAutoConfiguration implements InitializingBean, BeanClassLoaderAware {
+
+    private static final Set<String> providers = new HashSet<>();
+
+    private ClassLoader classLoader;
+
+    private int parseLine(BufferedReader reader, int lc, List<String> names)
+        throws IOException, ServiceConfigurationError {
+        String ln = reader.readLine();
+        if (ln == null) {
+            return -1;
+        }
+        int ci = ln.indexOf('#');
+        if (ci >= 0) {
+            ln = ln.substring(0, ci);
+        }
+        ln = ln.trim();
+        int n = ln.length();
+        if (n != 0) {
+            if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0)) {
+                log.error("Illegal configuration-file syntax: {}", ln);
+            }
+            int cp = ln.codePointAt(0);
+            if (!Character.isJavaIdentifierStart(cp)) {
+                log.error("Illegal provider-class name: {}", ln);
+            }
+            for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
+                cp = ln.codePointAt(i);
+                if (!Character.isJavaIdentifierPart(cp) && (cp != '.')) {
+                    log.error("Illegal provider-class name: {}", ln);
+                }
+            }
+            if (!providers.contains(ln) && !names.contains(ln)) {
+                names.add(ln);
+            }
+        }
+        return lc + 1;
+    }
+
+    private List<String> parse(URL url) throws ServiceConfigurationError {
+        InputStream in = null;
+        BufferedReader reader = null;
+        ArrayList<String> names = new ArrayList<>();
+        try {
+            in = url.openStream();
+            reader = new BufferedReader(new InputStreamReader(in, "utf-8"));
+            int lc = 1;
+            while ((lc = parseLine(reader, lc, names)) >= 0) {
+            }
+        } catch (IOException x) {
+            log.error("Error reading configuration file", x);
+        } finally {
+            try {
+                if (reader != null) {
+                    reader.close();
+                }
+                if (in != null) {
+                    in.close();
+                }
+            } catch (IOException y) {
+                log.error("Error closing configuration file", y);
+            }
+        }
+        return names;
+    }
+
+    @Override
+    public void setBeanClassLoader(ClassLoader classLoader) {
+        this.classLoader = classLoader;
+    }
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        log.info("Register database product now ...");
+        ClassLoader loader = (null != classLoader)
+            ? classLoader
+            : ProductProviderFactory.class.getClassLoader();
+        Enumeration<URL> resources = loader.getResources(DbConstants.SPI_FILE);
+        while (resources.hasMoreElements()) {
+            URL url = resources.nextElement();
+            providers.addAll(parse(url));
+        }
+        int totalCount = 0;
+        for (String className : providers) {
+            Class<?> aClass = classLoader.loadClass(className);
+            if (IProductFactoryProvider.class.isAssignableFrom(aClass)) {
+                if (aClass.isAnnotationPresent(Product.class)) {
+                    Product annotation = aClass.getAnnotation(Product.class);
+                    DbProductTypeEnum productType = annotation.value();
+                    if (null != productType) {
+                        ProductProviderFactory.register(productType, className);
+                        ++totalCount;
+                    }
+                }
+            }
+        }
+        log.info("Finish to register total {} database product !", totalCount);
+    }
+}

+ 43 - 0
km-modules/km-db/src/main/java/com/km/db/controller/DatabaseConnectionController.java

@@ -3,12 +3,16 @@ package com.km.db.controller;
 import cn.dev33.satoken.annotation.SaCheckPermission;
 import com.km.common.core.core.domain.CommonResult;
 import com.km.common.core.core.page.PageResult;
+import com.km.common.db.enums.DbProductTypeEnum;
 import com.km.common.log.annotation.Log;
 import com.km.common.log.enums.BusinessType;
 import com.km.common.web.annotation.RepeatSubmit;
 import com.km.common.web.core.BaseController;
 import com.km.db.domain.bo.DatabaseConnectionBo;
 import com.km.db.domain.vo.DatabaseConnectionVo;
+import com.km.db.domain.vo.DatabaseTypeDetailVo;
+import com.km.db.domain.vo.DatabaseTypeDriverVo;
+import com.km.db.domain.vo.DbConnectionNameVo;
 import com.km.db.service.IDatabaseConnectionService;
 import jakarta.annotation.Resource;
 import lombok.RequiredArgsConstructor;
@@ -22,6 +26,8 @@ import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import java.util.List;
+
 /**
  * 数据库链接Controller
  *
@@ -54,6 +60,34 @@ public class DatabaseConnectionController extends BaseController {
         return CommonResult.success(databaseConnectionService.selectById(id));
     }
 
+    /**
+     * 查询数据库产品类型
+     */
+    @SaCheckPermission("db:connection:list")
+    @GetMapping("/types")
+    public CommonResult<List<DatabaseTypeDetailVo>> getTypes() {
+        return CommonResult.success(databaseConnectionService.getTypes());
+    }
+
+    /**
+     * 测试数据库连接
+     */
+    @SaCheckPermission("db:connection:query")
+    @GetMapping(value = "test/{id}")
+    public CommonResult<Void> testConnection(@PathVariable("id") Long id) {
+        boolean flag = databaseConnectionService.testConnection(id);
+        return flag ? CommonResult.success() : CommonResult.fail();
+    }
+
+    /**
+     * 获取驱动
+     */
+    @SaCheckPermission("db:connection:query")
+    @GetMapping(value = "/drivers/{type}")
+    public CommonResult<List<DatabaseTypeDriverVo>> getDrivers(@PathVariable("type") DbProductTypeEnum type) {
+        return CommonResult.success(databaseConnectionService.getDrivers(type));
+    }
+
     /**
      * 新增数据库链接
      */
@@ -97,4 +131,13 @@ public class DatabaseConnectionController extends BaseController {
         }
         return CommonResult.success();
     }
+
+    /**
+     * 获取驱动
+     */
+    @SaCheckPermission("db:connection:query")
+    @GetMapping(value = "/list/name")
+    public CommonResult<List<DbConnectionNameVo>> getNameList() {
+        return CommonResult.success(databaseConnectionService.getNameList());
+    }
 }

+ 74 - 0
km-modules/km-db/src/main/java/com/km/db/controller/MetaDataController.java

@@ -0,0 +1,74 @@
+package com.km.db.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.km.common.core.core.domain.CommonResult;
+import com.km.common.core.core.page.PageResult;
+import com.km.common.web.core.BaseController;
+import com.km.db.domain.bo.DatabaseConnectionBo;
+import com.km.db.domain.vo.DatabaseConnectionVo;
+import com.km.db.domain.vo.MetadataSchemaDetailVo;
+import com.km.db.domain.vo.MetadataTableDetailVo;
+import com.km.db.domain.vo.MetadataTableInfoVo;
+import com.km.db.domain.vo.SchemaTableDataVo;
+import com.km.db.service.impl.MetaDataService;
+import jakarta.annotation.Resource;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * @Description: MetaDataController
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/db/metadata")
+public class MetaDataController extends BaseController {
+
+    @Resource
+    private MetaDataService metaDataService;
+
+    /**
+     * 查询数据库allSchemas
+     */
+    @SaCheckPermission("db:connection:list")
+    @GetMapping("/schemas/{id}")
+    public CommonResult<List<MetadataSchemaDetailVo>> allSchemas(@PathVariable("id") Long id) {
+        return CommonResult.success(metaDataService.allSchemas(id));
+    }
+
+    /**
+     * 查询数据库allTables
+     */
+    @SaCheckPermission("db:connection:list")
+    @GetMapping("/tables/{id}")
+    public CommonResult<List<MetadataTableInfoVo>> allTables(@PathVariable("id") Long id, @RequestParam("schema") String schema) {
+        return CommonResult.success(metaDataService.allTables(id, schema));
+    }
+
+    /**
+     * 查询数据库tableMeta
+     */
+    @SaCheckPermission("db:connection:list")
+    @GetMapping("/meta/table/{id}")
+    public CommonResult<MetadataTableDetailVo> tableMeta(@PathVariable("id") Long id, @RequestParam("schema") String schema, @RequestParam("table") String table) {
+        return CommonResult.success(metaDataService.tableDetail(id, schema,table));
+    }
+
+    /**
+     * 查询数据库allTables
+     */
+    @SaCheckPermission("db:connection:list")
+    @GetMapping("/data/table/{id}")
+    public CommonResult<SchemaTableDataVo> tableData(@PathVariable("id") Long id, @RequestParam("schema") String schema, @RequestParam("table") String table) {
+        return CommonResult.success(metaDataService.tableData(id, schema, table));
+    }
+}

+ 13 - 1
km-modules/km-db/src/main/java/com/km/db/domain/DatabaseConnection.java

@@ -1,10 +1,17 @@
 package com.km.db.domain;
 
+import com.km.common.db.enums.DbProductTypeEnum;
 import com.km.common.orm.core.domain.BaseEntity;
+import com.mybatisflex.annotation.Column;
 import com.mybatisflex.annotation.Id;
 import com.mybatisflex.annotation.Table;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+import org.apache.ibatis.type.EnumTypeHandler;
 
 import java.io.Serial;
 
@@ -17,6 +24,10 @@ import java.io.Serial;
 @Data
 @EqualsAndHashCode(callSuper = true)
 @Table(value = "database_connection")
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@Accessors(chain = true)
 public class DatabaseConnection extends BaseEntity {
     @Serial
     private static final long serialVersionUID = 1L;
@@ -35,7 +46,8 @@ public class DatabaseConnection extends BaseEntity {
     /**
      * 数据库类型
      */
-    private String type;
+    @Column(value = "type", typeHandler = EnumTypeHandler.class)
+    private DbProductTypeEnum type;
 
     /**
      * 驱动版本

+ 22 - 0
km-modules/km-db/src/main/java/com/km/db/domain/vo/DatabaseTypeDetailVo.java

@@ -0,0 +1,22 @@
+package com.km.db.domain.vo;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @Description: DatabaseTypeDetailVo
+ *
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 11:39
+ */
+@NoArgsConstructor
+@Data
+public class DatabaseTypeDetailVo {
+    private long id;
+
+    private String type;
+
+    private String driver;
+
+    private String sample;
+}

+ 22 - 0
km-modules/km-db/src/main/java/com/km/db/domain/vo/DatabaseTypeDriverVo.java

@@ -0,0 +1,22 @@
+package com.km.db.domain.vo;
+
+import lombok.Builder;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.util.List;
+
+/**
+ * @Description: DatabaseTypeDriverVo
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+@Data
+@Builder
+@Accessors(chain = true)
+public class DatabaseTypeDriverVo {
+    private String driverVersion;
+    private String driverClass;
+    private String driverPath;
+    private List<String> jarFiles;
+}

+ 20 - 0
km-modules/km-db/src/main/java/com/km/db/domain/vo/DbConnectionNameVo.java

@@ -0,0 +1,20 @@
+package com.km.db.domain.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @Description: DbConnectionNameVo
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class DbConnectionNameVo {
+    private Long id;
+
+    private String name;
+}

+ 25 - 0
km-modules/km-db/src/main/java/com/km/db/domain/vo/MetadataColumnDetailVo.java

@@ -0,0 +1,25 @@
+package com.km.db.domain.vo;
+
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * @Description: MetadataColumnDetailVo
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+@Data
+@Builder
+public class MetadataColumnDetailVo {
+    private String fieldName;
+    private String typeName;
+    private String typeClassName;
+    private String fieldType;
+    private String displaySize;
+    private String scaleSize;
+    private String precisionSize;
+    private String isPrimaryKey;
+    private String isAutoIncrement;
+    private String isNullable;
+    private String remarks;
+}

+ 18 - 0
km-modules/km-db/src/main/java/com/km/db/domain/vo/MetadataSchemaDetailVo.java

@@ -0,0 +1,18 @@
+package com.km.db.domain.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @Description: MetadataSchemaDetailResponseVo
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class MetadataSchemaDetailVo {
+    private String connection;
+    private String schema;
+}

+ 25 - 0
km-modules/km-db/src/main/java/com/km/db/domain/vo/MetadataTableDetailVo.java

@@ -0,0 +1,25 @@
+package com.km.db.domain.vo;
+
+import com.km.common.db.schema.IndexDescription;
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @Description: MetadataTableDetailVo
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+@Data
+@Builder
+public class MetadataTableDetailVo {
+    private String tableName;
+    private String schemaName;
+    private String remarks;
+    private String type;
+    private String createSql;
+    private List<String> primaryKeys;
+    private List<MetadataColumnDetailVo> columns;
+    private List<IndexDescription> indexes;
+}

+ 18 - 0
km-modules/km-db/src/main/java/com/km/db/domain/vo/MetadataTableInfoVo.java

@@ -0,0 +1,18 @@
+package com.km.db.domain.vo;
+
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * @Description: MetadataTableInfoVo
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+@Data
+@Builder
+public class MetadataTableInfoVo {
+    private String tableName;
+    private String schemaName;
+    private String remarks;
+    private String type;
+}

+ 21 - 0
km-modules/km-db/src/main/java/com/km/db/domain/vo/SchemaTableDataVo.java

@@ -0,0 +1,21 @@
+package com.km.db.domain.vo;
+
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @Description: SchemaTableDataVo
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+@Data
+@Builder
+public class SchemaTableDataVo {
+    private String schemaName;
+    private String tableName;
+    private List<String> columns;
+    private List<Map<String,Object>> rows;
+}

+ 51 - 0
km-modules/km-db/src/main/java/com/km/db/product/dm/DmFactoryProvider.java

@@ -0,0 +1,51 @@
+package com.km.db.product.dm;
+
+import com.km.common.db.annotation.Product;
+import com.km.common.db.enums.DbProductTypeEnum;
+import com.km.common.db.provider.AbstractFactoryProvider;
+import com.km.common.db.provider.AutoCastTableDataSynchronizeProvider;
+import com.km.common.db.provider.IMetadataProvider;
+import com.km.common.db.provider.IProductFeatures;
+import com.km.common.db.provider.ITableDataSynchronizeProvider;
+import com.km.common.db.provider.ITableDataWriteProvider;
+import com.km.common.db.provider.ITableManageProvider;
+import com.km.db.product.oracle.OracleTableManageProvider;
+
+import javax.sql.DataSource;
+
+/**
+ * @Description: DmFactoryProvider
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+@Product(DbProductTypeEnum.DM)
+public class DmFactoryProvider extends AbstractFactoryProvider {
+
+    public DmFactoryProvider(DataSource dataSource) {
+        super(dataSource);
+    }
+
+    public IProductFeatures getProductFeatures() {
+        return new DmFeatures();
+    }
+
+    @Override
+    public IMetadataProvider createMetadataQueryProvider() {
+        return new DmMetadataQueryProvider(this);
+    }
+
+    @Override
+    public ITableDataWriteProvider createTableDataWriteProvider(boolean useInsert) {
+        return new DmTableDataWriteProvider(this);
+    }
+
+    @Override
+    public ITableManageProvider createTableManageProvider() {
+        return new OracleTableManageProvider(this);
+    }
+
+    @Override
+    public ITableDataSynchronizeProvider createTableDataSynchronizeProvider() {
+        return new AutoCastTableDataSynchronizeProvider(this);
+    }
+}

+ 11 - 0
km-modules/km-db/src/main/java/com/km/db/product/dm/DmFeatures.java

@@ -0,0 +1,11 @@
+package com.km.db.product.dm;
+
+import com.km.common.db.provider.IProductFeatures;
+
+/**
+ * @Description: DmFeatures
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+public class DmFeatures implements IProductFeatures {
+}

+ 194 - 0
km-modules/km-db/src/main/java/com/km/db/product/dm/DmMetadataQueryProvider.java

@@ -0,0 +1,194 @@
+package com.km.db.product.dm;
+
+import com.km.common.db.constant.DbConstants;
+import com.km.common.db.provider.AbstractMetadataProvider;
+import com.km.common.db.provider.IProductFactoryProvider;
+import com.km.common.db.schema.ColumnDescription;
+import com.km.common.db.schema.ColumnMetaData;
+import com.km.common.db.schema.TableDescription;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @Description: DmMetadataQueryProvider
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+@Slf4j
+public class DmMetadataQueryProvider extends AbstractMetadataProvider {
+
+    private static final String SHOW_CREATE_TABLE_SQL =
+        "SELECT DBMS_METADATA.GET_DDL('TABLE','%s','%s') FROM DUAL ";
+    private static final String SHOW_CREATE_VIEW_SQL =
+        "SELECT DBMS_METADATA.GET_DDL('VIEW','%s','%s') FROM DUAL ";
+
+
+    public DmMetadataQueryProvider(IProductFactoryProvider factoryProvider) {
+        super(factoryProvider);
+    }
+
+    @Override
+    public String getTableDDL(Connection connection, String schemaName, String tableName) {
+        String sql = String.format(SHOW_CREATE_TABLE_SQL, tableName, schemaName);
+        try (Statement st = connection.createStatement()) {
+            if (st.execute(sql)) {
+                try (ResultSet rs = st.getResultSet()) {
+                    if (rs != null && rs.next()) {
+                        return rs.getString(1);
+                    }
+                }
+            }
+        } catch (SQLException e) {
+            throw new RuntimeException(e);
+        }
+
+        return null;
+    }
+
+    @Override
+    public String getViewDDL(Connection connection, String schemaName, String tableName) {
+        String sql = String.format(SHOW_CREATE_VIEW_SQL, tableName, schemaName);
+        try (Statement st = connection.createStatement()) {
+            if (st.execute(sql)) {
+                try (ResultSet rs = st.getResultSet()) {
+                    if (rs != null && rs.next()) {
+                        return rs.getString(1);
+                    }
+                }
+            }
+        } catch (SQLException e) {
+            throw new RuntimeException(e);
+        }
+
+        return null;
+    }
+
+    @Override
+    public List<ColumnDescription> querySelectSqlColumnMeta(Connection connection, String sql) {
+        String querySQL = String.format(
+            "SELECT * from (%s) tmp where ROWNUM<=1 ",
+            sql.replace(";", ""));
+        return this.getSelectSqlColumnMeta(connection, querySQL);
+    }
+
+    @Override
+    public void testQuerySQL(Connection connection, String sql) {
+        String testQuerySql = String.format("explain %s", sql.replace(";", ""));
+        if (log.isDebugEnabled()) {
+            log.debug("Execute sql :{}", testQuerySql);
+        }
+        try (Statement st = connection.createStatement()) {
+            st.execute(testQuerySql);
+        } catch (SQLException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+
+    /**
+     * https://eco.dameng.com/document/dm/zh-cn/sql-dev/dmpl-sql-datatype.html
+     * <p>
+     * https://eco.dameng.com/document/dm/zh-cn/pm/dm8_sql-data-types-operators.html
+     * <p>
+     * 违反表[xxx]唯一性约束: https://www.cnblogs.com/theli/p/12858875.html
+     */
+    @Override
+    public String getFieldDefinition(ColumnMetaData v, List<String> pks, boolean useAutoInc,
+                                     boolean addCr, boolean withRemarks) {
+        String fieldName = v.getName();
+        int length = v.getLength();
+        int precision = v.getPrecision();
+
+        StringBuilder builder = new StringBuilder(128);
+        builder.append(" \"").append(fieldName).append("\"    ");
+
+        int type = v.getType();
+        switch (type) {
+            case ColumnMetaData.TYPE_TIMESTAMP:
+            case ColumnMetaData.TYPE_TIME:
+                builder.append("TIMESTAMP");
+                break;
+            case ColumnMetaData.TYPE_DATE:
+                builder.append("DATE");
+                break;
+            case ColumnMetaData.TYPE_BOOLEAN:
+                builder.append("BIT");
+                break;
+            case ColumnMetaData.TYPE_NUMBER:
+            case ColumnMetaData.TYPE_BIGNUMBER:
+                if (null != pks && !pks.isEmpty() && pks.contains(fieldName)) {
+                    builder.append("BIGINT");
+                } else {
+                    builder.append("NUMERIC");
+                    if (length > 0) {
+                        if (length > 38) {
+                            length = 38;
+                        }
+
+                        builder.append('(').append(length);
+                        if (precision > 0) {
+                            builder.append(", ").append(precision);
+                        }
+                        builder.append(')');
+                    }
+                }
+                break;
+            case ColumnMetaData.TYPE_INTEGER:
+                builder.append("BIGINT");
+                break;
+            case ColumnMetaData.TYPE_STRING:
+                if (null != pks && pks.contains(fieldName)) {
+                    builder.append("VARCHAR(" + length + ")");
+                } else if (length > 0 && length < 1900) {
+                    // 最大存储长度由数据库页面大小决定,支持按照字节存放字符串,数据库页面大小与实际最大存储长度的关系为:
+                    // 4K->1900;8k->3900;16k->8000;32k->8188
+                    builder.append("VARCHAR(").append(length).append(')');
+                } else {
+                    builder.append("TEXT");
+                }
+                break;
+            case ColumnMetaData.TYPE_BINARY:
+                builder.append("BLOB");
+                break;
+            default:
+                builder.append("CLOB");
+                break;
+        }
+
+        if (addCr) {
+            builder.append(DbConstants.CR);
+        }
+
+        return builder.toString();
+    }
+
+    @Override
+    public List<String> getTableColumnCommentDefinition(TableDescription td,
+                                                        List<ColumnDescription> cds) {
+        List<String> results = new ArrayList<>();
+        if (StringUtils.isNotBlank(td.getRemarks())) {
+            results.add(String
+                .format("COMMENT ON TABLE \"%s\".\"%s\" IS '%s' ",
+                    td.getSchemaName(), td.getTableName(),
+                    td.getRemarks().replace("'", "")));
+        }
+
+        for (ColumnDescription cd : cds) {
+            if (StringUtils.isNotBlank(cd.getRemarks())) {
+                results.add(String
+                    .format("COMMENT ON COLUMN \"%s\".\"%s\".\"%s\" IS '%s' ",
+                        td.getSchemaName(), td.getTableName(), cd.getFieldName(),
+                        cd.getRemarks().replace("'", "")));
+            }
+        }
+
+        return results;
+    }
+}

+ 29 - 0
km-modules/km-db/src/main/java/com/km/db/product/dm/DmTableDataWriteProvider.java

@@ -0,0 +1,29 @@
+package com.km.db.product.dm;
+
+import com.km.common.db.Utils.ObjectCastUtils;
+import com.km.common.db.provider.DefaultTableDataWriteProvider;
+import com.km.common.db.provider.IProductFactoryProvider;
+
+import java.util.List;
+
+/**
+ * @Description: DmTableDataWriteProvider
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+public class DmTableDataWriteProvider extends DefaultTableDataWriteProvider {
+
+    public DmTableDataWriteProvider(IProductFactoryProvider factoryProvider) {
+        super(factoryProvider);
+    }
+
+    @Override
+    public long write(List<String> fieldNames, List<Object[]> recordValues) {
+        recordValues.parallelStream().forEach((Object[] row) -> {
+            for (int i = 0; i < row.length; ++i) {
+                row[i] = ObjectCastUtils.castByDetermine(row[i]);
+            }
+        });
+        return super.write(fieldNames, recordValues);
+    }
+}

+ 22 - 0
km-modules/km-db/src/main/java/com/km/db/product/oracle/OracleTableManageProvider.java

@@ -0,0 +1,22 @@
+package com.km.db.product.oracle;
+
+import com.km.common.db.provider.DefaultTableManageProvider;
+import com.km.common.db.provider.IProductFactoryProvider;
+
+/**
+ * @Description: OracleTableManageProvider
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+public class OracleTableManageProvider extends DefaultTableManageProvider {
+
+    public OracleTableManageProvider(IProductFactoryProvider factoryProvider) {
+        super(factoryProvider);
+    }
+
+    @Override
+    public void dropTable(String schemaName, String tableName) {
+        String sql = String.format("DROP TABLE \"%s\".\"%s\" CASCADE CONSTRAINTS", schemaName, tableName);
+        this.executeSql(sql);
+    }
+}

+ 90 - 0
km-modules/km-db/src/main/java/com/km/db/service/DriverLoadService.java

@@ -0,0 +1,90 @@
+package com.km.db.service;
+
+import com.km.common.db.enums.DbProductTypeEnum;
+import com.km.db.config.DbConfig;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.ArrayUtils;
+import org.springframework.boot.context.event.ApplicationReadyEvent;
+import org.springframework.context.event.EventListener;
+import org.springframework.stereotype.Service;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * @Description: DriverLoadService
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6 15:56
+ */
+@Slf4j
+@Service
+public class DriverLoadService {
+
+    private Map<DbProductTypeEnum, Map<String, File>> drivers = new EnumMap<>(DbProductTypeEnum.class);
+
+    @Resource
+    private DbConfig dbConfig;
+
+    @EventListener(ApplicationReadyEvent.class)
+    public void loadDrivers() {
+        try {
+            doLoadDrivers();
+            log.info("Finish load jdbc drivers from local path: {}", dbConfig.getDriversBasePath());
+        } catch (Exception e) {
+            log.error("load drivers failed:{}", e.getMessage(), e);
+            throw e;
+        }
+    }
+
+    private void doLoadDrivers() {
+        String driversBasePath = dbConfig.getDriversBasePath();
+        File file = new File(driversBasePath);
+        File[] types = file.listFiles();
+        if (ArrayUtils.isEmpty(types)) {
+            throw new IllegalArgumentException(
+                "No drivers type found from path:" + driversBasePath);
+        }
+        assert types != null;
+        for (File type : types) {
+            if (!DbProductTypeEnum.exists(type.getName())) {
+                continue;
+            }
+            File[] driverVersions = type.listFiles();
+            if (ArrayUtils.isEmpty(driverVersions)) {
+                throw new IllegalArgumentException(
+                    "No driver version found from path:" + type.getAbsolutePath());
+            }
+            assert driverVersions != null;
+            for (File driverVersion : driverVersions) {
+                if (ArrayUtils.isEmpty(driverVersion.listFiles())) {
+                    throw new IllegalArgumentException(
+                        "No driver version jar file found from path:" + driverVersion.getAbsolutePath());
+                }
+                DbProductTypeEnum typeEnum = DbProductTypeEnum.of(type.getName());
+                Map<String, File> versionMap = drivers.computeIfAbsent(typeEnum, k -> new HashMap<>());
+                versionMap.put(driverVersion.getName(), driverVersion);
+                log.info("Load driver for {} ,version:{},path:{}",
+                    typeEnum.getName(), driverVersion.getName(), driverVersion.getAbsolutePath());
+            }
+        }
+    }
+
+    public List<String> getDriverVersion(DbProductTypeEnum dbTypeEnum) {
+        return new ArrayList<>(Optional.ofNullable(drivers.get(dbTypeEnum)).orElseGet(HashMap::new)
+                .keySet());
+    }
+
+    public Map<String, File> getDriverVersionWithPath(DbProductTypeEnum dbTypeEnum) {
+        return Optional.ofNullable(drivers.get(dbTypeEnum)).orElse(new HashMap<>());
+    }
+
+    public File getVersionDriverFile(DbProductTypeEnum dbTypeEnum, String driverVersion) {
+        return drivers.get(dbTypeEnum).get(driverVersion);
+    }
+}

+ 23 - 0
km-modules/km-db/src/main/java/com/km/db/service/IDatabaseConnectionService.java

@@ -3,10 +3,15 @@ package com.km.db.service;
 import java.util.List;
 
 import com.km.common.core.core.page.PageResult;
+import com.km.common.db.enums.DbProductTypeEnum;
+import com.km.common.db.service.IMetadataService;
 import com.km.db.domain.DatabaseConnection;
 import com.km.db.domain.vo.DatabaseConnectionVo;
 import com.km.db.domain.bo.DatabaseConnectionBo;
 import com.km.common.orm.core.service.IBaseService;
+import com.km.db.domain.vo.DatabaseTypeDetailVo;
+import com.km.db.domain.vo.DatabaseTypeDriverVo;
+import com.km.db.domain.vo.DbConnectionNameVo;
 
 /**
  * 数据库链接Service接口
@@ -15,6 +20,8 @@ import com.km.common.orm.core.service.IBaseService;
  * 2024-05-04
  */
 public interface IDatabaseConnectionService extends IBaseService<DatabaseConnection> {
+
+    IMetadataService getMetaDataCoreService(DatabaseConnection dbConn);
     /**
      * 查询数据库链接
      *
@@ -63,4 +70,20 @@ public interface IDatabaseConnectionService extends IBaseService<DatabaseConnect
      */
     boolean deleteByIds(Long[] ids);
 
+    /**
+     * 测试数据库连接是否成功
+     *
+     * @return 结果:true 成功,false 失败
+     */
+    boolean testConnection(Long id);
+
+    /**
+     * getTypes 获取数据库类型
+     *
+     * @return: java.util.List<com.km.db.domain.vo.DatabaseConnectionVo>
+     **/
+    List<DatabaseTypeDetailVo> getTypes();
+    List<DatabaseTypeDriverVo> getDrivers(DbProductTypeEnum type);
+    List<DbConnectionNameVo> getNameList();
+
 }

+ 101 - 7
km-modules/km-db/src/main/java/com/km/db/service/impl/DatabaseConnectionServiceImpl.java

@@ -1,22 +1,41 @@
 package com.km.db.service.impl;
 
+import cn.hutool.core.io.FileUtil;
 import cn.hutool.core.util.ObjectUtil;
 import com.km.common.core.core.page.PageResult;
+import com.km.common.core.exception.ServiceException;
 import com.km.common.core.utils.MapstructUtils;
+import com.km.common.db.Utils.DataSourceUtils;
+import com.km.common.db.entity.CloseableDataSource;
+import com.km.common.db.enums.DbProductTypeEnum;
+import com.km.common.db.service.DefaultMetadataService;
+import com.km.common.db.service.IMetadataService;
 import com.km.common.orm.core.page.PageQuery;
 import com.km.common.orm.core.service.impl.BaseServiceImpl;
+import com.km.db.Utils.JdbcUrlUtils;
 import com.km.db.domain.DatabaseConnection;
 import com.km.db.domain.bo.DatabaseConnectionBo;
 import com.km.db.domain.vo.DatabaseConnectionVo;
+import com.km.db.domain.vo.DatabaseTypeDetailVo;
+import com.km.db.domain.vo.DatabaseTypeDriverVo;
+import com.km.db.domain.vo.DbConnectionNameVo;
 import com.km.db.mapper.DatabaseConnectionMapper;
+import com.km.db.service.DriverLoadService;
 import com.km.db.service.IDatabaseConnectionService;
 import com.mybatisflex.core.paginate.Page;
 import com.mybatisflex.core.query.QueryWrapper;
+import jakarta.annotation.Resource;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.io.File;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.stream.Collectors;
 
 import static com.km.db.domain.table.DatabaseConnectionTableDef.DATABASE_CONNECTION;
 
@@ -28,6 +47,40 @@ import static com.km.db.domain.table.DatabaseConnectionTableDef.DATABASE_CONNECT
  */
 @Service
 public class DatabaseConnectionServiceImpl extends BaseServiceImpl<DatabaseConnectionMapper, DatabaseConnection> implements IDatabaseConnectionService {
+    @Resource
+    private DriverLoadService driverLoadService;
+
+    @Override
+    public IMetadataService getMetaDataCoreService(DatabaseConnection dbConn) {
+        String typeName = dbConn.getType().getName().toUpperCase();
+        DbProductTypeEnum supportDbType = DbProductTypeEnum.valueOf(typeName);
+        if (supportDbType.hasAddress()) {
+            for (String pattern : supportDbType.getUrl()) {
+                final Matcher matcher = JdbcUrlUtils.getPattern(pattern).matcher(dbConn.getUrl());
+                if (!matcher.matches()) {
+                    if (1 == supportDbType.getUrl().length) {
+                        throw new ServiceException("远程地址不可连接:{}", dbConn.getName());
+                    } else {
+                        continue;
+                    }
+                }
+
+                String host = matcher.group("host");
+                String port = matcher.group("port");
+                if (StringUtils.isBlank(port)) {
+                    port = String.valueOf(supportDbType.getPort());
+                }
+                if (!JdbcUrlUtils.reachable(host, port)) {
+                    throw new ServiceException("远程地址不可连接:{}", dbConn.getName());
+                }
+            }
+        }
+        File driverVersionFile = driverLoadService.getVersionDriverFile(dbConn.getType(), dbConn.getDriverVersion());
+        String driverPath = driverVersionFile.getAbsolutePath();
+        CloseableDataSource dataSource = DataSourceUtils.createCommonDataSource(dbConn.getUrl(), dbConn.getDriver(),
+            driverPath, dbConn.getUserName(), dbConn.getPassword());
+        return new DefaultMetadataService(dataSource);
+    }
 
     @Override
     public QueryWrapper query() {
@@ -50,7 +103,6 @@ public class DatabaseConnectionServiceImpl extends BaseServiceImpl<DatabaseConne
             (databaseConnectionBo.getUserName()));
         queryWrapper.and(DATABASE_CONNECTION.PASSWORD.eq
             (databaseConnectionBo.getPassword()));
-
         return queryWrapper;
     }
 
@@ -118,16 +170,58 @@ public class DatabaseConnectionServiceImpl extends BaseServiceImpl<DatabaseConne
         return false;
     }
 
-    /**
-     * 批量删除数据库链接
-     *
-     * @param ids 需要删除的数据库链接主键集合
-     * @return 结果:true 删除成功,false 删除失败
-     */
     @Transactional
     @Override
     public boolean deleteByIds(Long[] ids) {
         return this.removeByIds(Arrays.asList(ids));
     }
 
+    @Override
+    public boolean testConnection(Long id) {
+        DatabaseConnectionVo connectionVo = selectById(id);
+        if (ObjectUtil.isEmpty(connectionVo)) {
+            throw new ServiceException("资源不存在!id:{}");
+        }
+        DatabaseConnection dbConn = MapstructUtils.convert(connectionVo, DatabaseConnection.class);
+        assert dbConn != null;
+        IMetadataService metaDataService = getMetaDataCoreService(dbConn);
+        try {
+            metaDataService.testQuerySql(dbConn.getType().getSql());
+        } finally {
+            metaDataService.close();
+        }
+        return true;
+    }
+
+    @Override
+    public List<DatabaseTypeDetailVo> getTypes() {
+        return Arrays.stream(DbProductTypeEnum.values()).map(type -> {
+            DatabaseTypeDetailVo connectionVo = new DatabaseTypeDetailVo();
+            connectionVo.setId(type.getId());
+            connectionVo.setType(type.getName().toUpperCase(Locale.ROOT));
+            connectionVo.setDriver(type.getDriver());
+            connectionVo.setSample(type.getSample());
+            return connectionVo;
+        }).toList();
+    }
+
+    @Override
+    public List<DatabaseTypeDriverVo> getDrivers(DbProductTypeEnum type) {
+        List<DatabaseTypeDriverVo> typeDrivers = new ArrayList<>();
+        driverLoadService.getDriverVersionWithPath(type).forEach((k, v) -> typeDrivers.add(DatabaseTypeDriverVo.builder()
+            .driverVersion(k)
+            .driverClass(type.getDriver())
+            .driverPath(v.getAbsolutePath())
+            .jarFiles(
+                FileUtil.listFileNames(v.getAbsolutePath())
+            )
+            .build()));
+        return typeDrivers;
+    }
+    @Override
+    public List<DbConnectionNameVo> getNameList() {
+        QueryWrapper queryWrapper = super.buildBaseQueryWrapper();
+        List<DatabaseConnectionVo> list = this.listAs(queryWrapper, DatabaseConnectionVo.class);
+        return list.stream().map(vo -> new DbConnectionNameVo(vo.getId(), vo.getName())).collect(Collectors.toList());
+    }
 }

+ 158 - 0
km-modules/km-db/src/main/java/com/km/db/service/impl/MetaDataService.java

@@ -0,0 +1,158 @@
+package com.km.db.service.impl;
+
+import com.km.common.core.utils.MapstructUtils;
+import com.km.common.db.schema.SchemaTableData;
+import com.km.common.db.schema.SchemaTableMeta;
+import com.km.common.db.schema.TableDescription;
+import com.km.common.db.service.IMetadataService;
+import com.km.db.domain.DatabaseConnection;
+import com.km.db.domain.vo.DatabaseConnectionVo;
+import com.km.db.domain.vo.MetadataColumnDetailVo;
+import com.km.db.domain.vo.MetadataSchemaDetailVo;
+import com.km.db.domain.vo.MetadataTableDetailVo;
+import com.km.db.domain.vo.MetadataTableInfoVo;
+import com.km.db.domain.vo.SchemaTableDataVo;
+import com.km.db.service.IDatabaseConnectionService;
+import jakarta.annotation.Resource;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * @Description: MetaDataService
+ * @Author: GaoKun Wang
+ * @Date: 2024/5/6
+ */
+@Service
+public class MetaDataService {
+    @Resource
+    private IDatabaseConnectionService connectionService;
+    public List<MetadataSchemaDetailVo> allSchemas(long id) {
+        DatabaseConnectionVo connectionVo = connectionService.selectById(id);
+        DatabaseConnection dbConn = MapstructUtils.convert(connectionVo, DatabaseConnection.class);
+        IMetadataService metaDataService = connectionService.getMetaDataCoreService(dbConn);
+        try {
+            List<String> schemas = metaDataService.querySchemaList();
+            return schemas.stream()
+                .map(s -> {
+                    assert dbConn != null;
+                    return new MetadataSchemaDetailVo(dbConn.getName(), s);
+                })
+                .collect(Collectors.toList());
+        } finally {
+            metaDataService.close();
+        }
+    }
+
+    public List<MetadataTableInfoVo> allTables(Long id, String schema) {
+        DatabaseConnectionVo connectionVo = connectionService.selectById(id);
+        DatabaseConnection dbConn = MapstructUtils.convert(connectionVo, DatabaseConnection.class);
+        IMetadataService metaDataService = connectionService.getMetaDataCoreService(dbConn);
+        try {
+            List<TableDescription> tables = metaDataService.queryTableList(schema);
+            return tables.stream()
+                .map(one -> MetadataTableInfoVo.builder()
+                    .tableName(one.getTableName())
+                    .schemaName(one.getSchemaName())
+                    .remarks(one.getRemarks())
+                    .type(one.getTableType())
+                    .build()
+                ).collect(Collectors.toList());
+        } finally {
+            metaDataService.close();
+        }
+    }
+    public MetadataTableDetailVo tableDetail(Long id, String schema, String table) {
+        DatabaseConnectionVo connectionVo = connectionService.selectById(id);
+        DatabaseConnection dbConn = MapstructUtils.convert(connectionVo, DatabaseConnection.class);
+        IMetadataService metaDataService = connectionService.getMetaDataCoreService(dbConn);
+        try {
+            SchemaTableMeta tableMeta = metaDataService.queryTableMeta(schema, table);
+            List<String> pks = tableMeta.getPrimaryKeys();
+            List<MetadataColumnDetailVo> columnDetailResponses = tableMeta.getColumns().stream()
+                .map(one -> MetadataColumnDetailVo.builder()
+                    .fieldName(one.getFieldName())
+                    .typeName(one.getFieldTypeName())
+                    .typeClassName(one.getFiledTypeClassName())
+                    .fieldType(String.valueOf(one.getFieldType()))
+                    .displaySize(String.valueOf(one.getDisplaySize()))
+                    .precisionSize(String.valueOf(one.getPrecisionSize()))
+                    .scaleSize(String.valueOf(one.getScaleSize()))
+                    .isPrimaryKey(
+                        toStr(
+                            CollectionUtils.isNotEmpty(pks)
+                                && pks.contains(one.getFieldName())))
+                    .isAutoIncrement(toStr(one.isAutoIncrement()))
+                    .isNullable(toStr(one.isNullable()))
+                    .remarks(one.getRemarks())
+                    .build()
+                ).collect(Collectors.toList());
+            return MetadataTableDetailVo.builder()
+                .tableName(tableMeta.getTableName())
+                .schemaName(tableMeta.getSchemaName())
+                .remarks(tableMeta.getRemarks())
+                .type(tableMeta.getTableType())
+                .createSql(tableMeta.getCreateSql())
+                .primaryKeys(tableMeta.getPrimaryKeys())
+                .columns(columnDetailResponses)
+                .indexes(tableMeta.getIndexes())
+                .build();
+        } finally {
+            metaDataService.close();
+        }
+    }
+
+    public SchemaTableDataVo tableData(Long id, String schema, String table) {
+        DatabaseConnectionVo connectionVo = connectionService.selectById(id);
+        DatabaseConnection dbConn = MapstructUtils.convert(connectionVo, DatabaseConnection.class);
+        IMetadataService metaDataService = connectionService.getMetaDataCoreService(dbConn);
+        try {
+            SchemaTableData data = metaDataService.queryTableData(schema, table, 10);
+            // el-table问题:https://www.cnblogs.com/LanTianYou/p/9649735.html
+            List<String> headers = data.getColumns().stream()
+                .map(one -> one.replaceAll("\\.", "_"))
+                .collect(Collectors.toList());
+            return SchemaTableDataVo.builder()
+                .schemaName(data.getSchemaName())
+                .tableName(data.getTableName())
+                .columns(headers)
+                .rows(convertRows(headers, data.getRows()))
+                .build();
+        } finally {
+            metaDataService.close();
+        }
+    }
+    private List<Map<String, Object>> convertRows(List<String> columns, List<List<Object>> rows) {
+        if (null == rows || rows.isEmpty()) {
+            return Collections.emptyList();
+        }
+        List<Map<String, Object>> result = new ArrayList<>(rows.size());
+        for (List<Object> row : rows) {
+            Map<String, Object> map = new LinkedHashMap<>();
+            for (int i = 0; i < columns.size(); ++i) {
+                Object v = row.get(i);
+                map.put(columns.get(i), Objects.nonNull(v) ? v.toString() : null);
+            }
+            result.add(map);
+        }
+        return result;
+    }
+
+    private String toStr(Boolean value) {
+        if (null == value) {
+            return "未知";
+        }
+        if (value) {
+            return "是";
+        }
+
+        return "否";
+    }
+}

+ 1 - 0
km-modules/km-db/src/main/resources/META-INF/services/db.providers

@@ -0,0 +1 @@
+com.km.db.product.dm.DmFactoryProvider