diff --git a/client-adapter/common/pom.xml b/client-adapter/common/pom.xml
index f018c490..e62fc386 100644
--- a/client-adapter/common/pom.xml
+++ b/client-adapter/common/pom.xml
@@ -14,7 +14,7 @@
com.alibaba.otter
canal.protocol
- ${canal_version}
+ 1.1.2-SNAPSHOT
joda-time
diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Util.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Util.java
new file mode 100644
index 00000000..ac2c32c3
--- /dev/null
+++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Util.java
@@ -0,0 +1,90 @@
+package com.alibaba.otter.canal.client.adapter.support;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import javax.sql.DataSource;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Util {
+
+ private static final Logger logger = LoggerFactory.getLogger(Util.class);
+
+ /**
+ * 通过DS执行sql
+ */
+ public static Object sqlRS(DataSource ds, String sql, Function fun) throws SQLException {
+ Connection conn = null;
+ Statement stmt = null;
+ ResultSet rs = null;
+ try {
+ conn = ds.getConnection();
+ stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
+ stmt.setFetchSize(Integer.MIN_VALUE);
+ rs = stmt.executeQuery(sql);
+ return fun.apply(rs);
+ } finally {
+ if (rs != null) {
+ try {
+ rs.close();
+ } catch (SQLException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+ if (stmt != null) {
+ try {
+ stmt.close();
+ } catch (SQLException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+ if (conn != null) {
+ try {
+ conn.close();
+ } catch (SQLException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+ }
+ }
+
+ /**
+ * sql执行获取resultSet
+ *
+ * @param conn sql connection
+ * @param sql sql
+ * @param consumer 回调方法
+ */
+ public static void sqlRS(Connection conn, String sql, Consumer consumer) {
+ Statement stmt = null;
+ ResultSet rs = null;
+ try {
+ stmt = conn.createStatement();
+ rs = stmt.executeQuery(sql);
+ consumer.accept(rs);
+ } catch (SQLException e) {
+ logger.error(e.getMessage(), e);
+ } finally {
+ if (rs != null) {
+ try {
+ rs.close();
+ } catch (SQLException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+ if (stmt != null) {
+ try {
+ stmt.close();
+ } catch (SQLException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+ }
+ }
+}
diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java
index d9b09161..681537cf 100644
--- a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java
+++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java
@@ -42,7 +42,7 @@ public class ESSyncConfigLoader {
}
public static synchronized void load() {
- logger.info("## Start loading mapping config ... ");
+ logger.info("## Start loading es mapping config ... ");
Collection configs = AdapterConfigs.get("es");
if (configs == null) {
return;
@@ -92,7 +92,7 @@ public class ESSyncConfigLoader {
esSyncConfig.put(c, config);
}
- logger.info("## Mapping config loaded");
+ logger.info("## ES mapping config loaded");
}
private static String readConfigContent(String config) {
@@ -113,7 +113,7 @@ public class ESSyncConfigLoader {
in.read(bytes);
return new String(bytes, StandardCharsets.UTF_8);
} catch (IOException e) {
- throw new RuntimeException("Read yml config error ", e);
+ throw new RuntimeException("Read es mapping config error ", e);
} finally {
try {
if (in != null) {
diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESSyncService.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESSyncService.java
index 272ef1a9..04d208f7 100644
--- a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESSyncService.java
+++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESSyncService.java
@@ -10,6 +10,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig;
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig.ESMapping;
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfigLoader;
@@ -73,7 +74,7 @@ public class ESSyncService {
dml.getDestination());
}
if (logger.isDebugEnabled()) {
- logger.debug("DML: {}", JSON.toJSONString(dml));
+ logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
}
}
}
diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java
index 5b6cf2bd..f2ee6994 100644
--- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java
+++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java
@@ -4,6 +4,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -33,7 +34,7 @@ public class MappingConfigLoader {
* @return 配置名/配置文件名--对象
*/
public static Map load() {
- logger.info("## Start loading mapping config ... ");
+ logger.info("## Start loading hbase mapping config ... ");
Map result = new LinkedHashMap<>();
@@ -116,7 +117,7 @@ public class MappingConfigLoader {
result.put(c, config);
}
- logger.info("## Mapping config loaded");
+ logger.info("## Hbase mapping config loaded");
return result;
}
@@ -136,9 +137,9 @@ public class MappingConfigLoader {
byte[] bytes = new byte[in.available()];
in.read(bytes);
- return new String(bytes, "UTF-8");
+ return new String(bytes, StandardCharsets.UTF_8);
} catch (IOException e) {
- throw new RuntimeException("Read ds-config.yml or hbase-mappings.conf error. ", e);
+ throw new RuntimeException("Read hbase mapping config error. ", e);
} finally {
try {
if (in != null) {
diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java
index 912baf08..172592e2 100644
--- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java
+++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java
@@ -14,6 +14,7 @@ import java.util.function.Function;
import javax.sql.DataSource;
+import com.alibaba.otter.canal.client.adapter.support.Util;
import org.apache.hadoop.hbase.util.Bytes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -34,43 +35,6 @@ public class HbaseEtlService {
private static Logger logger = LoggerFactory.getLogger(HbaseEtlService.class);
- public static Object sqlRS(DataSource ds, String sql, Function fun) throws SQLException {
- Connection conn = null;
- Statement stmt = null;
- ResultSet rs = null;
- try {
- conn = ds.getConnection();
- stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
- stmt.setFetchSize(Integer.MIN_VALUE);
- rs = stmt.executeQuery(sql);
- return fun.apply(rs);
- } finally {
- if (rs != null) {
- try {
- rs.close();
- } catch (SQLException e) {
- logger.error(e.getMessage(), e);
- }
- }
- if (stmt != null) {
- try {
- stmt.close();
- } catch (SQLException e) {
- logger.error(e.getMessage(), e);
- }
- }
- if (conn != null) {
- try {
- conn.close();
- } catch (SQLException e) {
- logger.error(e.getMessage(), e);
- }
- }
- rs = null;
- stmt = null;
- conn = null;
- }
- }
/**
* 建表
@@ -138,7 +102,7 @@ public class HbaseEtlService {
if (params != null && params.size() == 1 && hbaseMapping.getEtlCondition() == null) {
AtomicBoolean stExists = new AtomicBoolean(false);
// 验证是否有SYS_TIME字段
- sqlRS(ds, sql, rs -> {
+ Util.sqlRS(ds, sql, rs -> {
try {
ResultSetMetaData rsmd = rs.getMetaData();
int cnt = rsmd.getColumnCount();
@@ -169,7 +133,7 @@ public class HbaseEtlService {
// 获取总数
String countSql = "SELECT COUNT(1) FROM ( " + sql + ") _CNT ";
- long cnt = (Long) sqlRS(ds, countSql, rs -> {
+ long cnt = (Long) Util.sqlRS(ds, countSql, rs -> {
Long count = null;
try {
if (rs.next()) {
@@ -244,7 +208,7 @@ public class HbaseEtlService {
private static boolean executeSqlImport(DataSource ds, String sql, MappingConfig.HbaseMapping hbaseMapping,
HbaseTemplate hbaseTemplate, AtomicLong successCount, List errMsg) {
try {
- sqlRS(ds, sql, rs -> {
+ Util.sqlRS(ds, sql, rs -> {
int i = 1;
try {
diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java
index 2a242093..3ec58369 100644
--- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java
+++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java
@@ -7,6 +7,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.hbase.support.*;
import com.alibaba.otter.canal.client.adapter.support.Dml;
@@ -39,7 +40,7 @@ public class HbaseSyncService {
delete(config, dml);
}
if (logger.isDebugEnabled()) {
- logger.debug("DML: {}", JSON.toJSONString(dml));
+ logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
}
}
} catch (Exception e) {
diff --git a/client-adapter/launcher/pom.xml b/client-adapter/launcher/pom.xml
index 6c39f828..d48e6e71 100644
--- a/client-adapter/launcher/pom.xml
+++ b/client-adapter/launcher/pom.xml
@@ -33,7 +33,7 @@
com.alibaba.otter
canal.client
- ${canal_version}
+ 1.1.2-SNAPSHOT
org.yaml
@@ -107,6 +107,19 @@
jar-with-dependencies
provided
+
+ com.alibaba.otter
+ client-adapter.rdb
+ ${project.version}
+
+
+ *
+ *
+
+
+ jar-with-dependencies
+ provided
+
diff --git a/client-adapter/launcher/src/main/assembly/dev.xml b/client-adapter/launcher/src/main/assembly/dev.xml
index 1be787b5..394fe33f 100644
--- a/client-adapter/launcher/src/main/assembly/dev.xml
+++ b/client-adapter/launcher/src/main/assembly/dev.xml
@@ -43,6 +43,13 @@
**/*
+
+ ../rdb/src/main/resources/
+ /config
+
+ META-INF/**
+
+
target
logs
diff --git a/client-adapter/launcher/src/main/assembly/release.xml b/client-adapter/launcher/src/main/assembly/release.xml
index 57a815c8..163f2015 100644
--- a/client-adapter/launcher/src/main/assembly/release.xml
+++ b/client-adapter/launcher/src/main/assembly/release.xml
@@ -44,6 +44,13 @@
**/*
+
+ ../rdb/src/main/resources/
+ /config
+
+ META-INF/**
+
+
target
logs
diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterConfig.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterConfig.java
index f3e265f8..0f18c996 100644
--- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterConfig.java
+++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterConfig.java
@@ -55,8 +55,6 @@ public class AdapterConfig {
ds.setMaxWait(60000);
ds.setTimeBetweenEvictionRunsMillis(60000);
ds.setMinEvictableIdleTimeMillis(300000);
- ds.setPoolPreparedStatements(false);
- ds.setMaxPoolPreparedStatementPerConnectionSize(20);
ds.setValidationQuery("select 1");
try {
ds.init();
diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java
index 2e861642..045fee37 100644
--- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java
+++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java
@@ -3,6 +3,7 @@ package com.alibaba.otter.canal.adapter.launcher.loader;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
import org.apache.kafka.common.errors.WakeupException;
@@ -36,7 +37,7 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker {
groupId,
canalClientConfig.getBatchSize(),
flatMessage);
- // connector.setSessionTimeout(1L, TimeUnit.MINUTES);
+ connector.setSessionTimeout(30L, TimeUnit.SECONDS);
}
@Override
diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml
index 894d123e..2de61937 100644
--- a/client-adapter/launcher/src/main/resources/application.yml
+++ b/client-adapter/launcher/src/main/resources/application.yml
@@ -4,6 +4,7 @@ logging:
level:
com.alibaba.otter.canal.client.adapter.hbase: DEBUG
com.alibaba.otter.canal.client.adapter.es: DEBUG
+ com.alibaba.otter.canal.client.adapter.rdb: DEBUG
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
@@ -36,6 +37,18 @@ spring:
# - groupId: g2
# outAdapters:
# - name: logger
+# - name: oracle
+# properties:
+# jdbc.driverClassName: oracle.jdbc.OracleDriver
+# jdbc.url: jdbc:oracle:thin:@127.0.0.1:49161:XE
+# jdbc.username: mytest
+# jdbc.password: m121212
+# - name: postgresql
+# properties:
+# jdbc.driverClassName: org.postgresql.Driver
+# jdbc.url: jdbc:postgresql://localhost:5432/postgres
+# jdbc.username: postgres
+# jdbc.password: 121212
# mqTopics:
# - mqMode: rocketmq
# topic: example
@@ -53,3 +66,5 @@ spring:
# adapterConfigs:
# - hbase/mytest_person2.yml
# - es/mytest_user.yml
+# - oracle/mytest_user.yml
+# - postgresql/mytest_user.yml
diff --git a/client-adapter/logger/src/main/java/com/alibaba/otter/canal/client/adapter/logger/LoggerAdapterExample.java b/client-adapter/logger/src/main/java/com/alibaba/otter/canal/client/adapter/logger/LoggerAdapterExample.java
index 8f95b212..e9c693b7 100644
--- a/client-adapter/logger/src/main/java/com/alibaba/otter/canal/client/adapter/logger/LoggerAdapterExample.java
+++ b/client-adapter/logger/src/main/java/com/alibaba/otter/canal/client/adapter/logger/LoggerAdapterExample.java
@@ -3,9 +3,11 @@ package com.alibaba.otter.canal.client.adapter.logger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
-import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
import com.alibaba.otter.canal.client.adapter.support.Dml;
+import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
import com.alibaba.otter.canal.client.adapter.support.SPI;
/**
@@ -27,7 +29,7 @@ public class LoggerAdapterExample implements OuterAdapter {
@Override
public void sync(Dml dml) {
- logger.info("DML: {}", dml.toString());
+ logger.info("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
}
@Override
diff --git a/client-adapter/pom.xml b/client-adapter/pom.xml
index d18d1c19..3c1dcb55 100644
--- a/client-adapter/pom.xml
+++ b/client-adapter/pom.xml
@@ -14,8 +14,6 @@
1.8
1.8
UTF-8
-
- 1.1.2-SNAPSHOT
@@ -24,6 +22,7 @@
hbase
elasticsearch
launcher
+ rdb
diff --git a/client-adapter/rdb/pom.xml b/client-adapter/rdb/pom.xml
new file mode 100644
index 00000000..951579a9
--- /dev/null
+++ b/client-adapter/rdb/pom.xml
@@ -0,0 +1,85 @@
+
+
+
+ canal.client-adapter
+ com.alibaba.otter
+ 1.1.2-SNAPSHOT
+
+ 4.0.0
+ com.alibaba.otter
+ client-adapter.rdb
+ jar
+ canal client adapter rdb module for otter ${project.version}
+
+
+
+ com.alibaba.otter
+ client-adapter.common
+ ${project.version}
+ provided
+
+
+ org.yaml
+ snakeyaml
+ 1.19
+ provided
+
+
+
+ mysql
+ mysql-connector-java
+ 5.1.40
+ test
+
+
+ org.postgresql
+ postgresql
+ 42.1.4
+ test
+
+
+ com.oracle
+ ojdbc6
+ 11.2.0.4.0-atlassian-hosted
+ test
+
+
+ com.microsoft.sqlserver
+ mssql-jdbc
+ 7.0.0.jre8
+ test
+
+
+ junit
+ junit
+ 4.12
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 2.4
+
+
+ jar-with-dependencies
+
+
+
+
+ make-assembly
+ package
+
+ single
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java
new file mode 100644
index 00000000..5dd47a05
--- /dev/null
+++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java
@@ -0,0 +1,221 @@
+package com.alibaba.otter.canal.client.adapter.rdb;
+
+import java.sql.Connection;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.*;
+
+import javax.sql.DataSource;
+
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.alibaba.druid.pool.DruidDataSource;
+import com.alibaba.otter.canal.client.adapter.OuterAdapter;
+import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
+import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfigLoader;
+import com.alibaba.otter.canal.client.adapter.rdb.service.RdbEtlService;
+import com.alibaba.otter.canal.client.adapter.rdb.service.RdbSyncService;
+import com.alibaba.otter.canal.client.adapter.support.*;
+
+public abstract class RdbAdapter implements OuterAdapter {
+
+ private static Logger logger = LoggerFactory.getLogger(RdbAdapter.class);
+
+ private volatile Map rdbMapping = null; // 文件名对应配置
+ private volatile Map mappingConfigCache = null; // 库名-表名对应配置
+
+ private DruidDataSource dataSource;
+
+ private RdbSyncService rdbSyncService;
+
+ @Override
+ public void init(OuterAdapterConfig configuration) {
+ if (mappingConfigCache == null) {
+ synchronized (MappingConfig.class) {
+ if (mappingConfigCache == null) {
+ SPI spi = this.getClass().getAnnotation(SPI.class);
+ rdbMapping = MappingConfigLoader.load(spi.value());
+ mappingConfigCache = new HashMap<>();
+ for (MappingConfig mappingConfig : rdbMapping.values()) {
+ mappingConfigCache.put(StringUtils.trimToEmpty(mappingConfig.getDestination()) + "."
+ + mappingConfig.getDbMapping().getDatabase() + "."
+ + mappingConfig.getDbMapping().getTable(),
+ mappingConfig);
+ }
+ }
+ }
+ }
+
+ Map properties = configuration.getProperties();
+ dataSource = new DruidDataSource();
+ dataSource.setDriverClassName(properties.get("jdbc.driverClassName"));
+ dataSource.setUrl(properties.get("jdbc.url"));
+ dataSource.setUsername(properties.get("jdbc.username"));
+ dataSource.setPassword(properties.get("jdbc.password"));
+ dataSource.setInitialSize(1);
+ dataSource.setMinIdle(1);
+ dataSource.setMaxActive(2);
+ dataSource.setMaxWait(60000);
+ dataSource.setTimeBetweenEvictionRunsMillis(60000);
+ dataSource.setMinEvictableIdleTimeMillis(300000);
+
+ try {
+ dataSource.init();
+ } catch (SQLException e) {
+ logger.error("ERROR ## failed to initial datasource: " + properties.get("jdbc.url"), e);
+ }
+
+ rdbMapping.values().forEach(config -> {
+ try {
+ MappingConfig.DbMapping dbMapping = config.getDbMapping();
+ // 从源表加载所有字段名
+ if (dbMapping.getAllColumns() == null) {
+ synchronized (RdbSyncService.class) {
+ if (dbMapping.getAllColumns() == null) {
+ DataSource srcDS = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
+ Connection srcConn = srcDS.getConnection();
+ String srcMetaSql = "SELECT * FROM " + dbMapping.getDatabase() + "." + dbMapping.getTable()
+ + " WHERE 1=2 ";
+ List srcColumns = new ArrayList<>();
+ Util.sqlRS(srcConn, srcMetaSql, rs -> {
+ try {
+ ResultSetMetaData rmd = rs.getMetaData();
+ int cnt = rmd.getColumnCount();
+ for (int i = 1; i <= cnt; i++) {
+ srcColumns.add(rmd.getColumnName(i).toLowerCase());
+ }
+ } catch (SQLException e) {
+ logger.error(e.getMessage(), e);
+ }
+ });
+ Map columnsMap = new LinkedHashMap<>();
+
+ for (String srcColumn : srcColumns) {
+ String targetColumn = srcColumn;
+ if (dbMapping.getTargetColumns() != null) {
+ for (Map.Entry entry : dbMapping.getTargetColumns().entrySet()) {
+ String targetColumnName = entry.getKey();
+ String srcColumnName = entry.getValue();
+
+ if (srcColumnName != null
+ && srcColumnName.toLowerCase().equals(srcColumn.toUpperCase())) {
+ targetColumn = targetColumnName;
+ }
+ }
+ }
+ columnsMap.put(targetColumn, srcColumn);
+ }
+ dbMapping.setAllColumns(columnsMap);
+ }
+ }
+ }
+ } catch (SQLException e) {
+ logger.error(e.getMessage(), e);
+ }
+ });
+
+ rdbSyncService = new RdbSyncService(dataSource);
+ }
+
+ @Override
+ public void sync(Dml dml) {
+ String destination = StringUtils.trimToEmpty(dml.getDestination());
+ String database = dml.getDatabase();
+ String table = dml.getTable();
+ MappingConfig config = mappingConfigCache.get(destination + "." + database + "." + table);
+
+ rdbSyncService.sync(config, dml);
+ }
+
+ @Override
+ public EtlResult etl(String task, List params) {
+ EtlResult etlResult = new EtlResult();
+ MappingConfig config = rdbMapping.get(task);
+ if (config != null) {
+ DataSource srcDataSource = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
+ if (srcDataSource != null) {
+ return RdbEtlService.importData(srcDataSource, dataSource, config, params);
+ } else {
+ etlResult.setSucceeded(false);
+ etlResult.setErrorMessage("DataSource not found");
+ return etlResult;
+ }
+ } else {
+ StringBuilder resultMsg = new StringBuilder();
+ boolean resSucc = true;
+ // ds不为空说明传入的是destination
+ for (MappingConfig configTmp : rdbMapping.values()) {
+ // 取所有的destination为task的配置
+ if (configTmp.getDestination().equals(task)) {
+ DataSource srcDataSource = DatasourceConfig.DATA_SOURCES.get(configTmp.getDataSourceKey());
+ if (srcDataSource == null) {
+ continue;
+ }
+ EtlResult etlRes = RdbEtlService.importData(srcDataSource, dataSource, configTmp, params);
+ if (!etlRes.getSucceeded()) {
+ resSucc = false;
+ resultMsg.append(etlRes.getErrorMessage()).append("\n");
+ } else {
+ resultMsg.append(etlRes.getResultMessage()).append("\n");
+ }
+ }
+ }
+ if (resultMsg.length() > 0) {
+ etlResult.setSucceeded(resSucc);
+ if (resSucc) {
+ etlResult.setResultMessage(resultMsg.toString());
+ } else {
+ etlResult.setErrorMessage(resultMsg.toString());
+ }
+ return etlResult;
+ }
+ }
+ etlResult.setSucceeded(false);
+ etlResult.setErrorMessage("Task not found");
+ return etlResult;
+ }
+
+ @Override
+ public Map count(String task) {
+ MappingConfig config = rdbMapping.get(task);
+ MappingConfig.DbMapping dbMapping = config.getDbMapping();
+ String sql = "SELECT COUNT(1) AS cnt FROM " + dbMapping.getTargetTable();
+ Connection conn = null;
+ Map res = new LinkedHashMap<>();
+ try {
+ conn = dataSource.getConnection();
+ Util.sqlRS(conn, sql, rs -> {
+ try {
+ if (rs.next()) {
+ Long rowCount = rs.getLong("cnt");
+ res.put("count", rowCount);
+ }
+ } catch (SQLException e) {
+ logger.error(e.getMessage(), e);
+ }
+ });
+ } catch (SQLException e) {
+ logger.error(e.getMessage(), e);
+ } finally {
+ if (conn != null) {
+ try {
+ conn.close();
+ } catch (SQLException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+ }
+ res.put("targetTable", dbMapping.getTargetTable());
+
+ return res;
+ }
+
+ @Override
+ public void destroy() {
+ if (dataSource != null) {
+ dataSource.close();
+ }
+ }
+}
diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/MysqlAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/MysqlAdapter.java
new file mode 100644
index 00000000..ea73b1c0
--- /dev/null
+++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/MysqlAdapter.java
@@ -0,0 +1,8 @@
+package com.alibaba.otter.canal.client.adapter.rdb.adapters;
+
+import com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter;
+import com.alibaba.otter.canal.client.adapter.support.SPI;
+
+@SPI("mysql")
+public class MysqlAdapter extends RdbAdapter {
+}
diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/OracleAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/OracleAdapter.java
new file mode 100644
index 00000000..fe6111a2
--- /dev/null
+++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/OracleAdapter.java
@@ -0,0 +1,8 @@
+package com.alibaba.otter.canal.client.adapter.rdb.adapters;
+
+import com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter;
+import com.alibaba.otter.canal.client.adapter.support.SPI;
+
+@SPI("oracle")
+public class OracleAdapter extends RdbAdapter {
+}
diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/PostgresqlAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/PostgresqlAdapter.java
new file mode 100644
index 00000000..2cf4a444
--- /dev/null
+++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/PostgresqlAdapter.java
@@ -0,0 +1,8 @@
+package com.alibaba.otter.canal.client.adapter.rdb.adapters;
+
+import com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter;
+import com.alibaba.otter.canal.client.adapter.support.SPI;
+
+@SPI("postgresql")
+public class PostgresqlAdapter extends RdbAdapter {
+}
diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/SqlserverAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/SqlserverAdapter.java
new file mode 100644
index 00000000..2fe7e0ed
--- /dev/null
+++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/SqlserverAdapter.java
@@ -0,0 +1,8 @@
+package com.alibaba.otter.canal.client.adapter.rdb.adapters;
+
+import com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter;
+import com.alibaba.otter.canal.client.adapter.support.SPI;
+
+@SPI("sqlserver")
+public class SqlserverAdapter extends RdbAdapter {
+}
diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java
new file mode 100644
index 00000000..da7e6ef8
--- /dev/null
+++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java
@@ -0,0 +1,162 @@
+package com.alibaba.otter.canal.client.adapter.rdb.config;
+
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * RDB表映射配置
+ *
+ * @author rewerma 2018-11-07 下午02:41:34
+ * @version 1.0.0
+ */
+public class MappingConfig {
+
+ private String dataSourceKey; // 数据源key
+
+ private String destination; // canal实例或MQ的topic
+
+ private DbMapping dbMapping; // db映射配置
+
+ public String getDataSourceKey() {
+ return dataSourceKey;
+ }
+
+ public void setDataSourceKey(String dataSourceKey) {
+ this.dataSourceKey = dataSourceKey;
+ }
+
+ public DbMapping getDbMapping() {
+ return dbMapping;
+ }
+
+ public void setDbMapping(DbMapping dbMapping) {
+ this.dbMapping = dbMapping;
+ }
+
+ public String getDestination() {
+ return destination;
+ }
+
+ public void setDestination(String destination) {
+ this.destination = destination;
+ }
+
+ public void validate() {
+ if (dbMapping.database == null || dbMapping.database.isEmpty()) {
+ throw new NullPointerException("dbMapping.database");
+ }
+ if (dbMapping.table == null || dbMapping.table.isEmpty()) {
+ throw new NullPointerException("dbMapping.table");
+ }
+ if (dbMapping.targetTable == null || dbMapping.targetTable.isEmpty()) {
+ throw new NullPointerException("dbMapping.targetTable");
+ }
+ }
+
+ public static class DbMapping {
+
+ private String database; // 数据库名或schema名
+ private String table; // 表面名
+ private Map targetPk; // 目标表主键字段
+ private boolean mapAll = false; // 映射所有字段
+ private String targetTable; // 目标表名
+ private Map targetColumns; // 目标表字段映射
+
+ private String etlCondition; // etl条件sql
+
+ private Set families = new LinkedHashSet<>(); // column family列表
+ private int readBatch = 5000;
+ private int commitBatch = 5000; // etl等批量提交大小
+
+ private volatile Map allColumns; // mapAll为true,自动设置改字段
+
+ public String getDatabase() {
+ return database;
+ }
+
+ public void setDatabase(String database) {
+ this.database = database;
+ }
+
+ public String getTable() {
+ return table;
+ }
+
+ public void setTable(String table) {
+ this.table = table;
+ }
+
+ public Map getTargetPk() {
+ return targetPk;
+ }
+
+ public void setTargetPk(Map targetPk) {
+ this.targetPk = targetPk;
+ }
+
+ public boolean isMapAll() {
+ return mapAll;
+ }
+
+ public void setMapAll(boolean mapAll) {
+ this.mapAll = mapAll;
+ }
+
+ public String getTargetTable() {
+ return targetTable;
+ }
+
+ public void setTargetTable(String targetTable) {
+ this.targetTable = targetTable;
+ }
+
+ public Map getTargetColumns() {
+ return targetColumns;
+ }
+
+ public void setTargetColumns(Map targetColumns) {
+ this.targetColumns = targetColumns;
+ }
+
+ public String getEtlCondition() {
+ return etlCondition;
+ }
+
+ public void setEtlCondition(String etlCondition) {
+ this.etlCondition = etlCondition;
+ }
+
+ public Set getFamilies() {
+ return families;
+ }
+
+ public void setFamilies(Set families) {
+ this.families = families;
+ }
+
+ public int getReadBatch() {
+ return readBatch;
+ }
+
+ public void setReadBatch(int readBatch) {
+ this.readBatch = readBatch;
+ }
+
+ public int getCommitBatch() {
+ return commitBatch;
+ }
+
+ public void setCommitBatch(int commitBatch) {
+ this.commitBatch = commitBatch;
+ }
+
+ public Map getAllColumns() {
+ return allColumns;
+ }
+
+ public void setAllColumns(Map allColumns) {
+ this.allColumns = allColumns;
+ }
+ }
+}
diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfigLoader.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfigLoader.java
new file mode 100644
index 00000000..8658d45e
--- /dev/null
+++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfigLoader.java
@@ -0,0 +1,100 @@
+package com.alibaba.otter.canal.client.adapter.rdb.config;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.yaml.snakeyaml.Yaml;
+
+import com.alibaba.otter.canal.client.adapter.support.AdapterConfigs;
+
+/**
+ * RDB表映射配置加载器
+ *
+ * @author rewerma 2018-11-07 下午02:41:34
+ * @version 1.0.0
+ */
+public class MappingConfigLoader {
+
+ private static Logger logger = LoggerFactory.getLogger(MappingConfigLoader.class);
+
+ /**
+ * 加载HBase表映射配置
+ *
+ * @return 配置名/配置文件名--对象
+ */
+ public static Map load(String name) {
+ logger.info("## Start loading rdb mapping config ... ");
+
+ Map result = new LinkedHashMap<>();
+
+ Collection configs = AdapterConfigs.get(name);
+ if (configs == null) {
+ return result;
+ }
+ for (String c : configs) {
+ if (c == null) {
+ continue;
+ }
+ c = c.trim();
+ if (c.equals("") || c.startsWith("#")) {
+ continue;
+ }
+
+ String configContent = null;
+
+ if (c.endsWith(".yml")) {
+ configContent = readConfigContent(name + "/" + c);
+ }
+
+ MappingConfig config = new Yaml().loadAs(configContent, MappingConfig.class);
+
+ try {
+ config.validate();
+ } catch (Exception e) {
+ throw new RuntimeException("ERROR Config: " + c + " " + e.getMessage(), e);
+ }
+ result.put(c, config);
+ }
+
+ logger.info("## Rdb mapping config loaded");
+ return result;
+ }
+
+ public static String readConfigContent(String config) {
+ InputStream in = null;
+ try {
+ // 先取本地文件,再取类路径
+ File configFile = new File("../config/" + config);
+ if (configFile.exists()) {
+ in = new FileInputStream(configFile);
+ } else {
+ in = MappingConfigLoader.class.getClassLoader().getResourceAsStream(config);
+ }
+ if (in == null) {
+ throw new RuntimeException("Rdb mapping config file not found.");
+ }
+
+ byte[] bytes = new byte[in.available()];
+ in.read(bytes);
+ return new String(bytes, StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ throw new RuntimeException("Read rdb mapping config error. ", e);
+ } finally {
+ try {
+ if (in != null) {
+ in.close();
+ }
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+}
diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbEtlService.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbEtlService.java
new file mode 100644
index 00000000..321c9f21
--- /dev/null
+++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbEtlService.java
@@ -0,0 +1,290 @@
+package com.alibaba.otter.canal.client.adapter.rdb.service;
+
+import java.sql.*;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.sql.DataSource;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
+import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig.DbMapping;
+import com.alibaba.otter.canal.client.adapter.support.EtlResult;
+import com.alibaba.otter.canal.client.adapter.support.Util;
+import com.google.common.base.Joiner;
+
+/**
+ * RDB ETL 操作业务类
+ *
+ * @author rewerma @ 2018-11-7
+ * @version 1.0.0
+ */
+public class RdbEtlService {
+
+ private static final Logger logger = LoggerFactory.getLogger(RdbEtlService.class);
+
+ /**
+ * 导入数据
+ */
+ public static EtlResult importData(DataSource srcDS, DataSource targetDS, MappingConfig config,
+ List params) {
+ EtlResult etlResult = new EtlResult();
+ AtomicLong successCount = new AtomicLong();
+ List errMsg = new ArrayList<>();
+ String hbaseTable = "";
+ try {
+ if (config == null) {
+ logger.error("Config is null!");
+ etlResult.setSucceeded(false);
+ etlResult.setErrorMessage("Config is null!");
+ return etlResult;
+ }
+ DbMapping dbMapping = config.getDbMapping();
+
+ long start = System.currentTimeMillis();
+
+ // 拼接sql
+ StringBuilder sql = new StringBuilder(
+ "SELECT * FROM " + dbMapping.getDatabase() + "." + dbMapping.getTable());
+
+ // 拼接条件
+ appendCondition(params, dbMapping, srcDS, sql);
+
+ // 获取总数
+ String countSql = "SELECT COUNT(1) FROM ( " + sql + ") _CNT ";
+ long cnt = (Long) Util.sqlRS(srcDS, countSql, rs -> {
+ Long count = null;
+ try {
+ if (rs.next()) {
+ count = ((Number) rs.getObject(1)).longValue();
+ }
+ } catch (Exception e) {
+ logger.error(e.getMessage(), e);
+ }
+ return count == null ? 0 : count;
+ });
+
+ // 当大于1万条记录时开启多线程
+ if (cnt >= 10000) {
+ int threadCount = 3;
+ long perThreadCnt = cnt / threadCount;
+ ExecutorService executor = Executors.newFixedThreadPool(threadCount);
+ List> futures = new ArrayList<>(threadCount);
+ for (int i = 0; i < threadCount; i++) {
+ long offset = i * perThreadCnt;
+ Long size = null;
+ if (i != threadCount - 1) {
+ size = perThreadCnt;
+ }
+ String sqlFinal;
+ if (size != null) {
+ sqlFinal = sql + " LIMIT " + offset + "," + size;
+ } else {
+ sqlFinal = sql + " LIMIT " + offset + "," + cnt;
+ }
+ Future future = executor.submit(
+ () -> executeSqlImport(params, srcDS, targetDS, sqlFinal, dbMapping, successCount, errMsg));
+ futures.add(future);
+ }
+
+ for (Future future : futures) {
+ future.get();
+ }
+
+ executor.shutdown();
+ } else {
+ executeSqlImport(params, srcDS, targetDS, sql.toString(), dbMapping, successCount, errMsg);
+ }
+
+ logger.info(
+ dbMapping.getTable() + " etl completed in: " + (System.currentTimeMillis() - start) / 1000 + "s!");
+
+ etlResult.setResultMessage("导入目标表 " + dbMapping.getTargetTable() + " 数据:" + successCount.get() + " 条");
+ } catch (Exception e) {
+ logger.error(e.getMessage(), e);
+ errMsg.add(hbaseTable + " etl failed! ==>" + e.getMessage());
+ }
+
+ if (errMsg.isEmpty()) {
+ etlResult.setSucceeded(true);
+ } else {
+ etlResult.setErrorMessage(Joiner.on("\n").join(errMsg));
+ }
+ return etlResult;
+ }
+
+ private static void appendCondition(List params, DbMapping dbMapping, DataSource ds,
+ StringBuilder sql) throws SQLException {
+ if (params != null && params.size() == 1 && dbMapping.getEtlCondition() == null) {
+ AtomicBoolean stExists = new AtomicBoolean(false);
+ // 验证是否有SYS_TIME字段
+ Util.sqlRS(ds, sql.toString(), rs -> {
+ try {
+ ResultSetMetaData rsmd = rs.getMetaData();
+ int cnt = rsmd.getColumnCount();
+ for (int i = 1; i <= cnt; i++) {
+ String columnName = rsmd.getColumnName(i);
+ if ("SYS_TIME".equalsIgnoreCase(columnName)) {
+ stExists.set(true);
+ break;
+ }
+ }
+ } catch (Exception e) {
+ // ignore
+ }
+ return null;
+ });
+ if (stExists.get()) {
+ sql.append(" WHERE SYS_TIME >= '").append(params.get(0)).append("' ");
+ }
+ } else if (dbMapping.getEtlCondition() != null && params != null) {
+ String etlCondition = dbMapping.getEtlCondition();
+ int size = params.size();
+ for (int i = 0; i < size; i++) {
+ etlCondition = etlCondition.replace("{" + i + "}", params.get(i));
+ }
+
+ sql.append(" ").append(etlCondition);
+ }
+ }
+
+ /**
+ * 执行导入
+ */
+ private static boolean executeSqlImport(List params, DataSource srcDS, DataSource targetDS, String sql,
+ DbMapping dbMapping, AtomicLong successCount, List errMsg) {
+ try {
+ Util.sqlRS(srcDS, sql, rs -> {
+ int idx = 1;
+
+ try {
+ boolean completed = false;
+
+ Map columnType = new LinkedHashMap<>();
+ ResultSetMetaData rsd = rs.getMetaData();
+ int columnCount = rsd.getColumnCount();
+ for (int i = 1; i <= columnCount; i++) {
+ columnType.put(rsd.getColumnName(i).toLowerCase(), rsd.getColumnType(i));
+ }
+
+ Map columnsMap;
+ if (dbMapping.isMapAll()) {
+ columnsMap = dbMapping.getAllColumns();
+ } else {
+ columnsMap = dbMapping.getTargetColumns();
+ }
+
+ StringBuilder insertSql = new StringBuilder();
+ insertSql.append("INSERT INTO ").append(dbMapping.getTargetTable()).append(" (");
+ columnsMap
+ .forEach((targetColumnName, srcColumnName) -> insertSql.append(targetColumnName).append(","));
+
+ int len = insertSql.length();
+ insertSql.delete(len - 1, len).append(") VALUES (");
+ int mapLen = columnsMap.size();
+ for (int i = 0; i < mapLen; i++) {
+ insertSql.append("?,");
+ }
+ len = insertSql.length();
+ insertSql.delete(len - 1, len).append(")");
+ try (Connection connTarget = targetDS.getConnection();
+ PreparedStatement pstmt = connTarget.prepareStatement(insertSql.toString())) {
+ connTarget.setAutoCommit(false);
+
+ while (rs.next()) {
+ pstmt.clearParameters();
+
+ // 删除数据
+ Map values = new LinkedHashMap<>();
+ StringBuilder deleteSql = new StringBuilder(
+ "DELETE FROM " + dbMapping.getTargetTable() + " WHERE ");
+ appendCondition(dbMapping, deleteSql, values, rs);
+ try (PreparedStatement pstmt2 = connTarget.prepareStatement(deleteSql.toString())) {
+ int k = 1;
+ for (Object val : values.values()) {
+ pstmt2.setObject(k++, val);
+ }
+ pstmt2.execute();
+ }
+
+ int i = 1;
+ for (Map.Entry entry : columnsMap.entrySet()) {
+ String targetClolumnName = entry.getKey();
+ String srcColumnName = entry.getValue();
+ if (srcColumnName == null) {
+ srcColumnName = targetClolumnName;
+ }
+
+ Integer type = columnType.get(targetClolumnName.toLowerCase());
+
+ Object value = rs.getObject(srcColumnName);
+ if (value != null) {
+ RdbSyncService.setPStmt(type, pstmt, value, i);
+ } else {
+ pstmt.setNull(i, type);
+ }
+
+ i++;
+ }
+
+ pstmt.execute();
+ if (logger.isTraceEnabled()) {
+ logger.trace("Insert into target table, sql: {}", insertSql);
+ }
+
+ if (idx % dbMapping.getCommitBatch() == 0) {
+ connTarget.commit();
+ completed = true;
+ }
+ idx++;
+ successCount.incrementAndGet();
+ if (logger.isDebugEnabled()) {
+ logger.debug("successful import count:" + successCount.get());
+ }
+ }
+ if (!completed) {
+ connTarget.commit();
+ }
+ }
+
+ } catch (Exception e) {
+ logger.error(dbMapping.getTable() + " etl failed! ==>" + e.getMessage(), e);
+ errMsg.add(dbMapping.getTable() + " etl failed! ==>" + e.getMessage());
+ }
+ return idx;
+ });
+ return true;
+ } catch (Exception e) {
+ logger.error(e.getMessage(), e);
+ return false;
+ }
+ }
+
+ /**
+ * 拼接目标表主键where条件
+ */
+ private static void appendCondition(DbMapping dbMapping, StringBuilder sql, Map values,
+ ResultSet rs) throws SQLException {
+ // 拼接主键
+ for (Map.Entry entry : dbMapping.getTargetPk().entrySet()) {
+ String targetColumnName = entry.getKey();
+ String srcColumnName = entry.getValue();
+ if (srcColumnName == null) {
+ srcColumnName = targetColumnName;
+ }
+ sql.append(targetColumnName).append("=? AND ");
+ values.put(targetColumnName, rs.getObject(srcColumnName));
+ }
+ int len = sql.length();
+ sql.delete(len - 4, len);
+ }
+}
diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java
new file mode 100644
index 00000000..ab46bf4b
--- /dev/null
+++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java
@@ -0,0 +1,527 @@
+package com.alibaba.otter.canal.client.adapter.rdb.service;
+
+import java.io.Reader;
+import java.io.StringReader;
+import java.math.BigDecimal;
+import java.nio.charset.StandardCharsets;
+import java.sql.*;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+
+import javax.sql.DataSource;
+
+import com.alibaba.otter.canal.client.adapter.support.Util;
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.serializer.SerializerFeature;
+import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
+import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig.DbMapping;
+import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
+import com.alibaba.otter.canal.client.adapter.support.Dml;
+
+/**
+ * RDB同步操作业务
+ *
+ * @author rewerma 2018-11-7 下午06:45:49
+ * @version 1.0.0
+ */
+public class RdbSyncService {
+
+ private static final Logger logger = LoggerFactory.getLogger(RdbSyncService.class);
+
+ private final Map> COLUMNS_TYPE_CACHE = new ConcurrentHashMap<>();
+
+ private DataSource dataSource;
+
+ public RdbSyncService(DataSource dataSource){
+ this.dataSource = dataSource;
+ }
+
+ public void sync(MappingConfig config, Dml dml) {
+ try {
+ if (config != null) {
+ String type = dml.getType();
+ if (type != null && type.equalsIgnoreCase("INSERT")) {
+ insert(config, dml);
+ } else if (type != null && type.equalsIgnoreCase("UPDATE")) {
+ update(config, dml);
+ } else if (type != null && type.equalsIgnoreCase("DELETE")) {
+ delete(config, dml);
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
+ }
+ }
+ } catch (Exception e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 插入操作
+ *
+ * @param config 配置项
+ * @param dml DML数据
+ */
+ private void insert(MappingConfig config, Dml dml) throws SQLException {
+ List