开心一刻

今天坐沙发上看电视,旁边的老婆拿着手机贴了过来

老婆:老公,这次出门旅游,机票我准备买了哈

我:嗯

老婆:你、我、你爸妈、我爸妈,一共六张票

老婆:这上面还有意外保险,要不要买?

我:都特么团灭了,还买啥保险?

异构数据源同步

概念介绍

  • 数据源,不只是包含关系型数据库,还包括 NoSQL、数仓、中间件、ftp 等等,凡是有存储功能的都算
  • 异构,两端的数据源的结构存在差异,比如列数不一致、列类型不一致等等
  • 同步,将源数据源的数据同步到目标数据源,包括数据读取、转换和写入过程

所以,异构数据源同步就是指在不同类型或格式的数据源之间传输和同步数据的过程

同步策略

主要有两种同步策略:离线同步实时同步 ,各有其特点和适用场景

但是,这些我今天都不讲,就吊吊你们胃口

如果你们想了解,自己去查吧

今天我就要逆袭一把,将 离线同步 中的一个小配角转正成主角!

表结构同步

异构数据源同步 整个主线剧情中,数据同步 才是真正的主角

表结构同步 只能算活不过三集的那种配角

但今天不拍主线剧情,我要拍个番外篇来重点讲 表结构同步 ,我是导演嘛,当然我说了算

背景说明

主要是针对关系型数据库,当目标数据源的表不存在时,则先在目标数据源创建目标表,然后进行数据的同步

比如:从 MySQL 的表 tbl_t1 同步到 SQL Server 的表 tbl_tt ,若 tbl_tt 不存在,则根据 tbl_t1 的表结构创建 tbl_tt

所以这里就涉及到表结构的同步,也正是本文的主角!

如何实现

通过 jdbc 来实现,具体实现步骤如下

  1. 通过 jdbc 获取元数据信息:表元数据、列元数据、主键元数据、索引元数据

  2. 根据元数据拼接目标表的建表 SQL

  3. 通过 jdbc ,根据建表 SQL,在目标数据源创建目标表

第 3 步实现比较容易,难得是第 1、2步

虽然前路坑很多,但你们不要慌,我已经替你们趟掉很多了

我们以 MySQL 为例,假设我们库 test 下有表 tbl_sync

CREATE TABLE `tbl_sync` (
`c_bigint_auto` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'bigint 类型',
`c_bigint` bigint DEFAULT NULL COMMENT 'bigint 类型',
`c_vachar` varchar(100) NOT NULL COMMENT 'varchar 类型',
`c_char` char(32) NOT NULL COMMENT 'char 类型',
`c_text` text NOT NULL COMMENT 'text 类型',
`c_decimal_4` decimal(15,4) NOT NULL DEFAULT '5000.0000' COMMENT 'decimal 类型',
`c_decimal_0` decimal(10,0) DEFAULT NULL COMMENT 'decimal 类型',
`c_blob` blob COMMENT 'blob 类型',
`c_bit` bit(1) DEFAULT NULL COMMENT 'bit 类型',
`c_tinyint` tinyint DEFAULT NULL COMMENT 'tinyint 类型',
`c_binary` binary(10) DEFAULT NULL COMMENT 'binary 类型',
`c_float` float(13,0) DEFAULT NULL COMMENT 'float 类型',
`c_double` double(23,0) DEFAULT NULL COMMENT 'double 类型',
`c_varbinary` varbinary(20) DEFAULT NULL COMMENT 'varbinary 类型',
`c_longblob` longblob COMMENT 'longblob 类型',
`c_longtext` longtext COMMENT 'longtext 类型',
`c_json` json DEFAULT NULL COMMENT 'json 类型',
`c_date` date DEFAULT NULL COMMENT 'date 类型',
`c_time` time(2) DEFAULT NULL COMMENT 'time 类型',
`c_datetime` datetime(3) DEFAULT NULL COMMENT 'datetime 类型',
`c_timestamp` timestamp(4) NULL DEFAULT NULL COMMENT 'timestamp 类型',
`c_year` year DEFAULT NULL COMMENT 'year 类型',
PRIMARY KEY (`c_vachar`,`c_char`,`c_bigint_auto`),
UNIQUE KEY `uk_id` (`c_bigint_auto`),
KEY `idx_name_salary` (`c_vachar`,`c_decimal_4`)
) COMMENT='包含各种类型列的同步表';

现在需要将其同步到另一个 MySQLobj_db

表元数据

表的元信息比较少,包括表名、表类型、表说明(表注释)等,其他的,类似字符集、排序规则等,就继承数据库的

表名,我想你们都知道,也就是对应上面的 tbl_sync

表说明(表注释)你们肯定也知道,对应上面的 包含各种类型列的同步表

