如何将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 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 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 resultMap='BaseResultMap'> select <include refid='Base_Column_List' /> from user where id = #{id} limit 1 </select> <select 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的资料请关注好吧啦网其它相关文章!
相关文章: