老张
资深Java开发工程师
ClickHouse作为一款高性能的列式数据库,支持Array类型字段,但在Java中通过MyBatis读取这类字段需要特殊处理。本文将详细介绍如何实现这一功能。
ClickHouse的Array类型字段在Java中读取时面临以下挑战:
我们的解决方案主要包括以下步骤:
CREATE TABLE student_scores(
id Int32,
cursor_name Array(String), -- 字符串数组,存储科目名称
cursor_scores Array(Float64), -- 浮点数数组,存储科目成绩
cursor_ranking Array(UInt32) -- 整数数组,存储科目排名
) ENGINE = MergeTree()
ORDER BY id; public class StudentScores {
private Integer id;
private ArrayList<String> courseName;
private ArrayList<Float> courseScore;
private ArrayList<Integer> courseRanking;
// Getter和Setter方法
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;
}
} <?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="getStudentScoresById" resultMap="StudentScoresMap">
SELECT id, cursor_name, cursor_scores, cursor_ranking
FROM student_scores
WHERE id=#{id}
</select>
</mapper> 以String数组处理为例:
public class ClickHouseArrayStringHandler extends BaseTypeHandler<List<String>> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
List<String> parameter, JdbcType jdbcType) throws SQLException {
ps.setObject(i, parameter.toArray(new String[0]));
}
@Override
public List<String> getNullableResult(ResultSet rs, String columnName)
throws SQLException {
return parse(rs.getObject(columnName));
}
@Override
public List<String> getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
return parse(rs.getObject(columnIndex));
}
@Override
public List<String> getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
return parse(cs.getObject(columnIndex));
}
private List<String> parse(Object obj) {
if (obj == null) return null;
// 处理ClickHouseArray类型
if (obj instanceof ClickHouseArray) {
try {
Object array = ((ClickHouseArray) obj).getArray();
List<String> result = new ArrayList<>();
for (int i = 0; i < Array.getLength(array); i++) {
result.add(Array.get(array, i).toString());
}
return result;
} catch (SQLException e) {
throw new RuntimeException("解析ClickHouseArray失败", e);
}
}
// 处理普通数组
if (obj instanceof Object[]) {
return Arrays.stream((Object[]) obj)
.map(String::valueOf)
.collect(Collectors.toList());
}
// 处理字符串形式
String str = obj.toString().trim();
if (str.startsWith("[") && str.endsWith("]")) {
str = str.substring(1, str.length() - 1);
}
if (str.isEmpty()) return new ArrayList<>();
return Arrays.stream(str.split(","))
.map(String::trim)
.map(s -> s.replaceAll("^'|'$", ""))
.collect(Collectors.toList());
}
} 在application.yml中配置:
clickhouse:
mybatis:
mapper-locations: classpath*:/mapper/clickhouse/*.xml
type-handlers-package: com.clickhouse.handler 通过上述配置,我们可以正确读取ClickHouse中的Array类型字段:
{
"id": 1,
"courseName": ["语文", "数学", "英语"],
"courseScore": [92.5, 88.0, 95.5],
"courseRanking": [2, 5, 1]
} 对于大量数据查询,建议使用批量查询接口:
public interface StudentScoresMapper {
List<StudentScores> getStudentScoresByIds(@Param("ids") List<Integer> ids);
} TypeHandler可以设计为无状态,建议在Spring中配置为单例:
@Configuration
public class MyBatisConfig {
@Bean
public ClickHouseArrayStringHandler clickHouseArrayStringHandler() {
return new ClickHouseArrayStringHandler();
}
} 配置ClickHouse连接池提高性能:
spring:
datasource:
clickhouse:
url: jdbc:clickhouse://localhost:8123/default
username: default
password: ""
driver-class-name: ru.yandex.clickhouse.ClickHouseDriver
hikari:
maximum-pool-size: 10
minimum-idle: 5
connection-timeout: 30000 确保TypeHandler与字段类型匹配:
在TypeHandler中完善null值处理逻辑:
private List<String> parse(Object obj) {
if (obj == null) return Collections.emptyList();
// 其他处理逻辑
} 在多数据源环境下,需要为ClickHouse单独配置MyBatis:
@Configuration
@MapperScan(basePackages = "com.clickhouse.mapper",
sqlSessionFactoryRef = "clickHouseSqlSessionFactory")
public class ClickHouseDataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.clickhouse")
public DataSource clickHouseDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public SqlSessionFactory clickHouseSqlSessionFactory(
@Qualifier("clickHouseDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setTypeHandlersPackage("com.clickhouse.handler");
return sessionFactory.getObject();
}
} 本文详细介绍了通过MyBatis读取ClickHouse Array类型字段的完整方案:
这套方案同样适用于其他支持Array类型的数据库,如PostgreSQL等,只需调整TypeHandler的具体实现即可。