那表类型是什么,你们还知道吗?

我们通常说的表是狭义上的表,也就是 基本表,是最常见的表类型,用于存储具有明确定义的列和数据类型的数据

tbl_sync 就是 基本表 ,但广义上的表还包括 视图临时表系统表 等等

下文都是基于 基本表 ,大家需要注意这个前提

通过 jdbc 获取 表元数据,非常简单,直接看代码

Connection connection = dataSource.getConnection();
DatabaseMetaData databaseMetaData = connection.getMetaData();
ResultSet tableResultSet = databaseMetaData.getTables(connection.getCatalog(), connection.getSchema(),
"tbl_sync", new String[]{"TABLE"});
while (tableResultSet.next()) {
System.out.println("tableCatalog = " + tableResultSet.getString("TABLE_CAT"));
System.out.println("tableSchema = " + tableResultSet.getString("TABLE_SCHEM"));
System.out.println("tableName = " + tableResultSet.getString("TABLE_NAME"));
System.out.println("tableType = " + tableResultSet.getString("TABLE_TYPE"));
System.out.println("remarks = " + tableResultSet.getString("REMARKS"));
}

输出结果

tableCatalog = test
tableSchema = null
tableName = tbl_sync
tableType = TABLE
remarks = 包含各种类型列的同步表

一般我们只需要关注: TABLE_NAMETABLE_TYPEREMARKS

我们看下 java.sql.DatabaseMetaData#getTables 说明

点击查看代码
/**
* Retrieves a description of the tables available in the given catalog.
* Only table descriptions matching the catalog, schema, table
* name and type criteria are returned. They are ordered by
* <code>TABLE_TYPE</code>, <code>TABLE_CAT</code>,
* <code>TABLE_SCHEM</code> and <code>TABLE_NAME</code>.
* <P>
* Each table description has the following columns:
* <OL>
* <LI><B>TABLE_CAT</B> String {@code =>} table catalog (may be <code>null</code>)
* <LI><B>TABLE_SCHEM</B> String {@code =>} table schema (may be <code>null</code>)
* <LI><B>TABLE_NAME</B> String {@code =>} table name
* <LI><B>TABLE_TYPE</B> String {@code =>} table type. Typical types are "TABLE",
* "VIEW", "SYSTEM TABLE", "GLOBAL TEMPORARY",
* "LOCAL TEMPORARY", "ALIAS", "SYNONYM".
* <LI><B>REMARKS</B> String {@code =>} explanatory comment on the table
* <LI><B>TYPE_CAT</B> String {@code =>} the types catalog (may be <code>null</code>)
* <LI><B>TYPE_SCHEM</B> String {@code =>} the types schema (may be <code>null</code>)
* <LI><B>TYPE_NAME</B> String {@code =>} type name (may be <code>null</code>)
* <LI><B>SELF_REFERENCING_COL_NAME</B> String {@code =>} name of the designated
* "identifier" column of a typed table (may be <code>null</code>)
* <LI><B>REF_GENERATION</B> String {@code =>} specifies how values in
* SELF_REFERENCING_COL_NAME are created. Values are
* "SYSTEM", "USER", "DERIVED". (may be <code>null</code>)
* </OL>
*
* <P><B>Note:</B> Some databases may not return information for
* all tables.
*
* @param catalog a catalog name; must match the catalog name as it
* is stored in the database; "" retrieves those without a catalog;
* <code>null</code> means that the catalog name should not be used to narrow
* the search
* @param schemaPattern a schema name pattern; must match the schema name
* as it is stored in the database; "" retrieves those without a schema;
* <code>null</code> means that the schema name should not be used to narrow
* the search
* @param tableNamePattern a table name pattern; must match the
* table name as it is stored in the database
* @param types a list of table types, which must be from the list of table types
* returned from {@link #getTableTypes},to include; <code>null</code> returns
* all types
* @return <code>ResultSet</code> - each row is a table description
* @exception SQLException if a database access error occurs
* @see #getSearchStringEscape
*/

相信你们都能看懂,我只强调下 TABLE_TYPE

其值包括

  • TABLE
  • VIEW
  • SYSTEM TABLE
  • GLOBAL TEMPORARY,LOCAL TEMPORARY
  • ALIAS
  • SYNONYM

列元数据

列元信息比较多一点,包括列名、列类型、列类型名、是否自增、是否允许NULL、列大小、小数位数、默认值、列说明(列注释)等

通过 jdbc 获取 列元数据 也很简单,直接看代码

Connection connection = dataSource.getConnection();
DatabaseMetaData databaseMetaData = connection.getMetaData();
ResultSet columnResultSet = databaseMetaData.getColumns(connection.getCatalog(), connection.getSchema(),
"tbl_sync", null);
while (columnResultSet.next()) {
System.out.println("ColumnName = " + columnResultSet.getString("COLUMN_NAME"));
System.out.println("ColumnType = " + columnResultSet.getInt("DATA_TYPE"));
System.out.println("ColumnTypeName = " + columnResultSet.getString("TYPE_NAME"));
System.out.println("isAutoIncrement = " + columnResultSet.getString("IS_AUTOINCREMENT"));
System.out.println("isNullable = " + columnResultSet.getString("IS_NULLABLE"));
System.out.println("Precision = " + columnResultSet.getInt("COLUMN_SIZE"));
System.out.println("Scale = " + columnResultSet.getInt("DECIMAL_DIGITS"));
System.out.println("DefaultValue = " + columnResultSet.getString("COLUMN_DEF"));
System.out.println("Remarks = " + columnResultSet.getString("REMARKS"));
System.out.println("===================================");
}

输出结果

ColumnName = c_bigint_auto
ColumnType = -5
ColumnTypeName = BIGINT UNSIGNED
isAutoIncrement = YES
isNullable = NO
Precision = 20
Scale = 0
DefaultValue = null
Remarks = bigint 类型
===================================
ColumnName = c_bigint
ColumnType = -5
ColumnTypeName = BIGINT
isAutoIncrement = NO
isNullable = YES
Precision = 19
Scale = 0
DefaultValue = null
Remarks = bigint 类型
===================================
ColumnName = c_vachar
ColumnType = 12
ColumnTypeName = VARCHAR
isAutoIncrement = NO
isNullable = NO
Precision = 100
Scale = 0
DefaultValue = null
Remarks = varchar 类型
===================================
...

ColumnType 的值是 java.sql.Types-5 即是 java.sql.Types#BIGINT

那是不是根据 ColumnType 就可以推断出数据库列类型了?

我们看下如下输出

ColumnName = c_longtext
ColumnType = -1
ColumnTypeName = LONGTEXT
isAutoIncrement = NO
isNullable = YES
Precision = 2147483647
Scale = 0
DefaultValue = null
Remarks = longtext 类型
===================================
ColumnName = c_json
ColumnType = -1
ColumnTypeName = JSON
isAutoIncrement = NO
isNullable = YES
Precision = 1073741824
Scale = 0
DefaultValue = null
Remarks = json 类型
===================================

ColumnType = -1 是对应 LONGTEXT ,还是对应 JSON

我们再看一个

ColumnName = c_datetime
ColumnType = 93
ColumnTypeName = DATETIME
isAutoIncrement = NO
isNullable = YES
Precision = 23
Scale = 0
DefaultValue = null
Remarks = datetime 类型
===================================
ColumnName = c_timestamp
ColumnType = 93
ColumnTypeName = TIMESTAMP
isAutoIncrement = NO
isNullable = YES
Precision = 24
Scale = 0
DefaultValue = null
Remarks = timestamp 类型
===================================

ColumnType = 93 是对应 DATETIME,还是对应 TIMESTAMP

这说明不能通过 java.sql.Types 精准确认列的数据库类型!!!

那怎么办?

我相信你们已经看到了列的另一个元数据:ColumnTypeName

它不就是 源数据源 中列列类型吗?

比如列 c_timestamp 的类型不就是 TIMESTAMP 吗,丝毫不差,准确的很!

但是我们不能忘了我们的初衷:拼接目标表的建表 SQL

通过 ColumnTypeName 能不能对应到目标表的列类型?

直接使用,肯定是不行的,关系型数据库之间的类型不是完全一一对应的,比如 MySQLDATETIMEOracle 是没有的

那可不可以通过 ColumnTypeName 来映射了,比如 DATETIME 映射到 OracleDATE

理论上来说是可行的,但是,问题又来了!

我们是通过 jdbc 来完成映射的,它只提供了int 类型的 java.sql.Types ,并未提供 String 类型的 java.sql.Types

莫非你要自实现 String 类型的 java.sql.Types ? 你穷举的过来吗?

所以我们需要根据 java.sql.Types 对源数据源的列类型最大兼容性获取,而不是百分之百的精准获取

例如:java.sql.Types#LONGVARCHAR 就当作列类型 LONGTEXT ,然后向目标数据源映射

如果想更精准,则再结合 ColumnTypeName 的值向目标数据源映射

总之一句话:ColumnType 主导,ColumnTypeName 辅助,完成目标数据源列映射

java.sql.DatabaseMetaData#getColumns 能获取的元数据不局限于上述示例中的那些

大家可以去看下其源码注释,因为太长了,我就不贴了,我们重点看下 COLUMN_SIZE

