如何将Mybatis连接到ClickHouse

场景

最近在做数据分析项目,里面有这样一个业务:把匹配的数据打上标签,放到新的索引中。

数据量:累计亿级的数据

使用场景:可能会单次查询大量的数据,但不会设置复杂的条件,且这些数据不会被再次修改

原来使用的数据库:ElasticSearch

问题:上面也说了我这里打上标记后,这些数据几乎不会再修改了。ES 是一个全文检索引擎,更适用于进行大量文本检索的情况。这里与我上面的使用场景就不太匹配了。

技术选型的考虑:改用战斗民族开发的 ClickHouse,它适用于 OLAP 也就是数据分析的场景,当数据写入后,通过不同维度不断挖掘、分析,发现其中的商业价值。ClickHouse 适用于读远大于写的情况。

此外,相比ES,ClickHouse 占用的硬盘空间更小,也有利于降低运维成本。

下面是我在尝试接入 ClickHouse 时的一些实践,以及关于 ClickHouse数组类型转换问题的解决方案。

关于 ClickHouse 更详细的知识参考:https://zhuanlan.zhihu.com/p/98135840

示例代码已经上传到了 Git,目前更新第 28 节:https://github.com/laolunsi/spring-boot-examples/

Mybatis + ClickHouse

以前一直用 Mybatis 去操作 MySQL,其实 Mybatis 还可以操作 ClickHouse,这里用 Druid 进行连接管理。

maven 配置

    <dependency>      <groupId>com.alibaba</groupId>      <artifactId>druid</artifactId>      <version>1.2.5</version>    </dependency>    <dependency>      <groupId>ru.yandex.clickhouse</groupId>      <artifactId>clickhouse-jdbc</artifactId>      <version>0.2.6</version>    </dependency>    <dependency>      <groupId>org.mybatis.spring.boot</groupId>      <artifactId>mybatis-spring-boot-starter</artifactId>      <version>2.1.3</version>    </dependency>

配置文件:

spring: datasource:  type: com.alibaba.druid.pool.DruidDataSource  # 注意这里是自定义的配置,通过 JdbcParamConfig 来加载配置到 Spring 中  # 然后由 DruidConfig 来配置数据源  click:   driverClassName: ru.yandex.clickhouse.ClickHouseDriver   url: jdbc:clickhouse://127.0.0.1:8123/test # ip:port/database   userName: default   password: default # 按照自己连接的 clickhouse 数据库来   initialSize: 10   maxActive: 100   minIdle: 10   maxWait: 6000   validationQuery: SELECT 1

加载配置项的类:

