1.前言
最近根据项目的需要,要做一个数据同步的项目,需要使用到kettle在网上查看了很多相关的帖子,很多
都是直接使用kettle的客户端工具spoon进行直接同步,通过代码实现的很散。后面自己根据项目中的实际
需求,实现一个简单的全量更新,满足了项目的需求,但还存在一些问题需要改进。
2. 实现
1. 集成jar包
kettle没有开放的SDK,所以需要自己下载kettle,找到lib文件夹中的jar包,导入到Springboot项目中
找到核心的几个Jar包commons-lang,commons-vfs2,kettle-core,kettle-engine,metastore
在resources目录下新建一个lib文件夹,将上述所有jar包放入到lib文件夹中
kettle
core
7.1.0.0-12
system
${project.basedir}/src/main/resources/lib/kettle-core-7.1.0.0-12.jar
kettle
engine
7.1.0.0-12
system
${project.basedir}/src/main/resources/lib/kettle-engine-7.1.0.0-12.jar
kettle
metastore
7.1.0.0-12
system
${project.basedir}/src/main/resources/lib/metastore-7.1.0.0-12.jar
commons
vfs2
2.1
system
${project.basedir}/src/main/resources/lib/commons-vfs2-2.1-20150824.jar
commons
lang
2.6
system
${project.basedir}/src/main/resources/lib/commons-lang-2.6.jar
通过这样本地执行是没有问题,但是如果要打包部署到线上,还需要在maven中配置打包时将项目中的jar包也打包
还需要在Maven中添加一个配置
org.springframework.boot
spring-boot-maven-plugin
true
2.Spoon创建ktr文件
这里我采用的方式是分为三个步骤,第一次是获取到源数据库的DDL语句,然后再执行DDL语句,最后再进行数据的拷贝,
因为如果目标数据库不存在表是会报错的,所以必须保证目标数据库存在一致的表结构,所以执行DDL语句时 先执行删除
表语句再执行创建DDL语句,保证数据库的结构是一致的。
1.获取DDL语句
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191126103554935.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzI4NDUxMA==,size_16,color_FFFFFF,t_70)
2.执行DDL语句
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191126103603791.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzI4NDUxMA==,size_16,color_FFFFFF,t_70)
3.数据拷贝
![在这里插入图片描述](https://img-blog.csdnimg.cn/2019112610361280.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzI4NDUxMA==,size_16,color_FFFFFF,t_70)
创建好三个ktr文件后,放入到resources下,新建一个kettleFile文件夹,将三个文件放入文件夹中
3. 代码实现
package com.xtoneict.kettle;
import com.xtoneict.action.SaveConnectParameterAction;
import lombok.extern.slf4j.Slf4j;
import org.pentaho.di.core.KettleEnvironment;
import org.pentaho.di.core.RowMetaAndData;
import org.pentaho.di.core.database.DatabaseMeta;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.logging.KettleLogStore;
import org.pentaho.di.trans.Trans;
import org.pentaho.di.trans.TransMeta;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
public class KettleUtils {
/**
* 获取kettle中的DDL语句
* @param originAction 源数据库信息
* @param ddlPath 获取DDL文件的输入流
* @return
*/
public static Map getKettleDDL(SaveConnectParameterAction originAction, InputStream ddlPath) throws KettleException {
Map map = new HashMap<>();
KettleEnvironment.init();
TransMeta transMeta = new TransMeta(ddlPath,null,true,null,null);
List databases = transMeta.getDatabases();
DatabaseMeta originMeta = databases.get(0);
setDatabaseMeta(originMeta,originAction);
//创建tran对象
Trans trans = new Trans(transMeta);
trans.execute(null);
trans.waitUntilFinished();
if (trans.getErrors() != 0) {
String[] errMsgList = KettleLogStore.getAppender().getBuffer(trans.getLogChannelId(), false).toString().split("\n\r\n");
log.error("获取DDL语句出现错误"+errMsgList[0]);
}
//获取返回结果
List resultRows = trans.getResultRows();
resultRows.forEach(rowMetaAndData ->{
Object[] data = rowMetaAndData.getData();
map.put(data[0].toString(),data[3].toString());
});
return map;
}
/**
* 数据同步
* @param originAction 源数据库信息
* @param targetAction 目标数据库信息
* @param dataPath 数据拷贝文件的输入流
* @param ddlPath 创建DDL语句的输入流
* @param tableNames 选择拷贝表名称
* @return
*/
public static Boolean copyData(SaveConnectParameterAction originAction, SaveConnectParameterAction targetAction, InputStream dataPath, InputStream ddlPath,
String ddlStr, List tableNames) throws KettleException {
KettleEnvironment.init();
initDDL(targetAction,ddlPath,ddlStr);
TransMeta transMeta = new TransMeta(dataPath,null,true,null,null);
List databases = transMeta.getDatabases();
// 0为源 1为目标
DatabaseMeta originMeta = databases.get(0);
setDatabaseMeta(originMeta,originAction);
DatabaseMeta targetMeta = databases.get(1);
setDatabaseMeta(targetMeta,targetAction);
//创建tran对象
Trans trans = new Trans(transMeta);
tableNames.forEach(v-> {
try {
//设置参数
trans.setVariable("table_name", v);
trans.execute(null);
trans.waitUntilFinished();
if (trans.getErrors() != 0) {
//失败 保存
String[] errMsgList = KettleLogStore.getAppender().getBuffer(trans.getLogChannelId(), false).toString().split("\n\r\n");
log.error("数据同步出现错误"+errMsgList[0]);
}
} catch (KettleException e) {
log.error("Kettle数据同步出现异常"+e.getMessage());
}
});
return Boolean.TRUE;
}
/**
* 执行DDL语句 创建表结构
* @param originAction 源数据库信息
* @param ddlPath DDL文件的输入流
* @param ddlStr 执行的DDL语句
* @throws KettleException
*/
private static void initDDL(SaveConnectParameterAction originAction, InputStream ddlPath,
String ddlStr) throws KettleException {
TransMeta transMeta = new TransMeta(ddlPath,null,true,null,null);
DatabaseMeta database = transMeta.getDatabase(0);
setDatabaseMeta(database,originAction);
Trans trans = new Trans(transMeta);
trans.setVariable("ddl",ddlStr);
trans.execute(null);
trans.waitUntilFinished();
if (trans.getErrors() != 0) {
//失败 保存
String[] errMsgList = KettleLogStore.getAppender().getBuffer(trans.getLogChannelId(), false).toString().split("\n\r\n");
log.error("执行DDL语句出现错误"+errMsgList[0]);
}
}
/**
* 设置数据库连接参数
* @param databaseMeta
* @param action
*/
public static void setDatabaseMeta(DatabaseMeta databaseMeta, SaveConnectParameterAction action){
//连接地址
databaseMeta.setHostname(action.getHost());
//数据库名称
databaseMeta.setDBName(action.getDbName());
//端口
databaseMeta.setDBPort(action.getPort());
//用户
databaseMeta.setUsername(action.getUsername());
//密码
databaseMeta.setPassword(action.getPassword());
}
}
3.总结
这样实现了通过代码进行动态的数据拷贝,但是还是存在很多问题,只是简单实现了需求。但是kettle的相关资料比较
少,个人觉得代码操作的意义不是很大,但是通过代码实现增量更新会很麻烦 缺少资料进行操作。全量更新数据量太大
有很多问题,个人觉得kettle使用spoon工具类操作更优