* The COLUMN_SIZE column specifies the column size for the given column.
* For numeric data, this is the maximum precision. For character data, this is the length in characters.
* For datetime datatypes, this is the length in characters of the String representation (assuming the
* maximum allowed precision of the fractional seconds component). For binary data, this is the length in bytes. For the ROWID datatype,
* this is the length in bytes. Null is returned for data types where the
* column size is not applicable.

我给你们逐行翻译下

/**
* For numeric data, this is the maximum precision => 对于数值数据,表示最大精度
* For character data, this is the length in characters => 对于字符数据,表示字符长度
* For datetime datatypes, this is the length in characters of the String representation(assuming the maximum allowed precision of the fractional seconds component )
* => 对于日期时间数据类型,表示字符串表示形式的最大长度(假设最大允许的分秒小数部分的精度)
* 例如:"2024-04-30 14:00:00" => 19,"2024-04-30 14:00:00.234" => 23
* "14:00:00" => 8,"14:00:00.234" => 11
* For binary data, this is the length in bytes => 对于二进制数据,表示字节长度
* For the ROWID datatype, this is the length in bytes => 对于 ROWID 类型,表示字节长度
* 0 is returned for data types where the column size is not applicable => 对于列大小不适用的数据类型,返回0
*/

主键元数据

主键元信息就比较少了,我们一般只关注主键名、列名、列序号

通过 jdbc 代码获取,示例代码如下

Connection connection = dataSource.getConnection();
DatabaseMetaData databaseMetaData = connection.getMetaData();
ResultSet primaryKeysResultSet = databaseMetaData.getPrimaryKeys(connection.getCatalog(), connection.getSchema(), "tbl_sync");
while (primaryKeysResultSet.next()) {
String columnName = primaryKeysResultSet.getString("COLUMN_NAME");
short keySeq = primaryKeysResultSet.getShort("KEY_SEQ");
String pkName = primaryKeysResultSet.getString("PK_NAME");
System.out.println(columnName + " - " + keySeq + " - " + pkName);
}

输出结果

c_vachar - 1 - PRIMARY
c_char - 2 - PRIMARY
c_bigint_auto - 3 - PRIMARY

不用过多说明了吧,你们肯定都能看懂

索引元数据

与主键元数据类似,关注的元数据主要包括索引名、列名、列序号,同时多了一个 是否非唯一

通过 jdbc 获取,代码如下

Connection connection = dataSource.getConnection();
DatabaseMetaData databaseMetaData = connection.getMetaData();
ResultSet indexResultSet = databaseMetaData.getIndexInfo(connection.getCatalog(), connection.getSchema(), "tbl_sync", false, false);
while (indexResultSet.next()) {
String indexName = indexResultSet.getString("INDEX_NAME");
String columnName = indexResultSet.getString("COLUMN_NAME");
boolean nonUnique = indexResultSet.getBoolean("NON_UNIQUE");
short ordinalPosition = indexResultSet.getShort("ORDINAL_POSITION");
System.out.println(columnName + " - " + ordinalPosition + " - " + indexName + " - " + nonUnique);
}

输出结果

c_vachar - 1 - PRIMARY - false
c_char - 2 - PRIMARY - false
c_bigint_auto - 3 - PRIMARY - false
c_bigint_auto - 1 - uk_id - false
c_vachar - 1 - idx_name_salary - true
c_decimal_4 - 2 - idx_name_salary - true

建表 SQL

当相关元数据都获取到之后,就万事俱备,只欠东风了

我们将 test 库下的表 tbl_sync 同步到另一个 MySQLobj_db

SQL 拼接如下

