db-clickhouse 是 EasyFK 框架中面向 ClickHouse 列式数据库的数据访问组件。该模块基于 Spring Boot 自动配置和 HikariCP 高性能连接池,提供注解驱动的实体映射、通用 CRUD 操作、灵活的条件查询构建以及原生 SQL 执行能力,适用于日志分析、时序数据存储、实时报表统计等大数据量场景。
<dependency>
<groupId>com.mcst</groupId>
<artifactId>db-clickhouse</artifactId>
</dependency>dependencies {
implementation 'com.mcst:db-clickhouse'
}> 版本号由框架统一 BOM 管理,无需手动指定。
该模块会自动传递引入以下依赖:
所有配置项统一在 easyfk.config.db.clickhouse 前缀下。
| `url` | String | — | JDBC 连接地址(**必填**,配置后模块自动生效) |
|---|---|---|---|
| `password` | String | — | 连接密码 |
| `database` | String | — | 默认数据库名称 |
| `minPoolSize` | Integer | `5` | 连接池最小空闲连接数 |
| `maxPoolSize` | Integer | `20` | 连接池最大连接数 |
| `connectionTimeout` | Integer | `30000` | 连接超时时间(毫秒) |
| `socketTimeout` | Integer | `300000` | Socket 超时时间(毫秒) |
easyfk:
config:
db:
clickhouse:
url: jdbc:clickhouse://localhost:8123/default
username: default
password: your_password
database: default
minPoolSize: 5
maxPoolSize: 20
connectionTimeout: 30000
socketTimeout: 300000模块内置 HikariCP 连接池,已预设以下优化参数:
| 连接池名称 | `ClickHouseHikariPool` | 便于监控和日志区分 |
|---|---|---|
| 连接最大生命周期 | 30 分钟 | 防止连接长期持有导致资源泄漏 |
| 空闲连接超时 | 10 分钟 | 及时回收空闲连接 |
标记实体类对应的 ClickHouse 表名,作用于类级别。
| `name` | String | 是 | 表名 |
|---|
标记实体类字段对应的 ClickHouse 列名及属性,作用于字段级别。
| `name` | String | `""` | 列名(为空则自动将驼峰字段名转为下划线格式) |
|---|---|---|---|
| `timestamp` | boolean | `false` | 是否为时间戳字段(用于时间范围查询) |
| `partitionKey` | boolean | `false` | 是否为分区键 |
| `ignore` | boolean | `false` | 是否忽略该字段(不参与数据库操作) |
@Data
@ClickHouseTable(name = "user_event_log")
public class UserEventLog {
@ClickHouseColumn(name = "event_id", primaryKey = true)
private String eventId;
@ClickHouseColumn(name = "user_id")
private Long userId;
@ClickHouseColumn(name = "event_type")
private String eventType;
@ClickHouseColumn(name = "event_data")
private String eventData;
@ClickHouseColumn(name = "create_time", timestamp = true)
private LocalDateTime createTime;
@ClickHouseColumn(ignore = true)
private String tempField;
}> 未添加 @ClickHouseColumn 注解的字段,默认自动将驼峰命名转换为下划线命名参与数据库操作。标记 ignore = true 的字段将被排除。
继承 BaseClickHouseRepositoryImpl,即可获得全部基础 CRUD 能力:
@Repository
public class UserEventLogRepository extends BaseClickHouseRepositoryImpl<UserEventLog> {
// 可在此添加自定义查询方法
}@Service
public class EventService {
@Resource
private UserEventLogRepository eventLogRepository;
}| `insert(entity)` | 实体对象 | 单条数据插入 |
|---|---|---|
| `insertBatch(entities, batchSize)` | 实体列表, 批次大小 | 批量插入(指定批次大小) |
| `queryList(condition)` | 查询条件 | `List<T>` | 条件查询列表 |
|---|---|---|---|
| `count(condition)` | 查询条件 | `long` | 查询总数 |
| `aggregate(condition)` | 查询条件 | `List<Map>` | 聚合查询(需设置聚合函数和分组字段) |
| `delete(condition)` | 查询条件 | 条件删除(ALTER TABLE DELETE,**必须包含 WHERE 条件**) |
|---|
| `executeQuery(sql)` | SQL 语句 | `List<T>` | 原生 SQL 查询,结果映射为实体 |
|---|---|---|---|
| `executeUpdate(sql)` | SQL 语句 | `int` | 原生 SQL 执行(INSERT/UPDATE/DELETE) |
| `tableExists()` | `boolean` | 检查实体对应的表是否存在(查询 system.tables) |
|---|
ClickHouseSearchCondition 支持链式调用(@Accessors(chain = true))。
| `start` | String | — | 开始时间(支持绝对时间和相对时间) |
|---|---|---|---|
| `timeField` | String | `create_time` | 时间字段名 |
| `equalsConditions` | `Map<String, Object>` | 等于条件 |
|---|---|---|
| `likeConditions` | `Map<String, String>` | 模糊查询条件(LIKE) |
| `inConditions` | `Map<String, List<?>>` | IN 查询条件 |
| `gtConditions` | `Map<String, Object>` | 大于条件 |
| `gteConditions` | `Map<String, Object>` | 大于等于条件 |
| `ltConditions` | `Map<String, Object>` | 小于条件 |
| `lteConditions` | `Map<String, Object>` | 小于等于条件 |
| `betweenConditions` | `Map<String, Object[]>` | BETWEEN 条件(value 为 `[min, max]` 数组) |
| `aggregationFunctions` | `String[]` | 聚合函数,如 `count(*)`, `sum(amount)`, `avg(price)` |
|---|---|---|
| `selectFields` | `String[]` | 自定义查询字段 |
| `descFields` | `String[]` | — | 降序排序字段 |
|---|---|---|---|
| `defaultDesc` | Boolean | `true` | 默认按时间字段降序排列 |
| `pageSearch` | `PageSearch` | 分页查询(包含 page 和 limit) |
|---|
| `customWhere` | String | — | 自定义 WHERE 条件(直接拼接到 SQL) |
|---|---|---|---|
| `useFinal` | Boolean | `false` | 是否使用 FINAL 关键字(用于 ReplacingMergeTree 等表引擎) |
时间字段支持相对时间表达式,格式为 [+-]数字单位:
| `-1d` | 1 天前 | `now() - INTERVAL 1 DAY` |
|---|---|---|
| `-30m` | 30 分钟前 | `now() - INTERVAL 30 MINUTE` |
| `+1h` | 1 小时后 | `now() + INTERVAL 1 HOUR` |
| `-1w` | 1 周前 | `now() - INTERVAL 1 WEEK` |
| `-1y` | 1 年前 | `now() - INTERVAL 1 YEAR` |
| `now` / `now()` | 当前时间 | `now()` |
支持的时间单位:s(秒)、m(分)、h(时)、d(天)、w(周)、y(年)。
ClickHouseSearchConditionUtil.createSearchCondition() 可将框架通用的 SearchRequest 自动转换为 ClickHouseSearchCondition,自动处理:
@PostMapping("/search")
public List<UserEventLog> search(@RequestBody SearchRequest<UserEventParam> request) {
ClickHouseSearchCondition condition =
ClickHouseSearchConditionUtil.createSearchCondition(request, UserEventLog.class);
return eventLogRepository.queryList(condition);
}UserEventLog log = new UserEventLog();
log.setEventId("evt_001");
log.setUserId(10086L);
log.setEventType("LOGIN");
log.setCreateTime(LocalDateTime.now());
eventLogRepository.insert(log);List<UserEventLog> logs = new ArrayList<>();
for (int i = 0; i < 5000; i++) {
UserEventLog log = new UserEventLog();
log.setEventId("evt_" + i);
log.setUserId((long) (i % 100));
log.setEventType("PAGE_VIEW");
log.setCreateTime(LocalDateTime.now());
logs.add(log);
}
// 默认每批 1000 条
eventLogRepository.insertBatch(logs);
// 自定义批次大小
eventLogRepository.insertBatch(logs, 2000);ClickHouseSearchCondition condition = new ClickHouseSearchCondition()
.setEqualsConditions(Map.of("event_type", "LOGIN"))
.setStart("-7d")
.setStop("now")
.setDescFields(new String[]{"create_time"})
.setTop(100);
List<UserEventLog> logs = eventLogRepository.queryList(condition);ClickHouseSearchCondition condition = new ClickHouseSearchCondition()
.setEqualsConditions(Map.of("user_id", 10086L))
.setPageSearch(new PageSearch(1, 20));
List<UserEventLog> list = eventLogRepository.queryList(condition);
long total = eventLogRepository.count(condition);ClickHouseSearchCondition condition = new ClickHouseSearchCondition()
.setAggregationFunctions(new String[]{"event_type", "count(*) as cnt"})
.setGroupByFields(new String[]{"event_type"})
.setStart("-30d")
.setDescFields(new String[]{"cnt"})
.setTop(10);
List<Map<String, Object>> result = eventLogRepository.aggregate(condition);
// 结果示例:[{event_type=LOGIN, cnt=12345}, {event_type=PAGE_VIEW, cnt=98765}]ClickHouseSearchCondition condition = new ClickHouseSearchCondition()
.setBetweenConditions(Map.of("user_id", new Object[]{100L, 200L}));
List<UserEventLog> logs = eventLogRepository.queryList(condition);ClickHouseSearchCondition condition = new ClickHouseSearchCondition()
.setInConditions(Map.of("event_type", List.of("LOGIN", "LOGOUT", "REGISTER")));
List<UserEventLog> logs = eventLogRepository.queryList(condition);// 按时间范围删除
eventLogRepository.deleteByTimeRange("2025-01-01 00:00:00", "2025-06-30 23:59:59");
// 按条件删除
ClickHouseSearchCondition condition = new ClickHouseSearchCondition()
.setEqualsConditions(Map.of("event_type", "TEST"));
eventLogRepository.delete(condition);UserEventLog updateEntity = new UserEventLog();
updateEntity.setEventType("UPDATED_TYPE");
ClickHouseSearchCondition condition = new ClickHouseSearchCondition()
.setEqualsConditions(Map.of("event_id", "evt_001"));
eventLogRepository.update(updateEntity, condition);对于 ReplacingMergeTree 引擎的表,查询时可启用 FINAL 以获取去重后的最新数据:
ClickHouseSearchCondition condition = new ClickHouseSearchCondition()
.setUseFinal(true)
.setEqualsConditions(Map.of("user_id", 10086L));
List<UserEventLog> logs = eventLogRepository.queryList(condition);
// 生成 SQL: SELECT * FROM user_event_log FINAL WHERE user_id = 10086 ORDER BY create_time DESC// 返回实体列表
List<UserEventLog> logs = eventLogRepository.executeQuery(
"SELECT * FROM user_event_log WHERE event_type = 'LOGIN' LIMIT 10"
);
// 返回 Map 列表
List<Map<String, Object>> result = eventLogRepository.executeQueryForMap(
"SELECT event_type, count(*) as cnt FROM user_event_log GROUP BY event_type"
);
// 执行 DDL / DML
eventLogRepository.executeUpdate(
"ALTER TABLE user_event_log DELETE WHERE create_time < '2025-01-01'"
);com.mcst.db.clickhouse
├── annotation
│ ├── ClickHouseTable.java # 表名注解
│ └── ClickHouseColumn.java # 列名注解
├── config
│ └── ClickHouseConfig.java # Spring Boot 自动配置类
├── properties
│ └── ClickHouseProperties.java # 配置属性绑定类
├── repository
│ ├── IBaseClickHouseRepository.java # 通用仓库接口
│ └── BaseClickHouseRepositoryImpl.java # 通用仓库实现
├── search
│ └── ClickHouseSearchCondition.java # 查询条件封装类
└── utils
├── ClickHouseSearchConditionUtil.java # 查询条件转换工具类
└── ClickHouseSqlUtil.java # SQL 构建工具类1. 合理设置连接池大小:ClickHouse 适合少量长连接,不建议 maxPoolSize 设置过大。一般 10~30 即可满足需求。
2. 批量写入优先:ClickHouse 对高频小量写入不友好,请尽量使用 insertBatch() 积攒数据后批量写入,建议单批次 1000~5000 条。
3. 善用相对时间:查询近期数据时,使用相对时间表达式(如 -1d、-7d)比拼接绝对时间字符串更简洁,且自动适配服务端时间。
4. ReplacingMergeTree 配合 FINAL:若表引擎为 ReplacingMergeTree,查询时设置 useFinal = true 确保获取去重后的最新数据。
5. 避免频繁 DELETE / UPDATE:ClickHouse 的删除和更新是 ALTER TABLE 异步操作,不适合高频使用,应以追加写入为主。
6. 原生 SQL 兜底:对于复杂查询(如子查询、JOIN、窗口函数),使用 executeQuery() 或 executeQueryForMap() 执行原生 SQL。
7. 注解映射简化代码:为实体类添加 @ClickHouseTable 和 @ClickHouseColumn 注解,可免去手写 SQL 中表名和列名的映射工作。
easyfk-db-clickhouse — 高性能列式分析数据库集成方案。