Java使用mybatis通过JDBC读取Clickhouse中Array类型字段的值
1. 前言:
Clickhouse数据库支持定义字段类型为Array,Array中可以定义基本类型为String,int等等,那么我们如何读取它呢,特别是我们在使用Java读取数据时,有写程序里面既有Mysql数据源,又有Clickhouse数据源,也有可能还有Redis,MangoDB等等,这种情况下,可能使用的连接串都是类似的,都是通过JDBC来连接这些数据源的。比如我们数据源连接配置如下:

2.Clickhouse中表数据结构定义:
假设我们在clickhouse中有一张表,定义表结构如下(仅仅举例说明Array类型数据如何读取,请不要深究逻辑):
CREATE TABLE student_scores(
id Int32,
cursor_name Array(String), -- 字符串数组,用来存储科目,如 ['a', 'b', 'c']
cursor_scores Array(Float64), -- 浮点数数组,用来存储成绩,如 [95.5, 88.0, 91.2]
cursor_ranking(UInt32) -- 整数数组,用来代表单科分数排名,如 [1, 2, 3]
) ENGINE = MergeTree()
ORDER BY id;
3.Java代码中实体定义:
我们在Java代码中就会有一个对应的实体类 StudentScores:
public class StudentScores {
private Integer id;
private ArrayList<String> courseName;
private ArrayList<Float> courseScore;
private ArrayList<Integer> courseRanking;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public ArrayList<String> getCourseName() {
return courseName;
}
public void setCourseName(ArrayList<String> courseName) {
this.courseName = courseName;
}
public ArrayList<Float> getCourseScore() {
return courseScore;
}
public void setCourseScore(ArrayList<Float> courseScore) {
this.courseScore = courseScore;
}
public ArrayList<Integer> getCourseRanking() {
return courseRanking;
}
public void setCourseRanking(ArrayList<Integer> courseRanking) {
this.courseRanking = courseRanking;
}
}
4. Mybatis中mapper.xml配置:
我们如何用mybatis像读取这几个array字段呢?我们定义mapper.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.clickhouse.mapper.StudentScoresMapper">
<resultMap id="StudentScoresMap" type="com.demo.entity.StudentScores">
<id property="id" column="id"/>
<result property="courseName" column="cursor_name" jdbcType="ARRAY" typeHandler="com.clickhouse.handler.ClickHouseArrayStringHandler"/>
<result property="courseScore" column="cursor_scores" jdbcType="ARRAY" typeHandler="com.clickhouse.handler.ClickHouseArrayFloatHandler"/>
<result property="courseRanking" column="cursor_ranking" jdbcType="ARRAY" typeHandler="com.clickhouse.handler.ClickHouseArrayIntegerHandler"/>
</resultMap>
<select id="getUserTagsByUserId" resultMap="AdsUserTagMap">
select id, cursor_name, language_list, cursor_scores,cursor_ranking from student_scores where id=#{id}
</select>
</mapper>
这时候细心的朋友就会发现,我们在定义resultMap的时候,针对数据类型为Array的字段,我们在字段mapping时除了定义了jdbcType,还指定了typeHandler,为什么呢?应为Array不是基础数据类型,它可以是Array<String>也可以是Array<Integer>说白了就是一个容器,你指定哪种数据类型,它就往里面装指定的数据类型。如果你不告诉它里面到底是什么类型,那么对不起,不认识,只能报错给你看了
5.自定义typeHandler来解析clickhouse数据库定义表中的Array字段:
那么这个typeHandler是什么,为什么需要它?其实很简单,我们默认的Handler里面好像没有能处理jdbcType为Array类型的而且这个Array里面还装的不一样的数据。所以需要我们自己定义,其实也不难,实现以下对应的接口就可以了。话不多说,直接开干,上菜(上代码),我们就贴一个Array<String>来看看,其它的其实大同小异,只要在parse方法把object对象转换为对应的数据类型就可以了:
public class ClickHouseArrayStringHandler extends BaseTypeHandler<List<String>> {
@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, List<String> strings, JdbcType jdbcType) throws SQLException {
preparedStatement.setObject(i, strings.toArray(new String[0]));
}
@Override
public List<String> getNullableResult(ResultSet resultSet, String s) throws SQLException {
return parse(resultSet.getObject(s));
}
@Override
public List<String> getNullableResult(ResultSet resultSet, int i) throws SQLException {
return parse(resultSet.getObject(i));
}
@Override
public List<String> getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
return parse(callableStatement.getObject(i));
}
private List<String> parse(Object obj) {
if (obj == null) return null;
if (obj instanceof Object[]) {
return Stream.of((Object[]) obj).map(String::valueOf).collect(Collectors.toList());
}
if(obj instanceof ClickHouseArray){
try {
Object real = ((ClickHouseArray) obj).getArray();
List<String> out = new ArrayList<>();
for (int i = 0; i < Array.getLength(real); i++) {
out.add(Array.get(real, i).toString());
}
return out;
}catch (SQLException e){
log.error("ClickHouseArrayStringHandler parse ClickHouseArray error", e);
}
}
String s = obj.toString().trim();
if (s.startsWith("[") && s.endsWith("]")) s = s.substring(1, s.length() - 1);
if (s.isEmpty()) return new ArrayList<>();
return Arrays.stream(s.split(","))
.map(String::trim)
.map(x -> x.replaceAll("^'|'$", "")) // 去除可能的引号
.collect(Collectors.toList());
}
}
6. 在程序中配置注册handler:
要想使用自定义的typeHandler,还得在application配置文件中注册自定义的handler(只需要填写包名即可,不需要写具体的handler的类,这样可以扫描整个包下面的handler),大概的代码如下:
click-house:
mybatis-plus:
mapper-locations: classpath*:/mapper/clickhouse/*.xml
type-handlers-package: com.clickhouse.handler
7. 测试结果:
至此,读取clickhouse里面的Array字段的代码就基本完成了,我们随意写个controller,然后访问一下,可以得到结果如下:
{
"id": 1,
"cursorName": ["语文", "英语", "数学"],
"cursorScore": [92.5,98,86.5],
"cursorRanking": [2,4,11]
}
更多推荐


所有评论(0)