@Component@ConfigurationProperties(prefix = "spring.datasource.click")public class JdbcParamConfig {  private String userName;  private String password;  private String driverClassName ;  private String url ;  private Integer initialSize ;  private Integer maxActive ;  private Integer minIdle ;  private Integer maxWait ;  private String validationQuery;  // ignore getters and setters}

配置 Druid:

@Configuration@MapperScan(basePackages = {    "com.aegis.analysis.clickhousestorage.dao"})public class DruidConfig {  @Resource  private JdbcParamConfig jdbcParamConfig ;  @Bean(name = "clickDataSource")  public DataSource dataSource() throws ClassNotFoundException {    Class classes = Class.forName("com.alibaba.druid.pool.DruidDataSource");    DruidDataSource dataSource = (DruidDataSource) DataSourceBuilder        .create()        .driverClassName(jdbcParamConfig.getDriverClassName())        .type(classes)        .url(jdbcParamConfig.getUrl())        .username(jdbcParamConfig.getUserName())        .password(jdbcParamConfig.getPassword())        .build();    dataSource.setMaxWait(jdbcParamConfig.getMaxWait());    dataSource.setValidationQuery(jdbcParamConfig.getValidationQuery());    return dataSource;  }  @Bean  public SqlSessionFactory clickHouseSqlSessionFactoryBean() throws Exception {    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();    factory.setDataSource(dataSource());    // 实体 model的 路径 比如 com.order.model    factory.setTypeAliasesPackage("com.example.clickhousedemo.model");    //添加XML目录    ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();    factory.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));    //开启驼峰命名转换    factory.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);    return factory.getObject();  }}

定义一个 UserInfo 类,建表语句如下:

CREATE TABLE test.user (   `id` Int16,   `name` String,   `score` Float32,   `score2` Float64,   `state` Int8,   `createTime` DateTime,   `ranks` Array(UInt8)   ) ENGINE = MergeTree() ORDER BY id;

实体类:

public class UserInfo {  private Integer id; // int16  private String name; // String  private Float score; // float16  private Double score2; // float32  private Boolean state; // int8  private Date createTime; // datetime  private Integer[] ranks; // Array - Array 类型需要进行类型转换  // 具体转换方法与配置参考 ClickArrayToIntHandler 类与 UserMapper.xml 中关于查询和插入时 ranks 字段的配置  // ignore getters and setters}

DAO 和 Mapper 文件就按照连接 MYSQL 时的写法一样。

这里有个需要注意的点,ClickHouse 有个 Array 类型,可以用来存数组,就像 ES 一样。问题是类型转换需要自己定义。网上一些资料仅列出了基本类型的场景,我自己实现了一个转换器,可以参考一下:

/** * Java Int 数组与 ClockHouse Array Int 转换器 * @version 1.0 * @since 2019/11/14 9:59 */public class ClickArrayToIntHandler extends BaseTypeHandler<Integer[]> {  @Override  public void setNonNullParameter(PreparedStatement preparedStatement, int i, Integer[] integers, JdbcType jdbcType) throws SQLException {    preparedStatement.setObject(i, integers);  }  @Override  public Integer[] getNullableResult(ResultSet resultSet, String s) throws SQLException {    Object obj = resultSet.getObject(s);    return parseClickHouseArrayToInt(obj);  }  @Override  public Integer[] getNullableResult(ResultSet resultSet, int i) throws SQLException {    Object obj = resultSet.getObject(i);    return parseClickHouseArrayToInt(obj);  }  @Override  public Integer[] getNullableResult(CallableStatement callableStatement, int i) throws SQLException {    Object obj = callableStatement.getObject(i);    return parseClickHouseArrayToInt(obj);  }  private Integer[] parseClickHouseArrayToInt(Object obj) {    if (obj instanceof ClickHouseArray) {      int[] res = new int[0];      try {        res = (int[]) ((ClickHouseArray) obj).getArray();      } catch (SQLException ex) {        ex.printStackTrace();      }      if (res != null && res.length > 0) {        Integer[] resI = new Integer[res.length];        for (int i = 0; i < res.length; i++) {          resI[i] = res[i];        }        return resI;      }    }    return new Integer[0];  }}

DAO.xml 也给一个示例:

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.aegis.analysis.clickhousestorage.dao.UserInfoMapper">  <resultMap id="BaseResultMap" type="com.example.clickhousedemo.model.UserInfo">    <id column="id" property="id" />    <result column="name" property="name" />    <result column="name" property="name" />    <result column="score" property="score" />    <result column="score2" property="score2" />    <result column="state" property="state" />    <result column="createTime" property="createTime" />    <!-- <result column="ranks" property="ranks" jdbcType="JAVA_OBJECT" javaType="java.lang.Object" />-->    <result column="ranks" property="ranks" typeHandler="com.example.clickhousedemo.dao.ClickArrayToIntHandler" />  </resultMap>  <sql id="Base_Column_List">    *  </sql>  <insert id="saveData" parameterType="com.aegis.analysis.clickhousestorage.model.UserInfo" >    INSERT INTO user      (id,name, score, score2, state, createTime, ranks)    VALUES    (#{id},#{name}, #{score}, #{score2}, #{state}, #{createTime}, #{ranks, jdbcType=ARRAY,    typeHandler=com.example.clickhousedemo.dao.ClickArrayToIntHandler})  </insert>  <select id="selectById" resultMap="BaseResultMap">    select    <include refid="Base_Column_List" />    from user    where id = #{id}    limit 1  </select>  <select id="selectList" resultMap="BaseResultMap" >    select    <include refid="Base_Column_List" />    from user  </select></mapper>

具体代码可以去我的 Git 仓库里查看,还有 SpringBoot 整合其他中间件技术的示例,欢迎 Star!

https://github.com/laolunsi/spring-boot-examples

以上就是如何将Mybatis连接到ClickHouse的详细内容,更多关于Mybatis连接到ClickHouse的资料请关注其它相关文章!

我们人生中最大的懒惰,就是当我们明知自己拥有作出选择的能力,

如何将Mybatis连接到ClickHouse

相关文章:

你感兴趣的文章:

标签云: