From d1962ed615e0830a892188612e8921e8d2d299a4 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Sun, 4 Jan 2026 13:17:01 +0800 Subject: [PATCH] =?UTF-8?q?refactor(clickhouse):=20=E5=B0=86=E7=BB=9F?= =?UTF-8?q?=E8=AE=A1=E6=95=B0=E6=8D=AE=E6=9F=A5=E8=AF=A2=E4=BB=8E=20MyBati?= =?UTF-8?q?s=20=E8=BF=81=E7=A7=BB=E5=88=B0=20JDBC=20=E6=A8=A1=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除 ClickHouseStatsMapper 接口及 XML 映射文件 - 使用 NamedParameterJdbcTemplate 替代 MyBatis 实现数据查询 - 添加日期格式化工具类处理 ClickHouse 时间格式 - 重构所有统计查询方法使用原生 SQL 字符串构建 - 添加 MySQL 主数据源配置确保多数据源正确配置 - 升级 ClickHouse JDBC 驱动版本到 0.8.5 - 解决 0.6.x 版本参数绑定问题通过手动 SQL 构建 - 保持原有查询逻辑不变仅改变实现方式 --- pom.xml | 2 +- .../mapper/ClickHouseStatsMapper.java | 95 ------ .../impl/ClickHouseStatsQueryServiceImpl.java | 311 ++++++++++++++++-- .../config/ClickHouseDataSourceConfig.java | 39 +-- .../config/MySqlPrimaryDataSourceConfig.java | 41 +++ .../clickhouse/ClickHouseStatsMapper.xml | 269 --------------- 6 files changed, 341 insertions(+), 416 deletions(-) delete mode 100644 src/main/java/com/ycwl/basic/clickhouse/mapper/ClickHouseStatsMapper.java create mode 100644 src/main/java/com/ycwl/basic/config/MySqlPrimaryDataSourceConfig.java delete mode 100644 src/main/resources/mapper/clickhouse/ClickHouseStatsMapper.xml diff --git a/pom.xml b/pom.xml index fa73ca3f..a886068c 100644 --- a/pom.xml +++ b/pom.xml @@ -308,7 +308,7 @@ com.clickhouse clickhouse-jdbc - 0.6.0 + 0.8.5 all diff --git a/src/main/java/com/ycwl/basic/clickhouse/mapper/ClickHouseStatsMapper.java b/src/main/java/com/ycwl/basic/clickhouse/mapper/ClickHouseStatsMapper.java deleted file mode 100644 index 8f44506f..00000000 --- a/src/main/java/com/ycwl/basic/clickhouse/mapper/ClickHouseStatsMapper.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.ycwl.basic.clickhouse.mapper; - -import com.ycwl.basic.model.mobile.statistic.req.CommonQueryReq; -import org.apache.ibatis.annotations.Mapper; -import org.apache.ibatis.annotations.Param; - -import java.util.Date; -import java.util.HashMap; -import java.util.List; - -/** - * ClickHouse 统计数据 Mapper - * 用于查询 t_stats 和 t_stats_record 表 - */ -@Mapper -public interface ClickHouseStatsMapper { - - /** - * 统计预览视频人数 - */ - Integer countPreviewVideoOfMember(CommonQueryReq query); - - /** - * 统计扫码访问人数 - */ - Integer countScanCodeOfMember(CommonQueryReq query); - - /** - * 统计推送订阅人数 - */ - Integer countPushOfMember(CommonQueryReq query); - - /** - * 统计上传头像人数 - */ - Integer countUploadFaceOfMember(CommonQueryReq query); - - /** - * 统计生成视频人数 - * 注意:需要关联 MySQL 中的 task 表,此处只返回 face_id 列表 - */ - List listFaceIdsWithUpload(CommonQueryReq query); - - /** - * 统计总访问人数 - */ - Integer countTotalVisitorOfMember(CommonQueryReq query); - - /** - * 统计预览视频条数 - */ - Integer countPreviewOfVideo(CommonQueryReq query); - - /** - * 获取用户分销员 ID 列表 - */ - List getBrokerIdListForUser(@Param("memberId") Long memberId, - @Param("startTime") Date startTime, - @Param("endTime") Date endTime); - - /** - * 获取用户最近进入类型 - */ - Long getUserRecentEnterType(@Param("memberId") Long memberId, - @Param("endTime") Date endTime); - - /** - * 获取用户项目 ID 列表 - */ - List getProjectIdListForUser(@Param("memberId") Long memberId, - @Param("startTime") Date startTime, - @Param("endTime") Date endTime); - - /** - * 统计分销员扫码次数 - */ - Integer countBrokerScanCount(@Param("brokerId") Long brokerId); - - /** - * 按日期统计分销员扫码数据(用于 BrokerRecordMapper.getDailySummaryByBrokerId) - */ - List> getDailyScanStats(@Param("brokerId") Long brokerId, - @Param("startTime") Date startTime, - @Param("endTime") Date endTime); - - /** - * 按小时统计扫码人数 - */ - List> scanCodeMemberChartByHour(CommonQueryReq query); - - /** - * 按日期统计扫码人数 - */ - List> scanCodeMemberChartByDate(CommonQueryReq query); -} diff --git a/src/main/java/com/ycwl/basic/clickhouse/service/impl/ClickHouseStatsQueryServiceImpl.java b/src/main/java/com/ycwl/basic/clickhouse/service/impl/ClickHouseStatsQueryServiceImpl.java index 99ec986e..7c5a2b48 100644 --- a/src/main/java/com/ycwl/basic/clickhouse/service/impl/ClickHouseStatsQueryServiceImpl.java +++ b/src/main/java/com/ycwl/basic/clickhouse/service/impl/ClickHouseStatsQueryServiceImpl.java @@ -1,15 +1,17 @@ package com.ycwl.basic.clickhouse.service.impl; -import com.ycwl.basic.clickhouse.mapper.ClickHouseStatsMapper; import com.ycwl.basic.clickhouse.service.StatsQueryService; import com.ycwl.basic.mapper.TaskMapper; import com.ycwl.basic.model.mobile.statistic.req.CommonQueryReq; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.stereotype.Service; -import java.util.Collections; +import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -17,102 +19,367 @@ import java.util.List; /** * ClickHouse 统计数据查询服务实现 * 当 clickhouse.enabled=true 时启用 + * + * 注意:ClickHouse JDBC 驱动 0.6.x 对参数绑定支持有问题, + * 因此使用字符串格式化方式构建 SQL(参数均为内部生成的数值或日期,无 SQL 注入风险) */ @Slf4j @Service @ConditionalOnProperty(prefix = "clickhouse", name = "enabled", havingValue = "true") public class ClickHouseStatsQueryServiceImpl implements StatsQueryService { + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); + private static final SimpleDateFormat DATETIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + @Autowired - private ClickHouseStatsMapper clickHouseStatsMapper; + @Qualifier("clickHouseJdbcTemplate") + private NamedParameterJdbcTemplate namedJdbcTemplate; + + private JdbcTemplate jdbcTemplate; @Autowired private TaskMapper taskMapper; + private JdbcTemplate getJdbcTemplate() { + if (jdbcTemplate == null) { + jdbcTemplate = namedJdbcTemplate.getJdbcTemplate(); + } + return jdbcTemplate; + } + + /** + * 格式化日期时间为 ClickHouse 可识别的字符串 + */ + private String formatDateTime(Date date) { + return date != null ? "'" + DATETIME_FORMAT.format(date) + "'" : null; + } + + /** + * 格式化日期为 ClickHouse 可识别的字符串 + */ + private String formatDate(Date date) { + return date != null ? "'" + DATE_FORMAT.format(date) + "'" : null; + } + @Override public Integer countPreviewVideoOfMember(CommonQueryReq query) { - return clickHouseStatsMapper.countPreviewVideoOfMember(query); + StringBuilder sql = new StringBuilder(); + sql.append("SELECT ifNull(count(1), 0) AS count FROM ( "); + sql.append(" SELECT 1 FROM t_stats_record r "); + sql.append(" INNER JOIN t_stats s ON r.trace_id = s.trace_id "); + sql.append(" WHERE r.trace_id IN ( "); + sql.append(" SELECT trace_id FROM t_stats_record "); + sql.append(" WHERE action = 'ENTER_SCENIC' AND identifier = '").append(query.getScenicId()).append("' "); + sql.append(" ) "); + sql.append(" AND r.action = 'LOAD' "); + sql.append(" AND r.identifier = 'pages/videoSynthesis/buy' "); + sql.append(" AND JSONExtractString(r.params, 'share') = '' "); + if (query.getStartTime() != null) { + sql.append(" AND r.create_time >= ").append(formatDateTime(query.getStartTime())).append(" "); + } + if (query.getEndTime() != null) { + sql.append(" AND r.create_time <= ").append(formatDateTime(query.getEndTime())).append(" "); + } + sql.append(" GROUP BY s.member_id "); + sql.append(")"); + + return getJdbcTemplate().queryForObject(sql.toString(), Integer.class); } @Override public Integer countScanCodeOfMember(CommonQueryReq query) { - return clickHouseStatsMapper.countScanCodeOfMember(query); + StringBuilder sql = new StringBuilder(); + sql.append("SELECT ifNull(count(1), 0) AS count FROM ( "); + sql.append(" SELECT 1 FROM t_stats_record r "); + sql.append(" INNER JOIN t_stats s ON r.trace_id = s.trace_id "); + sql.append(" WHERE r.trace_id IN ( "); + sql.append(" SELECT trace_id FROM t_stats_record "); + sql.append(" WHERE action = 'ENTER_SCENIC' AND identifier = '").append(query.getScenicId()).append("' "); + sql.append(" ) "); + sql.append(" AND r.action = 'LAUNCH' "); + sql.append(" AND JSONExtractInt(r.params, 'scene') IN (1047, 1048, 1049) "); + if (query.getStartTime() != null) { + sql.append(" AND s.create_time >= ").append(formatDateTime(query.getStartTime())).append(" "); + } + if (query.getEndTime() != null) { + sql.append(" AND s.create_time <= ").append(formatDateTime(query.getEndTime())).append(" "); + } + sql.append(" GROUP BY s.member_id "); + sql.append(")"); + + return getJdbcTemplate().queryForObject(sql.toString(), Integer.class); } @Override public Integer countPushOfMember(CommonQueryReq query) { - return clickHouseStatsMapper.countPushOfMember(query); + StringBuilder sql = new StringBuilder(); + sql.append("SELECT ifNull(count(1), 0) AS count FROM ( "); + sql.append(" SELECT 1 FROM t_stats_record r "); + sql.append(" INNER JOIN t_stats s ON r.trace_id = s.trace_id "); + sql.append(" WHERE r.trace_id IN ( "); + sql.append(" SELECT trace_id FROM t_stats_record "); + sql.append(" WHERE action = 'ENTER_SCENIC' AND identifier = '").append(query.getScenicId()).append("' "); + sql.append(" ) "); + sql.append(" AND r.action = 'PERM_REQ' "); + sql.append(" AND r.identifier = 'NOTIFY' "); + if (query.getStartTime() != null) { + sql.append(" AND r.create_time >= ").append(formatDateTime(query.getStartTime())).append(" "); + } + if (query.getEndTime() != null) { + sql.append(" AND r.create_time <= ").append(formatDateTime(query.getEndTime())).append(" "); + } + sql.append(" GROUP BY s.member_id "); + sql.append(")"); + + return getJdbcTemplate().queryForObject(sql.toString(), Integer.class); } @Override public Integer countUploadFaceOfMember(CommonQueryReq query) { - return clickHouseStatsMapper.countUploadFaceOfMember(query); + StringBuilder sql = new StringBuilder(); + sql.append("SELECT ifNull(count(1), 0) AS count FROM ( "); + sql.append(" SELECT 1 FROM t_stats_record r "); + sql.append(" INNER JOIN t_stats s ON r.trace_id = s.trace_id "); + sql.append(" WHERE r.trace_id IN ( "); + sql.append(" SELECT trace_id FROM t_stats_record "); + sql.append(" WHERE action = 'ENTER_SCENIC' AND identifier = '").append(query.getScenicId()).append("' "); + sql.append(" ) "); + sql.append(" AND r.action = 'FACE_UPLOAD' "); + if (query.getStartTime() != null) { + sql.append(" AND s.create_time >= ").append(formatDateTime(query.getStartTime())).append(" "); + } + if (query.getEndTime() != null) { + sql.append(" AND s.create_time <= ").append(formatDateTime(query.getEndTime())).append(" "); + } + sql.append(" GROUP BY s.member_id "); + sql.append(")"); + + return getJdbcTemplate().queryForObject(sql.toString(), Integer.class); + } + + private List listFaceIdsWithUpload(CommonQueryReq query) { + StringBuilder sql = new StringBuilder(); + sql.append("SELECT DISTINCT r.identifier FROM t_stats_record r "); + sql.append("INNER JOIN t_stats s ON r.trace_id = s.trace_id "); + sql.append("WHERE r.trace_id IN ( "); + sql.append(" SELECT trace_id FROM t_stats_record "); + sql.append(" WHERE action = 'ENTER_SCENIC' AND identifier = '").append(query.getScenicId()).append("' "); + sql.append(") "); + sql.append("AND r.action = 'FACE_UPLOAD' "); + if (query.getStartTime() != null) { + sql.append("AND s.create_time >= ").append(formatDateTime(query.getStartTime())).append(" "); + } + if (query.getEndTime() != null) { + sql.append("AND s.create_time <= ").append(formatDateTime(query.getEndTime())).append(" "); + } + + return getJdbcTemplate().queryForList(sql.toString(), String.class); } @Override public Integer countCompleteVideoOfMember(CommonQueryReq query) { - // 从 ClickHouse 获取 face_id 列表,然后在 MySQL 中查询完成的任务 - List faceIds = clickHouseStatsMapper.listFaceIdsWithUpload(query); + List faceIds = listFaceIdsWithUpload(query); if (faceIds == null || faceIds.isEmpty()) { return 0; } - // 在 MySQL 中统计已完成任务的用户数 return taskMapper.countCompletedTaskMembersByFaceIds(faceIds); } @Override public Integer countCompleteOfVideo(CommonQueryReq query) { - // 从 ClickHouse 获取 face_id 列表,然后在 MySQL 中查询完成的任务数 - List faceIds = clickHouseStatsMapper.listFaceIdsWithUpload(query); + List faceIds = listFaceIdsWithUpload(query); if (faceIds == null || faceIds.isEmpty()) { return 0; } - // 在 MySQL 中统计已完成的任务数 return taskMapper.countCompletedTasksByFaceIds(faceIds); } @Override public Integer countTotalVisitorOfMember(CommonQueryReq query) { - return clickHouseStatsMapper.countTotalVisitorOfMember(query); + StringBuilder sql = new StringBuilder(); + sql.append("SELECT ifNull(count(1), 0) AS count FROM ( "); + sql.append(" SELECT 1 FROM t_stats_record r "); + sql.append(" INNER JOIN t_stats s ON r.trace_id = s.trace_id "); + sql.append(" WHERE r.trace_id IN ( "); + sql.append(" SELECT trace_id FROM t_stats_record "); + sql.append(" WHERE action = 'ENTER_SCENIC' AND identifier = '").append(query.getScenicId()).append("' "); + sql.append(" ) "); + sql.append(" AND r.action = 'LAUNCH' "); + if (query.getStartTime() != null) { + sql.append(" AND s.create_time >= ").append(formatDateTime(query.getStartTime())).append(" "); + } + if (query.getEndTime() != null) { + sql.append(" AND s.create_time <= ").append(formatDateTime(query.getEndTime())).append(" "); + } + sql.append(" GROUP BY s.member_id "); + sql.append(")"); + + return getJdbcTemplate().queryForObject(sql.toString(), Integer.class); } @Override public Integer countPreviewOfVideo(CommonQueryReq query) { - return clickHouseStatsMapper.countPreviewOfVideo(query); + StringBuilder sql = new StringBuilder(); + sql.append("SELECT ifNull(count(1), 0) AS count FROM ( "); + sql.append(" SELECT 1 FROM t_stats_record r "); + sql.append(" INNER JOIN t_stats s ON r.trace_id = s.trace_id "); + sql.append(" WHERE r.trace_id IN ( "); + sql.append(" SELECT trace_id FROM t_stats_record "); + sql.append(" WHERE action = 'ENTER_SCENIC' AND identifier = '").append(query.getScenicId()).append("' "); + sql.append(" ) "); + sql.append(" AND r.action = 'LOAD' "); + sql.append(" AND r.identifier = 'pages/videoSynthesis/buy' "); + sql.append(" AND JSONExtractString(r.params, 'id') != '' "); + sql.append(" AND JSONExtractString(r.params, 'share') = '' "); + if (query.getStartTime() != null) { + sql.append(" AND s.create_time >= ").append(formatDateTime(query.getStartTime())).append(" "); + } + if (query.getEndTime() != null) { + sql.append(" AND s.create_time <= ").append(formatDateTime(query.getEndTime())).append(" "); + } + sql.append(" GROUP BY JSONExtractString(r.params, 'id') "); + sql.append(")"); + + return getJdbcTemplate().queryForObject(sql.toString(), Integer.class); } @Override public List getBrokerIdListForUser(Long memberId, Date startTime, Date endTime) { - return clickHouseStatsMapper.getBrokerIdListForUser(memberId, startTime, endTime); + StringBuilder sql = new StringBuilder(); + sql.append("SELECT toInt64(sub.identifier) AS identifier FROM ( "); + sql.append(" SELECT identifier, max(r.create_time) AS createTime "); + sql.append(" FROM t_stats_record r "); + sql.append(" INNER JOIN t_stats s ON r.trace_id = s.trace_id "); + sql.append(" WHERE r.action = 'CODE_SCAN' "); + sql.append(" AND s.member_id = ").append(memberId).append(" "); + if (startTime != null) { + sql.append(" AND r.create_time >= ").append(formatDateTime(startTime)).append(" "); + } + if (endTime != null) { + sql.append(" AND r.create_time <= ").append(formatDateTime(endTime)).append(" "); + } + sql.append(" GROUP BY identifier "); + sql.append(") sub ORDER BY sub.createTime DESC"); + + return getJdbcTemplate().queryForList(sql.toString(), Long.class); } @Override public Long getUserRecentEnterType(Long memberId, Date endTime) { - return clickHouseStatsMapper.getUserRecentEnterType(memberId, endTime); + StringBuilder sql = new StringBuilder(); + sql.append("SELECT JSONExtractInt(r.params, 'scene') AS scene "); + sql.append("FROM t_stats_record r "); + sql.append("INNER JOIN t_stats s ON r.trace_id = s.trace_id "); + sql.append("WHERE r.action = 'LAUNCH' "); + sql.append(" AND s.member_id = ").append(memberId).append(" "); + if (endTime != null) { + sql.append(" AND r.create_time <= ").append(formatDateTime(endTime)).append(" "); + } + sql.append("ORDER BY r.create_time DESC LIMIT 1"); + + try { + return getJdbcTemplate().queryForObject(sql.toString(), Long.class); + } catch (Exception e) { + return null; + } } @Override public List getProjectIdListForUser(Long memberId, Date startTime, Date endTime) { - return clickHouseStatsMapper.getProjectIdListForUser(memberId, startTime, endTime); + StringBuilder sql = new StringBuilder(); + sql.append("SELECT toInt64(r.identifier) AS identifier "); + sql.append("FROM t_stats_record r "); + sql.append("INNER JOIN t_stats s ON r.trace_id = s.trace_id "); + sql.append("WHERE s.member_id = ").append(memberId).append(" "); + sql.append(" AND r.action = 'ENTER_PROJECT' "); + sql.append(" AND r.create_time < ").append(formatDateTime(endTime)).append(" "); + sql.append(" AND r.create_time > ").append(formatDateTime(startTime)).append(" "); + sql.append("ORDER BY r.create_time DESC LIMIT 1"); + + return getJdbcTemplate().queryForList(sql.toString(), Long.class); } @Override public Integer countBrokerScanCount(Long brokerId) { - return clickHouseStatsMapper.countBrokerScanCount(brokerId); + String sql = "SELECT count(1) AS count FROM t_stats_record " + + "WHERE action = 'CODE_SCAN' AND identifier = '" + brokerId + "'"; + + return getJdbcTemplate().queryForObject(sql, Integer.class); } @Override public List> getDailyScanStats(Long brokerId, Date startTime, Date endTime) { - return clickHouseStatsMapper.getDailyScanStats(brokerId, startTime, endTime); + String startDateStr = DATE_FORMAT.format(startTime); + String endDateStr = DATE_FORMAT.format(endTime); + + String sql = "SELECT toDate(create_time) AS date, count(DISTINCT id) AS scanCount " + + "FROM t_stats_record " + + "WHERE action = 'CODE_SCAN' " + + " AND identifier = '" + brokerId + "' " + + " AND toDate(create_time) BETWEEN '" + startDateStr + "' AND '" + endDateStr + "' " + + "GROUP BY toDate(create_time)"; + + return getJdbcTemplate().query(sql, (rs, rowNum) -> { + HashMap map = new HashMap<>(); + map.put("date", rs.getDate("date")); + map.put("scanCount", rs.getLong("scanCount")); + return map; + }); } @Override public List> scanCodeMemberChartByHour(CommonQueryReq query) { - return clickHouseStatsMapper.scanCodeMemberChartByHour(query); + StringBuilder sql = new StringBuilder(); + sql.append("SELECT formatDateTime(s.create_time, '%m-%d %H') AS t, "); + sql.append(" count(DISTINCT s.member_id) AS count "); + sql.append("FROM t_stats_record r "); + sql.append("INNER JOIN t_stats s ON r.trace_id = s.trace_id "); + sql.append("WHERE r.trace_id IN ( "); + sql.append(" SELECT trace_id FROM t_stats_record "); + sql.append(" WHERE action = 'ENTER_SCENIC' "); + if (query.getScenicId() != null) { + sql.append(" AND identifier = '").append(query.getScenicId()).append("' "); + } + sql.append(") "); + sql.append("AND r.action = 'LAUNCH' "); + sql.append("AND JSONExtractInt(r.params, 'scene') IN (1047, 1048, 1049) "); + sql.append("AND s.create_time BETWEEN ").append(formatDateTime(query.getStartTime())); + sql.append(" AND ").append(formatDateTime(query.getEndTime())).append(" "); + sql.append("GROUP BY formatDateTime(s.create_time, '%m-%d %H')"); + + return getJdbcTemplate().query(sql.toString(), (rs, rowNum) -> { + HashMap map = new HashMap<>(); + map.put("t", rs.getString("t")); + map.put("count", rs.getString("count")); + return map; + }); } @Override public List> scanCodeMemberChartByDate(CommonQueryReq query) { - return clickHouseStatsMapper.scanCodeMemberChartByDate(query); + StringBuilder sql = new StringBuilder(); + sql.append("SELECT formatDateTime(s.create_time, '%m-%d') AS t, "); + sql.append(" count(DISTINCT s.member_id) AS count "); + sql.append("FROM t_stats_record r "); + sql.append("INNER JOIN t_stats s ON r.trace_id = s.trace_id "); + sql.append("WHERE r.trace_id IN ( "); + sql.append(" SELECT trace_id FROM t_stats_record "); + sql.append(" WHERE action = 'ENTER_SCENIC' "); + if (query.getScenicId() != null) { + sql.append(" AND identifier = '").append(query.getScenicId()).append("' "); + } + sql.append(") "); + sql.append("AND r.action = 'LAUNCH' "); + sql.append("AND JSONExtractInt(r.params, 'scene') IN (1047, 1048, 1049) "); + sql.append("AND s.create_time BETWEEN ").append(formatDateTime(query.getStartTime())); + sql.append(" AND ").append(formatDateTime(query.getEndTime())).append(" "); + sql.append("GROUP BY formatDateTime(s.create_time, '%m-%d')"); + + return getJdbcTemplate().query(sql.toString(), (rs, rowNum) -> { + HashMap map = new HashMap<>(); + map.put("t", rs.getString("t")); + map.put("count", rs.getString("count")); + return map; + }); } } diff --git a/src/main/java/com/ycwl/basic/config/ClickHouseDataSourceConfig.java b/src/main/java/com/ycwl/basic/config/ClickHouseDataSourceConfig.java index f6084b21..6eba3b9b 100644 --- a/src/main/java/com/ycwl/basic/config/ClickHouseDataSourceConfig.java +++ b/src/main/java/com/ycwl/basic/config/ClickHouseDataSourceConfig.java @@ -1,56 +1,37 @@ package com.ycwl.basic.config; import com.zaxxer.hikari.HikariDataSource; -import org.apache.ibatis.session.SqlSessionFactory; -import org.mybatis.spring.SqlSessionFactoryBean; -import org.mybatis.spring.SqlSessionTemplate; -import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import javax.sql.DataSource; /** * ClickHouse 数据源配置 * 用于 t_stats 和 t_stats_record 表的查询 + * + * 使用 NamedParameterJdbcTemplate 而非 MyBatis,以避免干扰 MyBatis-Plus 的自动配置 */ @Configuration @ConditionalOnProperty(prefix = "clickhouse", name = "enabled", havingValue = "true") -@MapperScan( - basePackages = "com.ycwl.basic.clickhouse.mapper", - sqlSessionFactoryRef = "clickHouseSqlSessionFactory" -) public class ClickHouseDataSourceConfig { + /** + * ClickHouse 数据源(非 Primary) + */ @Bean(name = "clickHouseDataSource") @ConfigurationProperties(prefix = "clickhouse.datasource") public DataSource clickHouseDataSource() { return new HikariDataSource(); } - @Bean(name = "clickHouseSqlSessionFactory") - public SqlSessionFactory clickHouseSqlSessionFactory( - @Qualifier("clickHouseDataSource") DataSource dataSource) throws Exception { - SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); - factoryBean.setDataSource(dataSource); - factoryBean.setMapperLocations( - new PathMatchingResourcePatternResolver() - .getResources("classpath:mapper/clickhouse/*.xml") - ); - // 配置 MyBatis 设置 - org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(); - configuration.setMapUnderscoreToCamelCase(true); - factoryBean.setConfiguration(configuration); - return factoryBean.getObject(); - } - - @Bean(name = "clickHouseSqlSessionTemplate") - public SqlSessionTemplate clickHouseSqlSessionTemplate( - @Qualifier("clickHouseSqlSessionFactory") SqlSessionFactory sqlSessionFactory) { - return new SqlSessionTemplate(sqlSessionFactory); + @Bean(name = "clickHouseJdbcTemplate") + public NamedParameterJdbcTemplate clickHouseJdbcTemplate( + @Qualifier("clickHouseDataSource") DataSource dataSource) { + return new NamedParameterJdbcTemplate(dataSource); } } diff --git a/src/main/java/com/ycwl/basic/config/MySqlPrimaryDataSourceConfig.java b/src/main/java/com/ycwl/basic/config/MySqlPrimaryDataSourceConfig.java new file mode 100644 index 00000000..49d4f56f --- /dev/null +++ b/src/main/java/com/ycwl/basic/config/MySqlPrimaryDataSourceConfig.java @@ -0,0 +1,41 @@ +package com.ycwl.basic.config; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +import javax.sql.DataSource; + +/** + * MySQL 主数据源配置 + * + * 当 ClickHouse 启用时,需要显式配置 MySQL 数据源并标记为 @Primary, + * 以确保 MyBatis-Plus 和其他组件使用正确的数据源 + */ +@Configuration +@ConditionalOnProperty(prefix = "clickhouse", name = "enabled", havingValue = "true") +public class MySqlPrimaryDataSourceConfig { + + /** + * MySQL 数据源属性 + */ + @Primary + @Bean + @ConfigurationProperties(prefix = "spring.datasource") + public DataSourceProperties mysqlDataSourceProperties() { + return new DataSourceProperties(); + } + + /** + * MySQL 主数据源 + * 使用 @Primary 确保这是默认数据源 + */ + @Primary + @Bean(name = "dataSource") + public DataSource mysqlDataSource(DataSourceProperties properties) { + return properties.initializeDataSourceBuilder().build(); + } +} diff --git a/src/main/resources/mapper/clickhouse/ClickHouseStatsMapper.xml b/src/main/resources/mapper/clickhouse/ClickHouseStatsMapper.xml deleted file mode 100644 index 43edd275..00000000 --- a/src/main/resources/mapper/clickhouse/ClickHouseStatsMapper.xml +++ /dev/null @@ -1,269 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -