文章目录
关于ETL的流程熟悉以及自己的见解
一些概念
ETL
ETL,是英文 Extract-Transform-Load 的缩写,用来描述将数据从来源端经过抽取(extract)、转换(transform)、加载(load)至目的端的过程.(数据仓 说法就是从数据源抽取数据出来,进行清洗加工转换,然后加载到定义好的。
kettle
Kettle 中文名称叫水壶,该项目的主程序员MATT 希望把各种数据放到一个壶里,然后以一种指定的格式流出。
- Kettle是一款国外开源的ETL工具,纯java编写,可以在Window、Linux、Unix上运行,绿色无需安装,数据抽取高效稳定。
- Kettle这个ETL工具集,它允许你管理来自不同数据库的数据,通过提供一个图形化的用户环境来描述你想做什么,而不是你想怎么做。
- Kettle中有两种脚本文件,transformation和job,transformation完成针对数据的基础转换,job则完成整个工作流的控制(也就是说先转换数据,再做工作流)
解读:如图,可以看到,transformation中进行了发送、读写的步骤转换;job关联transformation基础转换,在进行一系列的工作流操作,比如Ftp获取相关文件、执行SQL转换信息等
Kettle编写抽取数据
下面的讲解没有图形化kettle的,相对应的是底层代码的剖析。因为基于公司的业务没有用到图形化,有兴趣的小伙伴可以转向相关学习网址:https://cloud.tencent.com/developer/article/1585297
(打开项目)
引用jar
这是我的pom文件,根据不同需求进行微调修改
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<kettle.version>5.1.0.0-752</kettle.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- 引入jdbc支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- 引入web支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入devtools支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!--kettle的支持:这里的core和engine版本最好一致-->
<dependency>
<groupId>pentaho-kettle</groupId>
<artifactId>kettle-core</artifactId>
<version>${kettle.version}</version>
</dependency>
<dependency>
<groupId>pentaho-kettle</groupId>
<artifactId>kettle-engine</artifactId>
<version>${kettle.version}</version>
</dependency>
<!--mysql支持-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.44</version>
</dependency>
<!--lombok支持:简洁开发-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--各种基础工具包-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8</version>
</dependency>
<dependency>
<groupId>commons-vfs</groupId>
<artifactId>commons-vfs</artifactId>
<version>20100924-pentaho</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
库表文件
找一个可以测试的mysql库,建立以下的表文件,并在源表插入若干测试数据
Create database test;
-- 创建源表
CREATE TABLE `etl_src_table` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`age` int(3) DEFAULT NULL,
`mail` varchar(40) DEFAULT NULL,
`phone` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
-- 创建目标表
CREATE TABLE `etl_dest_table` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`age` int(3) DEFAULT NULL,
`mail` varchar(40) DEFAULT NULL,
`phone` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
数据库连接类
此类封装了数据库连接信息
/**
* @author qijian
* @description 数据库类
* @date 2021/8/17 - 10:20
*/
@Data
public class DatabaseConn {
/** 数据库服务器IP地址 */
private String server;
/** 数据库类型 */
private String type;
/** 访问类型(Native,ODBC,JNDI) */
private String access = "Native";
/** 数据库名称 */
private String database;
/** 连接端口 */
private String port;
/** 连接用户名 */
private String username;
/** 连接密码 */
private String password;
}
表信息封装类
此类封装了转换需要的源表、目标表的一些基础信息
/**
* @author qijian
* @description 表信息封装类
* @date 2021/8/17 - 10:21
*/
@Data
public class ExtractBean {
/**
* 源表数据库连接
*/
private DatabaseConn srcDB;
/**
* 源表表名
*/
private String[] srcTable = new String[0];
/**
* 源表交换字段类型
*/
private String[] srcFields;
/**
* 源表主键
*/
private String[] srcPk;
/**
* 目标表的数据库配置
*/
private DatabaseConn destDB;
/**
* 目标表
*/
private String destTable;
/**
* 目标表字段
*/
private String[] destFields;
/**
* 目标表主键
*/
private String[] destPk;
/**
* 数据转换
*/
private FieldTransfer[] fieldTransfers;
}
利用kettle进行转换测试
前面做好了jar包、相关辅助类的铺垫,现在正式写demo测试代码。
我们建一个测试Test类
初始化
使用kettle必须先要初始化,这里初始化并进行测试数据封装
public ExtractBean before() {
try {
//1、初始化Kettle环境
KettleEnvironment.init();
EnvUtil.environmentInit();
} catch (KettleException e) {
e.printStackTrace();
}
// 初始化数据源表和目标表数据
// 1)初始化数据源表
ExtractBean extractBean = new ExtractBean();
DatabaseConn srcDB = new DatabaseConn();
srcDB.setDatabase("test");
srcDB.setServer("192.168.10.64");
srcDB.setPort("3306");
srcDB.setUsername("root");
srcDB.setPassword("root");
srcDB.setType("MySQL");
extractBean.setSrcDB(srcDB);
extractBean
.setSrcFields(new String[] { "name", "age", "mail", "phone" });
extractBean.setSrcPk(new String[] { "id" });
extractBean.setSrcTable(new String[] { "etl_src_table" });
// 2)初始化目标表
DatabaseConn destDB = new DatabaseConn();
destDB.setDatabase("test");
destDB.setServer("192.168.10.64");
destDB.setPort("3306");
destDB.setUsername("root");
destDB.setPassword("root");
destDB.setType("MySQL");
extractBean.setDestDB(destDB);
extractBean
.setDestFields(new String[] { "name", "age", "mail", "phone" });
extractBean.setDestPk(new String[] { "id" });
extractBean.setDestTable("etl_dest_table");
return extractBean;
}
定义数据库连接
这里有两种方式:xml传参和构造传参
//2、定义数据库信息:此处有两种方法,一种是使用XML,一种构造方法传参
final String srcDBName = "srcDB";
//1)xml方式
//数据库连接信息,适用于DatabaseMeta其中 一个构造器DatabaseMeta(String xml)
// String databasesXML =
// "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
// "<connection>" +
// "<name>{0}</name>" +
// "<server>{1}</server>" +
// "<type>{2}</type>" +
// "<access>{3}</access>" +
// "<database>{4}</database>" +
// "<port>{5}</port>" +
// "<username>{6}</username>" +
// "<password>{7}</password>" +
// "<attributes>" +
// "<attribute>" +
// "<code>CUSTOM_DRIVER_CLASS</code>"+
// "<attribute>dm.jdbc.driver.DmDriver</attribute>" +
// "</attribute>"+
// "<attribute>" +
// "<code>CUSTOM_URL</code>"+
// "<attribute>jdbc:dm://localhost:5236/SYSDBA</attribute>" +
// "</attribute>"+
// "</attributes>"+
// "</connection>";
//
// String xml = MessageFormat.format(databasesXML, new Object[] {
// srcDBName,
// srcDB.getServer(),
// "Generic database",
// "Native",
// srcDB.getDatabase(),
// srcDB.getPort(),
// srcDB.getUsername(),
// srcDB.getPassword()
// });
// DatabaseMeta dbMeta = new DatabaseMeta(xml);
//2)构造方法传参
DatabaseMeta dbMeta = new DatabaseMeta(srcDBName, srcDB.getType(),
srcDB.getAccess(), srcDB.getServer(), srcDB.getDatabase(),
srcDB.getPort(), srcDB.getUsername(), srcDB.getPassword());
//其他数据库可以跳过,为了防止数据库字段查询出来乱码问题
dbMeta.setConnectSQL(MessageFormat.format("set names ''{0}'';",
new Object[] { "utf8" }));
transMeta.addDatabase(dbMeta);
生成表输入
//registry是给每个步骤生成一个标识Id用
PluginRegistry registry = PluginRegistry.getInstance();
//3、生成表输入TableInputMeta
TableInputMeta tableInputMeta = new TableInputMeta();
String tableInputPluginId = registry.getPluginId(StepPluginType.class,
tableInputMeta);
//给表输入添加一个DatabaseMeta连接数据库
tableInputMeta.setDatabaseMeta(transMeta.findDatabase(srcDBName));
设置sql查询
//4、构造查询SQL
String selectSql = "select {0} from {1}";
selectSql = MessageFormat.format(
selectSql,
new Object[] {
StringUtils.join(extractBean.getSrcFields(), ","),
extractBean.getSrcTable()[0] });
tableInputMeta.setSQL(selectSql);
// 打印查询SQL
System.out.println(selectSql);
//将TableInputMeta添加到转换中
//只有将操作添加到步骤中才可以,这样才能形成一个流程
StepMeta tableInputStepMeta = new StepMeta(tableInputPluginId,
"tableInput", (StepMetaInterface) tableInputMeta);
transMeta.addStep(tableInputStepMeta);
获取目标库信息
//5、获取目标库信息
DatabaseConn destDB = extractBean.getDestDB();
final String destDBName = "destDB";
DatabaseMeta destDbMeta = new DatabaseMeta(destDBName, destDB.getType(),
destDB.getAccess(), destDB.getServer(), destDB.getDatabase(),
destDB.getPort(), destDB.getUsername(), destDB.getPassword());
//输出到表中
TableOutputMeta tableOutputMeta = new TableOutputMeta();
tableOutputMeta.setDatabaseMeta(destDbMeta);
//设置目标表的 schema和表名
tableOutputMeta.setSchemaName(null);
tableOutputMeta.setTablename(extractBean.getDestTable());
//指定目标表数据库字段
tableOutputMeta.setSpecifyFields(true);
/**
* 在此处如果是oracle中含有大字段的有一个问题:
* ORA-24816: 在实际的 LONG 或 LOB 列之后提供了扩展的非 LONG 绑定数据
解决方法:在实际的 LONG 或 LOB 列之后提供了扩展的非 LONG 绑定数据错误,
这个错误是因为在绑定参数时把数据库中字段类型为LONG的字段放置在其他字段前设置了,
只要将类型为Lob的字段绑定参数时放在最后设置即可解决。
即设置 destFields为: long ,clob,blob这样的顺序,long类型等不能放在clob后面
*/
tableOutputMeta.setFieldStream(extractBean.getDestFields());
tableOutputMeta.setFieldDatabase(extractBean.getDestFields());
String tableOutputPluginId = registry.getPluginId(StepPluginType.class, tableOutputMeta);
StepMeta tableOutputStep = new StepMeta(tableOutputPluginId, "tableOutput" , (StepMetaInterface) tableOutputMeta);
//将步骤添加进去
transMeta.addStep(tableOutputStep);
关联input和output步骤
//6、将步骤和上一步关联起来
transMeta.addTransHop(new TransHopMeta(tableInputStepMeta, tableOutputStep));
添加监听
//7、设置执行参数,添加监听
Trans trans = new Trans(transMeta);
trans.setMonitored(true);
trans.setInitializing(true);
trans.setPreparing(true);
trans.setLogLevel(LogLevel.BASIC);
trans.setRunning(true);
trans.setSafeModeEnabled(true);
trans.addTransListener(new TransAdapter() {
@Override
public void transFinished(Trans trans) {
System.out.println("转换执行完成");
}
});
执行抽取
//8、执行抽取
try {
// trans.startThreads();
trans.execute(null);
} catch (KettleException e) {
e.printStackTrace();
}
trans.waitUntilFinished();
if (trans.getErrors() > 0) {
System.out.println("抽取数据出错.");
return;
}
System.out.println("抽取加载数据完成");
控制台输出
当你的控制台输出,是以下类似的输出数据,就说明转换成功,代码跑的没问题。之后再去目标表看下有没有数据生成