java web项目整合kettle进行数据自动导入

最近有一个需求,就是要将数据文件打包上传到服务器,上传的同时分析数据文件并将数据清洗入库。

分析:

  1. 有多个不同类型的数据文件,如果单个上传对于用户来说无疑是一件比较痛苦的事,因此需要将多个文件打包上传,记录上传文件,点击记录则可以看到该压缩包内的文件明细记录;
  2. 数据文件清洗入库,该项目采用国外的一款开源ETL工具kettle,Kettle 中文名称叫水壶,意思是把各种数据放到一个壶里,然后以一种指定的格式流出,这种理念很符合本需求;
  3. kettle清洗对数据文件格式要求较高,因此文件上传之后清洗之前需要校验压缩包内数据文件的格式,不规范的文件给出详细的错误提示。

该项目使用springboot + thymeleaf + mybatis,通过mybatis插件pagehelper插件进行分页,简单封装了Page类。
**

项目github地址:kettle-springboot

**

实现:

github上有完整的项目代码,有此需求的朋友可以关注一下,本文只介绍关键步骤。

1、spring boot整合kettle

  • POM
<!-- with pentaho-kettle -->
<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>
<dependency>
	<groupId>pentaho-kettle</groupId>
	<artifactId>kettle-dbdialog</artifactId>
	<version>${kettle-version}</version>
</dependency>
<!--kettle执行复杂脚本需要此包-->
<dependency>
	<groupId>org.codehaus.janino</groupId>
	<artifactId>janino</artifactId>
	<version>${janino-version}</version>
</dependency>

注意最后一个jar包(janino),执行简单job和translation时用不上,但是复杂作业没有此包会报错。

  • application.yml
    将kettle的资源库、模板目录、日志级别等的配置放在springboot的默认配置文件,当然也可以将其拆出来,或者直接写在代码里(不建议)。
# kettle相关配置
kettle:
  filerepository:
    path: D:/ch/Kettle-repo/test
    id: kettleRepo
    name: kettleRepo
    description: 恩施kettle文件资源库
  templates: #数据模板文件路径
    path: D:/ch/Kettle-repo/templates
  log:
    level: basic  # 对应nothing  error  minimal  basic  detailed  debug  rowlevel
    path: D:/hx/log/kettle_log

说明:kettle.filerepository.path为kettle本地文件资源库的路径,后面的java代码通过该路径来读取资源库,取得该文件资源库下的作业和转换(有兴趣的朋友可以使用数据库资源库)。

  • java代码,对java代码调用kettle工具执行kettle作业或转换进行了封装,仅列出关键部分,可做参考,本项目github上有完整代码。
 /**
  * 配置kettle文件库资源库环境
  **/
 public KettleFileRepository fileRepositoryCon() throws KettleException {
     String msg;
     //初始化
     /*EnvUtil.environmentInit();
     KettleEnvironment.init();*/

     //资源库元对象
     KettleFileRepositoryMeta fileRepositoryMeta = new KettleFileRepositoryMeta(this.KETTLE_REPO_ID, this.KETTLE_REPO_NAME, this.KETTLE_REPO_DESC, this.KETTLE_REPO_PATH);
     // 文件形式的资源库
     KettleFileRepository repo = new KettleFileRepository();
     repo.init(fileRepositoryMeta);
     //连接到资源库
     repo.connect("", "");//默认的连接资源库的用户名和密码

     if (repo.isConnected()) {
         msg = "kettle文件库资源库【" + KETTLE_REPO_PATH + "】连接成功";
         logger.info(msg);
         return repo;
     } else {
         msg = "kettle文件库资源库【" + KETTLE_REPO_PATH + "】连接失败";
         logger.error(msg);
         throw new KettleDcException(msg);
     }
 }
 public void callTrans(String transPath, String transName, Map<String,String> namedParams, String[] clParams) throws Exception {
     String msg;
     KettleFileRepository repo = this.fileRepositoryCon();
     TransMeta transMeta = this.loadTrans(repo, transPath, transName);
     //转换
     Trans trans = new Trans(transMeta);
     //设置命名参数
     if(null != namedParams) {
         for(Iterator<Map.Entry<String, String>> it = namedParams.entrySet().iterator(); it.hasNext();){
             Map.Entry<String, String> entry = it.next();
             trans.setParameterValue(entry.getKey(), entry.getValue());
         }
     }
     trans.setLogLevel(this.getLogerLevel(KETTLE_LOG_LEVEL));
     //执行
     trans.execute(clParams);
     trans.waitUntilFinished();
     //记录日志
     String logChannelId = trans.getLogChannelId();
     LoggingBuffer appender = KettleLogStore.getAppender();
     String logText = appender.getBuffer(logChannelId, true).toString();
     logger.info(logText);
     //抛出异常
     if (trans.getErrors() > 0) {
         msg = "There are errors during transformation exception!(转换过程中发生异常)";
         logger.error(msg);
         throw new KettleDcException(msg);
     }
 }
 public boolean callJob(String jobPath, String jobName, Map<String,String> variables, String[] clParams) throws Exception {
     String msg;
     KettleFileRepository repo = this.fileRepositoryCon();
     JobMeta jobMeta = this.loadJob(repo, jobPath, jobName);
     Job job = new Job(repo, jobMeta);
     //向Job 脚本传递参数,脚本中获取参数值:${参数名}
     if(null != variables) {
         for(Iterator<Map.Entry<String, String>> it = variables.entrySet().iterator(); it.hasNext();){
             Map.Entry<String, String> entry = it.next();
             job.setVariable(entry.getKey(), entry.getValue());
         }
     }
     //设置日志级别
     job.setLogLevel(this.getLogerLevel(KETTLE_LOG_LEVEL));
     job.setArguments(clParams);
     job.start();
     job.waitUntilFinished();
     //记录日志
     String logChannelId = job.getLogChannelId();
     LoggingBuffer appender = KettleLogStore.getAppender();
     String logText = appender.getBuffer(logChannelId, true).toString();
     logger.info(logText);
     if (job.getErrors() > 0) {
         msg = "There are errors during job exception!(执行job发生异常)";
         logger.error(msg);
         throw new KettleDcException(msg);
     }
     return true;
 }
 /**
  * 加载转换
  */
 private TransMeta loadTrans(KettleFileRepository repo, String transPath, String transName) throws Exception{
     String msg;
     RepositoryDirectoryInterface dir = repo.findDirectory(transPath);//根据指定的字符串路径找到目录
     if(null == dir){
         msg = "kettle资源库转换路径不存在【"+repo.getRepositoryMeta().getBaseDirectory()+transPath+"】!";
         throw new KettleDcException(msg);
     }
     TransMeta transMeta = repo.loadTransformation(repo.getTransformationID(transName, dir), null);
     if(null == transMeta){
         msg = "kettle资源库【"+dir.getPath()+"】不存在该转换【"+transName+"】!";
         throw new KettleDcException(msg);
     }
     return transMeta;
 }

 /**
  * 加载job
  */
 private JobMeta loadJob(KettleFileRepository repo, String jobPath, String jobName) throws Exception{
     String msg;
     RepositoryDirectoryInterface dir = repo.findDirectory(jobPath);//根据指定的字符串路径找到目录
     if(null == dir){
         msg = "kettle资源库Job路径不存在【"+repo.getRepositoryMeta().getBaseDirectory()+jobPath+"】!";
         throw new KettleDcException(msg);
     }
     JobMeta jobMeta = repo.loadJob(repo.getJobId(jobName, dir), null);
     if(null == jobMeta){
         msg = "kettle资源库【"+dir.getPath()+"】不存在该转换【"+jobName+"】!";
         throw new KettleDcException(msg);
     }
     return jobMeta;
 }

调用

Map<String,String> variables = new HashMap<>();
//传入文件解压出来后的路径
variables.put("param",FileUtil.getBasePath(t_relative_path));
boolean re = kettleManager.callJob(t_job_path,t_job_name,variables, null);

2、spring boot整合mybatis

  • POM
 <dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>druid</artifactId>
	<version>${druid-version}</version>
</dependency>
  <dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
          <version>${mysql-connector-java-version}</version>
	<!--<scope>runtime</scope>-->
</dependency>
<dependency>
	<groupId>org.mybatis.spring.boot</groupId>
	<artifactId>mybatis-spring-boot-starter</artifactId>
	<version>${mybatis-spring-boot-starter-version}</version>
</dependency>

数据库连接池使用的druid,相关配置文件为druid.properties

  • mybatis配置
    mybatis配置文件为resources/mybatis/mybatis-config.xml,mapper.xml放在resources/mybatis/mapper/目录下。如需在控制台打印sql语句以便调试,在mybatis-config.xml里加入
    <setting name="logImpl" value="STDOUT_LOGGING"/>即可

springboot的mybatis配置通过注解@Configuration完成,如下:

@Configuration
@PropertySource(value = "classpath:druid.properties")
public class SpringConfig {

    @Bean(name = "dataSource")
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druidDataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        return druidDataSource;
    }
    /*==================MyBatis配置====================*/
    @Bean(name = "sqlSessionFactory")
    @Primary
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
        //此句必须要加上,不然打包后运行jar包时无法识别mybatis别名
        VFS.addImplClass(SpringBootVFS.class);
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        // 设置mybatis的主配置文件
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        Resource mybatisConfigXml = resolver.getResource("classpath:mybatis/mybatis-config.xml");
        bean.setConfigLocation(mybatisConfigXml);
        //设置mybatis扫描的mapper.xml文件的路径(非常重要,否则找不到mapper.xml文件)
        Resource[] mapperResources = resolver.getResources("classpath:mybatis/mapper/*.xml");
        bean.setMapperLocations(mapperResources);
        // 设置别名包,便于在mapper.xml文件中ParemeType和resultType不要写完整的包名
        bean.setTypeAliasesPackage("com.ch.dataclean.model");

        return bean.getObject();
    }

    @Bean(name = "sqlSessionTemplate")
    @Primary
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

   //初始化kettle环境
    @Bean(name = "KettleEnvironmentInit")
    public StartInit startInit(){
        return new StartInit();
    }
}

特别注意:配置bean SqlSessionFactory时一定要加上这句[VFS.addImplClass(SpringBootVFS.class);] ,不加的话在idea里运行正常,可是打成jar运行时会识别不了mybatis的别名,即使使用了注解@Alias("")。

  • DAO
public interface DAO {
    /**
     * 保存对象
     */
    public Object save(String str, Object obj) throws Exception;
    /**
     * 修改对象
     */
    public Object update(String str, Object obj) throws Exception;
    /**
     * 删除对象
     */
    public Object delete(String str, Object obj) throws Exception;
    /**
     * 查找对象
     */
    public Object findForObject(String str, Object obj) throws Exception;
    /**
     * 查找对象
     */
    public Object findForList(String str, Object obj) throws Exception;
    /**
     * 查找对象封装成Map
     */
    public Object findForMap(String sql, Object obj, String key, String value) throws Exception;
}
@Repository
public class DaoSupport implements DAO {
    @Resource(name = "sqlSessionTemplate")
    private SqlSessionTemplate sqlSessionTemplate;
    /**
     * 保存对象
     */
    public Object save(String str, Object obj) throws Exception {
        return sqlSessionTemplate.insert(str, obj);
    }
    /**
     * 批量更新
     */
    public Object batchSave(String str, List objs )throws Exception{
        return sqlSessionTemplate.insert(str, objs);
    }
    /**
     * 修改对象
     */
    public Object update(String str, Object obj) throws Exception {
        return sqlSessionTemplate.update(str, obj);
    }
    /**
     * 批量更新
     */
    public void batchUpdate(String str, List objs )throws Exception{
        SqlSessionFactory sqlSessionFactory = sqlSessionTemplate.getSqlSessionFactory();
        //批量执行器
        SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH,false);
        try{
            if(objs!=null){
                for(int i=0,size=objs.size();i<size;i++){
                    sqlSession.update(str, objs.get(i));
                }
                sqlSession.flushStatements();
                sqlSession.commit();
                sqlSession.clearCache();
            }
        }finally{
            sqlSession.close();
        }
    }
    /**
     * 批量更新
     */
    public Object batchDelete(String str, List objs )throws Exception{
        return sqlSessionTemplate.delete(str, objs);
    }
    /**
     * 删除对象
     */
    public Object delete(String str, Object obj) throws Exception {
        return sqlSessionTemplate.delete(str, obj);
    }
    /**
     * 查找对象
     */
    public Object findForObject(String str, Object obj) throws Exception {
        return sqlSessionTemplate.selectOne(str, obj);
    }
    /**
     * 查找对象
     */
    public Object findForList(String str, Object obj) throws Exception {
        return sqlSessionTemplate.selectList(str, obj);
    }
    public Object findForMap(String str, Object obj, String key, String value) throws Exception {
        return sqlSessionTemplate.selectMap(str, obj, key);
    }
    public SqlSessionTemplate getSqlSessionTemplate() {
        return sqlSessionTemplate;
    }
    public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
        this.sqlSessionTemplate = sqlSessionTemplate;
    }
}

