Mybatis 居然可以操作 ClickHouse?

可能是最简单的一篇 ClickHouse 入门实践教程了!

环境:springboot 2.4
整合技术:durid + 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

### 回答1: MyBatis 是一个 Java 持久层框架,而 ClickHouse 是一个面向列存储的分布式数据库,支持高并发和快速的数据插入。如果你想要使用 MyBatis 将数据插入 ClickHouse 中,可以按照以下步骤进行操作: 1. 添加 ClickHouse 的依赖库到项目中。 这可以通过在 pom.xml 文件中添加以下依赖项来完成: ```xml <dependency> <groupId>ru.yandex.clickhouse</groupId> <artifactId>clickhouse-jdbc</artifactId> <version>0.2.4</version> </dependency> ``` 2. 在 MyBatis 的配置文件中添加 ClickHouse 的数据源信息。 这可以通过在 mybatis-config.xml 文件中添加以下数据源配置来完成: ```xml <dataSource type="com.zaxxer.hikari.HikariDataSource"> <property name="driverClassName" value="ru.yandex.clickhouse.ClickHouseDriver"/> <property name="jdbcUrl" value="jdbc:clickhouse://localhost:8123/default"/> <property name="username" value="username"/> <property name="password" value="password"/> </dataSource> ``` 3. 在 Mapper 接口中添加对 ClickHouse操作方法。 这可以通过在 Mapper 接口中添加相应的 SQL 语句来完成,例如: ```java @Insert("INSERT INTO table_name (column1, column2, column3) VALUES (#{value1}, #{value2}, #{value3})") void insertIntoClickHouse(@Param("value1") String value1, @Param("value2") int value2, @Param("value3") double value3); ``` 在这个例子中,我们使用了 @Insert 注解来定义一个插入语句,其中 column1、column2 和 column3 是 ClickHouse 数据库中的列名,#{value1}、#{value2} 和 #{value3} 则是 MyBatis 中使用的参数名。 通过这些步骤,你就可以使用 MyBatis 来批量插入数据到 ClickHouse 中了。当然,这只是一个简单的例子,具体的实现还需要根据实际情况进行调整。 ### 回答2: mybatis在批量插入ClickHouse时,可以使用以下语法。 1. 首先,在mybatis的Mapper接口中定义一个方法来执行批量插入操作,例如: ``` void batchInsert(List<Map<String, Object>> data); ``` 2. 在对应的Mapper.xml文件中,编写实际执行批量插入的SQL语句,例如: ``` <insert id="batchInsert" parameterType="java.util.List"> INSERT INTO table_name (column1, column2, column3) VALUES <foreach collection="list" item="item" index="index" separator=","> (#{item.column1}, #{item.column2}, #{item.column3}) </foreach> </insert> ``` 3. 在代码中调用批量插入方法,例如: ``` List<Map<String, Object>> data = new ArrayList<>(); // 构造插入数据 Map<String, Object> row1 = new HashMap<>(); row1.put("column1", value1); row1.put("column2", value2); row1.put("column3", value3); data.add(row1); Map<String, Object> row2 = new HashMap<>(); row2.put("column1", value4); row2.put("column2", value5); row2.put("column3", value6); data.add(row2); // 调用批量插入方法 mapper.batchInsert(data); ``` 以上代码中,`table_name`为要插入的ClickHouse表的名称,`column1`、`column2`、`column3`为要插入的表的列名,`value1`、`value2`、`value3`等为具体要插入的值。 通过以上步骤,可以使用mybatis实现ClickHouse的批量插入操作。 ### 回答3: 在MyBatis中,可以使用批量插入操作来提高数据插入的效率。而在ClickHouse中,它也提供了相应的语法来支持批量插入操作。 在MyBatis中,我们可以使用foreach标签来实现批量插入。首先,我们需要定义一个List类型的参数,用于存储待插入的数据。然后,在SQL语句中使用foreach标签来循环遍历待插入的数据,并生成插入语句。 例如,假设我们有一个表名为"table_name",它包含两个字段"col1"和"col2",我们要将一组数据批量插入到该表中。我们可以按照以下步骤进行操作: 1. 在Mapper接口中定义一个批量插入的方法,方法参数为List类型的数据集合。 ```java void batchInsert(List<YourEntity> dataList); ``` 2. 在对应的Mapper XML文件中编写批量插入的SQL语句,使用foreach标签循环遍历待插入的数据。 ```xml <insert id="batchInsert" parameterType="java.util.List"> INSERT INTO table_name (col1, col2) VALUES <foreach collection="list" item="item" separator=","> (#{item.col1}, #{item.col2}) </foreach> </insert> ``` 其中,list为方法参数名称,item为循环遍历中的每个元素,#{item.col1}和#{item.col2}为每个元素中对应的属性。 3. 调用批量插入方法,将数据集合作为参数传入。 ```java yourMapper.batchInsert(dataList); ``` 通过以上步骤,我们就可以使用MyBatis的批量插入语法来实现在ClickHouse中批量插入数据。注意,在进行批量插入操作时,需要注意数据的大小限制和性能提升的平衡,以避免出现插入失败或性能下降的情况。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值