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核心包-->
<dependency>
<!--可以随便写-->
<groupId>kettle</groupId>
<!--可以随便写-->
<artifactId>core</artifactId>
<!--可以随便写-->
<version>7.1.0.0-12</version>
<scope>system</scope>
<!--配置项目中对应的jar包路径-->
<systemPath>${project.basedir}/src/main/resources/lib/kettle-core-7.1.0.0-12.jar</systemPath>
</dependency>
<dependency>
<groupId>kettle</groupId>
<artifactId>engine</artifactId>
<version>7.1.0.0-12</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/resources/lib/kettle-engine-7.1.0.0-12.jar</systemPath>
</dependency>
<dependency>
<groupId>kettle</groupId>
<artifactId>metastore</artifactId>
<version>7.1.0.0-12</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/resources/lib/metastore-7.1.0.0-12.jar</systemPath>
</dependency>
<dependency>
<groupId>commons</groupId>
<artifactId>vfs2</artifactId>
<version>2.1</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/resources/lib/commons-vfs2-2.1-20150824.jar</systemPath>
</dependency>
<dependency>
<groupId>commons</groupId>
<artifactId>lang</artifactId>
<version>2.6</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/resources/lib/commons-lang-2.6.jar</systemPath>
</dependency>
通过这样本地执行是没有问题,但是如果要打包部署到线上,还需要在maven中配置打包时将项目中的jar包也打包
还需要在Maven中添加一个配置
<build>
<plugins>
<!--maven打包将本地jar包也打入-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<includeSystemScope>true</includeSystemScope>
</configuration>
</plugin>
</plugins>
</build>
2.Spoon创建ktr文件
这里我采用的方式是分为三个步骤,第一次是获取到源数据库的DDL语句,然后再执行DDL语句,最后再进行数据的拷贝,
因为如果目标数据库不存在表是会报错的,所以必须保证目标数据库存在一致的表结构,所以执行DDL语句时 先执行删除
表语句再执行创建DDL语句,保证数据库的结构是一致的。
1.获取DDL语句
2.执行DDL语句
3.数据拷贝
创建好三个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<String, String> getKettleDDL(SaveConnectParameterAction originAction, InputStream ddlPath) throws KettleException {
Map<String, String> map = new HashMap<>();
KettleEnvironment.init();
TransMeta transMeta = new TransMeta(ddlPath,null,true,null,null);
List<DatabaseMeta> 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<RowMetaAndData> 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<String> tableNames) throws KettleException {
KettleEnvironment.init();
initDDL(targetAction,ddlPath,ddlStr);
TransMeta transMeta = new TransMeta(dataPath,null,true,null,null);
List<DatabaseMeta> 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工具类操作更优