3、pagehelper插件

  • POM
<dependency>
	<groupId>com.github.pagehelper</groupId>
	<artifactId>pagehelper</artifactId>
	<version>5.1.8</version>
</dependency>

本文使用的是pagehelper5.1.8,使用5.0以后的版本即可,5.0以前的没有如下方法,既不能通过此方法设置排序参数。

public static <E> Page<E> startPage(int pageNum, int pageSize, String orderBy)
  • 配置
    在mybatis的配置文件mybatis-config.xml里configuration下加入:
<plugins>
   <!-- com.github.pagehelper为PageHelper类所在包名 -->
   <plugin interceptor="com.github.pagehelper.PageInterceptor">
       <!-- 4.0.0以后版本可以不设置该参数 -->
       <!-- 设置数据库类型 Oracle,Mysql,MariaDB,SQLite,Hsqldb,PostgreSQL六种数据库-->
       <!--<property name="helperDialect" value="mysql"/>-->

       <!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 -->
       <!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 -->
       <!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 -->
       <property name="reasonable" value="true"/>
   </plugin>
</plugins>
  • Page.java
//此处省略setter/getter
/**
 * Description: pagehelper分页实体类
 * Created by Aaron on 2018/11/21
 */
public class Page<T> {
    private int pageNum = 1;
    private int pageSize = 10;
    private int startRow;
    private int endRow;
    private long total;
    private int pages;
    //排序
    private String orderBy;
    private List<T> rows;
    /**
     * 分页查询
     */
    public Page<T> queryForPage(SqlSessionTemplate sqlSessionTemplate, String sqlMappingStr, Map param, Page page){

        if(null != this.orderBy && "" != this.orderBy.trim()){
            PageHelper.startPage(page.getPageNum(),page.getPageSize(),this.orderBy);
        }else {
            PageHelper.startPage(page.getPageNum(),page.getPageSize());
        }
        List<T> list = sqlSessionTemplate.selectList(sqlMappingStr, param);
        PageInfo pageInfo = new PageInfo(list);
        page.setPageNum(pageInfo.getPageNum());
        page.setPageSize(pageInfo.getPageSize());
        page.setRows(list);
        page.setTotal(pageInfo.getTotal());
        page.setPages(pageInfo.getPages());
        page.setStartRow(pageInfo.getEndRow());
        page.setEndRow(pageInfo.getEndRow());
        return page;
    }
}

结语:此项目也可作为springboot入门,想学习springboot的朋友可以借鉴,里面有本人对于springboot整合其他框架摸索出来的一些思想。以前都是自己写的分页,缺点是分页查询的时候需要另外写一个sql查询总数,关于pagehelper,目前的感受是真的方便,性能方面如何暂且不知,这都是后话了,先上线后迭代,哈哈。就写这么多吧,熬夜伤身,此刻思路已经不清晰了。该项目源码见 kettle-springboot,比较简单,也希望对你们有用。晚安各位。

  • 5
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值