点击查看代码
public String getCreateTableSql(String schemaName, TableMeta tableMeta, List<ColumnMeta> columnMetas,
IndexMeta primaryKeyMeta, Map<String, IndexMeta> indexMetaMap) {
StringBuilder createSql = new StringBuilder("CREATE TABLE " + schemaName + "." + tableMeta.getTableName() + " ( ");
for (ColumnMeta columnMeta : columnMetas) {
createSql.append(columnMeta.getColumnName()).append(" ").append(getColumnType(columnMeta));
if (columnMeta.getIfUnsigned()) {
createSql.append(" UNSIGNED");
}
if (columnMeta.getIfNullable() == 0) {
createSql.append(" NOT NULL");
}
if (StrUtil.isNotBlank(columnMeta.getDefaultValue())) {
createSql.append(" DEFAULT '").append(columnMeta.getDefaultValue()).append("'");
}
if (columnMeta.getIfAutoIncrement()) {
createSql.append(" AUTO_INCREMENT");
}
if (StrUtil.isNotBlank(columnMeta.getRemarks())) {
createSql.append(" COMMENT '").append(columnMeta.getRemarks()).append("'");
}
createSql.append(",");
}
// 主键处理
if (ObjectUtil.isNotNull(primaryKeyMeta)) {
List<IndexColumnMeta> indexColumns = primaryKeyMeta.getIndexColumns();
indexColumns.sort(Comparator.comparingInt(IndexColumnMeta::getOrdinalPosition));
createSql.append(" PRIMARY KEY (");
for (int i=0; i<indexColumns.size(); i++) {
if (i>0) {
createSql.append(",");
}
createSql.append(indexColumns.get(i).getColumnName());
}
createSql.append("),");
}
if (CollectionUtil.isNotEmpty(indexMetaMap)) {
for (IndexMeta indexMeta : indexMetaMap.values()) {
if (indexMeta.getIndexType() == IndexTypeEnum.UNIQUE) {
// 唯一索引
createSql.append("UNIQUE ");
}
createSql.append("KEY ").append(indexMeta.getIndexName()).append(" (");
List<IndexColumnMeta> indexColumns = indexMeta.getIndexColumns();
indexColumns.sort(Comparator.comparingInt(IndexColumnMeta::getOrdinalPosition));
for (int i=0; i<indexColumns.size(); i++) {
if (i>0) {
createSql.append(",");
}
createSql.append(indexColumns.get(i).getColumnName());
}
createSql.append("),");
}
}
// 删除最后一个逗号
createSql.deleteCharAt(createSql.length()-1);
createSql.append(")");
if (StrUtil.isNotBlank(tableMeta.getRemarks())) {
createSql.append(" COMMENT '").append(tableMeta.getRemarks()).append("'");
}
return createSql.toString();
} /**
* 获取表 列类型
* @param columnMeta 列元数据
* @return mysql 列类型
*/
private String getColumnType(ColumnMeta columnMeta) {
switch (columnMeta.getColumnType()) {
// 数值类型
case Types.TINYINT:
return "TINYINT";
case Types.SMALLINT:
return "SMALLINT";
case Types.INTEGER:
return "INT";
case Types.BIGINT:
return "BIGINT";
case Types.FLOAT:
case Types.REAL:
return columnMeta.getPrecision() > 0 ? "FLOAT(" + columnMeta.getPrecision() + "," + columnMeta.getScale() + ")" : "FLOAT";
case Types.DOUBLE:
return columnMeta.getPrecision() > 0 ? "DOUBLE(" + columnMeta.getPrecision() + "," + columnMeta.getScale() + ")" : "DOUBLE";
case Types.DECIMAL:
return "DECIMAL(" + columnMeta.getPrecision() + "," + columnMeta.getScale() + ")";
case Types.NUMERIC:
return columnMeta.getScale() <= 0 ? "BIGINT" : "DECIMAL(" + columnMeta.getPrecision() + "," + columnMeta.getScale() + ")";
// 字符与字符串类型
case Types.CHAR:
case Types.NCHAR:
return columnMeta.getPrecision() > 0 ? "CHAR(" + columnMeta.getPrecision() + ")" : "CHAR";
case Types.VARCHAR:
case Types.NVARCHAR:
return columnMeta.getPrecision() > 0 ? "VARCHAR(" + columnMeta.getPrecision() + ")" : "VARCHAR";
case Types.LONGVARCHAR:
case Types.LONGNVARCHAR:
switch (columnMeta.getColumnTypeName()) {
case "TINYTEXT":
return "TINYTEXT";
case "MEDIUMTEXT":
return "MEDIUMTEXT";
case "LONGTEXT":
return "LONGTEXT";
case "JSON":
return "JSON";
default:
return "TEXT";
}
case Types.CLOB:
case Types.NCLOB:
return "LONGTEXT";
// 日期和时间类型
case Types.DATE:
switch (columnMeta.getColumnTypeName()) {
case "YEAR":
return "YEAR";
default:
return "DATE";
}
case Types.TIME:
return "TIME" + (columnMeta.getPrecision() > 8 ? "(" + (columnMeta.getPrecision() - 9) + ")" : "");
case Types.TIMESTAMP:
switch (columnMeta.getColumnTypeName()) {
case "DATETIME":
return "DATETIME" + (columnMeta.getPrecision() > 19 ? "(" + (columnMeta.getPrecision() - 20) + ")" : "");
case "DATE":
// oracle 的 DATE
return "DATETIME";
default:
return "TIMESTAMP"+ (columnMeta.getPrecision() > 19 ? "(" + (columnMeta.getPrecision() - 20) + ")" : "");
}
// 二进制类型
case Types.BIT:
case Types.BOOLEAN:
return columnMeta.getPrecision() > 0 ? "BIT(" + columnMeta.getPrecision() + ")" : "BIT";
case Types.BINARY:
return columnMeta.getPrecision() > 0 ? "BINARY(" + columnMeta.getPrecision() + ")" : "BINARY";
case Types.VARBINARY:
return columnMeta.getPrecision() > 0 ? "VARBINARY(" + columnMeta.getPrecision() + ")" : "VARBINARY";
case Types.BLOB:
case Types.LONGVARBINARY:
switch (columnMeta.getColumnTypeName()) {
case "TINYBLOB":
return "TINYBLOB";
case "MEDIUMBLOB":
return "MEDIUMBLOB";
case "LONGBLOB":
return "LONGBLOB";
default:
return "BLOB";
}
case Types.OTHER:
if (columnMeta.getColumnTypeName().contains("VARCHAR")) {
return "VARCHAR" + (columnMeta.getPrecision() > 0 ? "(" + columnMeta.getPrecision() + ")" : "");
} else if (columnMeta.getColumnTypeName().contains("TIMESTAMP")) {
return "TIMESTAMP" + (columnMeta.getScale() > 0 ? "(" + columnMeta.getScale() + ")" : "");
}
else {
throw new SyncException("不支持的类型:" + columnMeta.getColumnTypeName());
}
default:
throw new SyncException("不支持的类型:" + columnMeta.getColumnTypeName());
}
}

