Bootstrap

ETL数据交换入门kettle

关于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("抽取加载数据完成");
控制台输出

当你的控制台输出,是以下类似的输出数据,就说明转换成功,代码跑的没问题。之后再去目标表看下有没有数据生成

在这里插入图片描述

;