場景
最近在做數據分析項目,里面有這樣一個業務:把匹配的數據打上標簽,放到新的索引中。
數據量:累計億級的數據
使用場景:可能會單次查詢大量的數據,但不會設置復雜的條件,且這些數據不會被再次修改
原來使用的數據庫: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 配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
< 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 > |
配置文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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 |
加載配置項的類:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
@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 類,建表語句如下:
1
2
3
4
5
6
7
8
9
|
CREATE TABLE test. user ( `id` Int16, ` name ` String, `score` Float32, `score2` Float64, `state` Int8, `createTime` DateTime, `ranks` Array(UInt8) ) ENGINE = MergeTree() ORDER BY id; |
實體類:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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 一樣。問題是類型轉換需要自己定義。網上一些資料僅列出了基本類型的場景,我自己實現了一個轉換器,可以參考一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
/** * 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 也給一個示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
<? 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的資料請關注服務器之家其它相關文章!
原文鏈接:http://www.eknown.cn/index.php/spring-boot/clickhouse.html