结合元数据的获取

点击查看代码
@Test
public void getMySQLCreateTableSql() throws SQLException {
Connection connection = dataSource.getConnection();
DatabaseMetaData databaseMetaData = connection.getMetaData();
ResultSet tableResultSet = databaseMetaData.getTables(connection.getCatalog(), connection.getSchema(), "tbl_sync", new String[]{"TABLE"});
TableMeta tableMeta = new TableMeta();
while (tableResultSet.next()) {
tableMeta.setTableName(tableResultSet.getString("TABLE_NAME"));
tableMeta.setTableType(tableResultSet.getString("TABLE_TYPE"));
tableMeta.setRemarks(tableResultSet.getString("REMARKS"));
}
// 获取列元数据
ResultSet columnResultSet = databaseMetaData.getColumns(connection.getCatalog(), connection.getSchema(), "tbl_sync", null);
List<ColumnMeta> columnMetas = new ArrayList<>();
while (columnResultSet.next()) {
ColumnMeta columnMeta = new ColumnMeta();
columnMeta.setColumnName(columnResultSet.getString("COLUMN_NAME"));
columnMeta.setColumnType(columnResultSet.getInt("DATA_TYPE"));
columnMeta.setColumnTypeName(columnResultSet.getString("TYPE_NAME"));
columnMeta.setIfAutoIncrement("YES".equalsIgnoreCase(columnResultSet.getString("IS_AUTOINCREMENT")));
columnMeta.setIfNullable("YES".equalsIgnoreCase(columnResultSet.getString("IS_NULLABLE")) ? 1 : 0);
columnMeta.setPrecision(columnResultSet.getInt("COLUMN_SIZE"));
columnMeta.setScale(columnResultSet.getInt("DECIMAL_DIGITS"));
columnMeta.setDefaultValue(columnResultSet.getString("COLUMN_DEF"));
columnMeta.setRemarks(columnResultSet.getString("REMARKS"));
columnMeta.setIfUnsigned(columnMeta.getColumnTypeName().contains("UNSIGNED"));
columnMetas.add(columnMeta);
}
columnResultSet.close();
// 获取主键元数据
ResultSet primaryKeyResultSet = databaseMetaData.getPrimaryKeys(connection.getCatalog(), connection.getSchema(), "tbl_sync");
IndexMeta primaryKeyMeta = new IndexMeta();
while (primaryKeyResultSet.next()) {
IndexColumnMeta indexColumnMeta = new IndexColumnMeta(primaryKeyResultSet.getString("COLUMN_NAME"), primaryKeyResultSet.getShort("KEY_SEQ"));
primaryKeyMeta.setIndexName(primaryKeyResultSet.getString("PK_NAME"));
primaryKeyMeta.getIndexColumns().add(indexColumnMeta);
}
primaryKeyResultSet.close();
// 获取索引元数据
ResultSet indexResultSet = databaseMetaData.getIndexInfo(connection.getCatalog(), connection.getSchema(), "tbl_sync", false, false);
Map<String, IndexMeta> indexMetaMap = new HashMap<>();
while (indexResultSet.next()) {
String indexName = indexResultSet.getString("INDEX_NAME");
if (indexName.equals(primaryKeyMeta.getIndexName())) {
continue;
}
IndexMeta indexMeta = indexMetaMap.get(indexName);
if (ObjectUtil.isNull(indexMeta)) {
indexMeta = new IndexMeta(indexName);
indexMetaMap.put(indexName, indexMeta);
}
indexMeta.setIndexType(indexResultSet.getBoolean("NON_UNIQUE") ? IndexTypeEnum.NORMAL : IndexTypeEnum.UNIQUE);
indexMeta.getIndexColumns().add(new IndexColumnMeta(indexResultSet.getString("COLUMN_NAME"), indexResultSet.getShort("ORDINAL_POSITION")));
}
indexResultSet.close(); MysqlSql mysqlSql = new MysqlSql();
String createTableSql = mysqlSql.getCreateTableSql("obj_db", tableMeta, columnMetas, primaryKeyMeta, indexMetaMap);
System.out.println(SQLUtils.formatMySql(createTableSql));
}

得到的建表 SQL 如下

CREATE TABLE obj_db.tbl_sync (
c_bigint_auto BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'bigint 类型',
c_bigint BIGINT COMMENT 'bigint 类型',
c_vachar VARCHAR(100) NOT NULL COMMENT 'varchar 类型',
c_char CHAR(32) NOT NULL COMMENT 'char 类型',
c_text TEXT NOT NULL COMMENT 'text 类型',
c_decimal_4 DECIMAL(15, 4) NOT NULL DEFAULT '5000.0000' COMMENT 'decimal 类型',
c_decimal_0 DECIMAL(10, 0) COMMENT 'decimal 类型',
c_blob BLOB COMMENT 'blob 类型',
c_bit BIT(1) COMMENT 'bit 类型',
c_tinyint TINYINT COMMENT 'tinyint 类型',
c_binary BINARY(10) COMMENT 'binary 类型',
c_float FLOAT(13, 0) COMMENT 'float 类型',
c_double DOUBLE(23, 0) COMMENT 'double 类型',
c_varbinary VARBINARY(20) COMMENT 'varbinary 类型',
c_longblob LONGBLOB COMMENT 'longblob 类型',
c_longtext LONGTEXT COMMENT 'longtext 类型',
c_json JSON COMMENT 'json 类型',
c_date DATE COMMENT 'date 类型',
c_time TIME(2) COMMENT 'time 类型',
c_datetime DATETIME(3) COMMENT 'datetime 类型',
c_timestamp TIMESTAMP(4) COMMENT 'timestamp 类型',
c_year YEAR COMMENT 'year 类型',
PRIMARY KEY (c_vachar, c_char, c_bigint_auto),
UNIQUE KEY uk_id (c_bigint_auto),
KEY idx_name_salary (c_vachar, c_decimal_4)
) COMMENT '包含各种类型列的同步表'

可以看出,与原表的结构是一致的!

此处应该有掌声

同源同步

何谓同源?

就是数据库类型相同的数据源,例如从 MySQL 同步到 MySQL

这种情况还有必要进行 SQL 拼接吗?

还记得怎么查看 MySQL 表的完整定义吗

SHOW CREATE TABLE test.tbl_sync

这是不是就可以获取到表的 DDL

所以同源的表结构同步,就不用拼接 SQL 那么复杂了,直接获取 DDL 后在目标数据源建表即可

总结

  • 异构数据源同步的策略有两种:离线同步 和 实时同步,各自的特点及使用场景需要区分清楚
  • 关系型数据库的元数据有很多种,大家可以仔细看看 java.sql.DatabaseMetaData
  • 同源表结构同步,可以不用拼接建表 SQL,可以直接获取建表 DDL
  • 异源表结构同步,需要先获取源表的相关元数据,然后再拼接目标表的建表 SQL,最后在目标数据源执行 SQL 创建目标表
  • COLUMN_SIZE 针对不同的列类型,它的含义不同,文中已经详细说明,值得大家注意

异构数据源同步之表结构同步 → 通过 jdbc 实现,没那么简单的更多相关文章

  1. MySQL表结构同步工具 mysql-schema-sync

    mysql-schema-sync 是一款使用go开发的.跨平台的.绿色无依赖的 MySQL 表结构自动同步工具.用于将线上(其他环境)数据库结构变化同步到测试(本地)环境! 可以解决多人开发,每人都 ...

  2. Sqoop将MySQL表结构同步到hive(text、orc)

    Sqoop将MySQL表结构同步到hive sqoop create-hive-table --connect jdbc:mysql://localhost:3306/sqooptest --user ...

  3. 使用DB查询分析器实现异构数据源中数据表的相互访问

    1  引言   硕士程序员马根峰(CSDN专访马根峰:海量数据处理与分析大师的中国本土程序员)推出的个人作品----万能数据库查询分析器,中文版本DB 查询分析器.英文版本<DB Query A ...

  4. 利用navcat为mysql数据库单独的表赋权限及表结构同步

    为mysql数据库单独的表赋权限 场景:考勤系统需要拿OA数据库td_oa中的flow_run和flow_run_data表中的数据做考勤计算 考勤系统只需要读取这两张表的数据,所以只需要开通一个单独 ...

  5. Hibernate由model类自动同步数据库表结构

    在开发中遇到了个问题,每次测试数据库增加表结构的时候,本地pull下最新代码导致启动报错,上网搜了快速解决办法---->hibernate 配置属性中,hibernate.hbm2ddl.aut ...

  6. 使用schemasync同步数据库表结构

    安装方式 wget http://www.schemasync.org/downloads/SchemaSync-0.9.4.tar.gz tar -xf SchemaSync-0.9.4.tar.g ...

  7. navicat有数据额结构同步

    这个功能可能检查两个库的表结构异同,进行表结构构同步,可以生成同步语句. 比如在测试环境表中新增了字段,可以通过这个工具进行表结构同步.

  8. powerdesigner 连接 Oracle ,并将表结构导入到powerdesigner中

    powerdesigner 详细很多人都用过,很多人可能也有用来连接各种数据库以及从数据库中把表结构更新到powerdesigner的model中,或者将自己设计的表结构应用到数据库中.那么我今天就说 ...

  9. 数据库表结构文档查看器 基于netcore

    前言 日常开发业务代码,新接手一块不熟悉的业务时需要频繁的查看对应业务的数据库表设计文档.相比于直接翻看业务代码,有必要提供一个数据库表结构文档查看器来解决这些繁琐的问题. CML.SqlDoc CM ...

  10. 在线修改大表结构pt-online-schema-change

    使用场景 在线数据库的维护中,总会涉及到研发修改表结构的情况,修改一些小表影响很小,而修改大表时,往往影响业务的正常运转,如表数据量超过500W,1000W,甚至过亿时 在线修改大表的可能影响 在线修 ...

随机推荐

  1. 运行xxl-job,整合xxl-job至jeecg-boot项目

    1.前言:xxl-job是一个分布式任务调度平台,其核心设计目标是开发迅速.学习简单.轻量级.易扩展.现已开放源代码并接入多家公司线上产品线,开箱即用. 源码仓库地址:https://gitee.co ...

  2. C++代码实现OnComponentHit事件粒子消失蓝图--斯坦福

    蓝图节点 OnComponentBeginOverlap,OnComponentHit等等之类如何迁移到C++中 方法 这些蓝图节点实际上就是一个UE4已经定义好的事件,在蓝图中使用模块的连接来实现事 ...

  3. list集合中的实现类ArrayList

    如上图所示,list集合是 Collection 接口的子接口,它是一个元素有序(每个元素都有对应的顺序索引,第一个元素索引为0).且可重复的集合,他有三个实现类,如下: ArrayList add方 ...

  4. #主席树,fread,fwrite#洛谷 1972 [SDOI2009]HH的项链

    题目 分析 维护每个位置的后继,问题转换为后继在区间外的位置的个数, 但是这题太卡常了,所以我就加了fread和fwrite 其实树状数组的解法我也写过了 代码 #include <cstdio ...

  5. jenkins 持续集成和交付 —— 参数化构建(八)

    前言 这个其实就是我们构建的脚本时候希望能有一些变量,能给我们更多的选择. 正文 选择这个: 填好后,这边就有一个参数配置: 那么接下来就是把变量放到我们的脚本中. 如下: 脚本变成变量符,这样就ok ...

  6. 重新整理数据结构与算法(c#)—— 堆排序[二十一]

    前言 将下面按照从小到大排序: int[] arr = { 4, 6, 8, 5, 9 }; 这时候可以通过冒泡排序,计数排序等. 但是一但数据arr很大,那么会产生排序过于缓慢,堆排序就是一个很好的 ...

  7. C++ 默认参数与引用传递:语法、用法及示例

    C++ 默认参数 默认参数概述 在 C++ 中,函数参数可以拥有默认值.这意味着,在调用函数时,如果省略了某个参数,那么将使用为该参数指定的默认值. 设置默认参数 默认参数值使用等号 = 符号进行设置 ...

  8. 在windows电脑中安装redis

    1,github下载地址:https://github.com/MSOpenTech/redis/tags 2,下载完成后,解压到对应文件夹 3,打开redis.windows.conf,在#requ ...

  9. async与await暂停作用

    1. async function Request () { await new Promise(res => { setTimeout(() => { console.log(1) re ...

  10. k8s集群部署1.28.2版本(无坑)

    Kubernetes-1.28.2 集群介绍及搭建 一.Kubernetes 概述 1.什么是Kubernetes? K8S 的全称为 Kubernetes.用于自动部署.扩展和管理"容器化 ...