Commit 8fdc34e9b3af191bf5d4b2bfb2cce5bd92533451
1 parent
74e32f50
fix(mybatis): 修复 Map 查询结果的列名大小写兼容
Showing
9 changed files
with
257 additions
and
8 deletions
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/config/YudaoMybatisAutoConfiguration.java
| ... | ... | @@ -4,11 +4,15 @@ import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.hutool.core.util.StrUtil; |
| 5 | 5 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 6 | 6 | import cn.iocoder.yudao.framework.mybatis.core.handler.DefaultDBFieldHandler; |
| 7 | +import cn.iocoder.yudao.framework.mybatis.core.injector.YudaoSqlInjector; | |
| 8 | +import cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap; | |
| 7 | 9 | import com.baomidou.mybatisplus.annotation.DbType; |
| 10 | +import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer; | |
| 8 | 11 | import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; |
| 9 | 12 | import com.baomidou.mybatisplus.core.handlers.IJsonTypeHandler; |
| 10 | 13 | import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; |
| 11 | 14 | import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator; |
| 15 | +import com.baomidou.mybatisplus.core.injector.ISqlInjector; | |
| 12 | 16 | import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; |
| 13 | 17 | import com.baomidou.mybatisplus.extension.incrementer.*; |
| 14 | 18 | import com.baomidou.mybatisplus.extension.parser.JsqlParserGlobal; |
| ... | ... | @@ -17,12 +21,15 @@ import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; |
| 17 | 21 | import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; |
| 18 | 22 | import com.fasterxml.jackson.databind.ObjectMapper; |
| 19 | 23 | import org.apache.ibatis.annotations.Mapper; |
| 24 | +import org.apache.ibatis.mapping.ResultMap; | |
| 20 | 25 | import org.mybatis.spring.annotation.MapperScan; |
| 21 | 26 | import org.springframework.boot.autoconfigure.AutoConfiguration; |
| 22 | 27 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
| 23 | 28 | import org.springframework.context.annotation.Bean; |
| 29 | +import org.springframework.context.annotation.Primary; | |
| 24 | 30 | import org.springframework.core.env.ConfigurableEnvironment; |
| 25 | 31 | |
| 32 | +import java.util.ArrayList; | |
| 26 | 33 | import java.util.List; |
| 27 | 34 | import java.util.concurrent.TimeUnit; |
| 28 | 35 | |
| ... | ... | @@ -59,6 +66,19 @@ public class YudaoMybatisAutoConfiguration { |
| 59 | 66 | } |
| 60 | 67 | |
| 61 | 68 | @Bean |
| 69 | + @Primary // 特殊:使用 @Primary 的原因,避免 MyBatis Plus Join 再创建 MPJSqlInjector 包装它 | |
| 70 | + public ISqlInjector yudaoSqlInjector() { | |
| 71 | + return new YudaoSqlInjector(); // selectMaps 的结果行,使用 CaseInsensitiveResultMap | |
| 72 | + } | |
| 73 | + | |
| 74 | + @Bean | |
| 75 | + public ConfigurationCustomizer caseInsensitiveResultMapConfigurationCustomizer() { | |
| 76 | + // 注册 CaseInsensitiveResultMap 的 resultMap,用于注解 SQL 的 @ResultMap | |
| 77 | + return configuration -> configuration.addResultMap(new ResultMap.Builder(configuration, | |
| 78 | + CaseInsensitiveResultMap.RESULT_MAP_ID, CaseInsensitiveResultMap.class, new ArrayList<>()).build()); | |
| 79 | + } | |
| 80 | + | |
| 81 | + @Bean | |
| 62 | 82 | @ConditionalOnProperty(prefix = "mybatis-plus.global-config.db-config", name = "id-type", havingValue = "INPUT") |
| 63 | 83 | public IKeyGenerator keyGenerator(ConfigurableEnvironment environment) { |
| 64 | 84 | DbType dbType = IdTypeEnvironmentPostProcessor.getDbType(environment); | ... | ... |
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/injector/YudaoSqlInjector.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.mybatis.core.injector; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap; | |
| 4 | +import com.baomidou.mybatisplus.core.injector.AbstractMethod; | |
| 5 | +import com.baomidou.mybatisplus.core.metadata.TableInfo; | |
| 6 | +import com.github.yulichang.injector.MPJSqlInjector; | |
| 7 | +import com.github.yulichang.method.mp.SelectMaps; | |
| 8 | +import org.apache.ibatis.mapping.MappedStatement; | |
| 9 | +import org.apache.ibatis.mapping.SqlSource; | |
| 10 | +import org.apache.ibatis.session.Configuration; | |
| 11 | + | |
| 12 | +import java.util.List; | |
| 13 | + | |
| 14 | +/** | |
| 15 | + * 自定义 SQL 注入器 | |
| 16 | + * | |
| 17 | + * 在 MyBatis Plus Join 的 {@link MPJSqlInjector} 基础上,将 selectMaps 的结果行替换为 {@link CaseInsensitiveResultMap}, | |
| 18 | + * 解决 PostgreSQL、Oracle 等数据库列标签大小写折叠,导致按固定 key 取不到值的问题 | |
| 19 | + * | |
| 20 | + * @author 芋道源码 | |
| 21 | + */ | |
| 22 | +public class YudaoSqlInjector extends MPJSqlInjector { | |
| 23 | + | |
| 24 | + @Override | |
| 25 | + public List<AbstractMethod> getMethodList(Configuration configuration, Class<?> mapperClass, TableInfo tableInfo) { | |
| 26 | + List<AbstractMethod> methods = super.getMethodList(configuration, mapperClass, tableInfo); | |
| 27 | + methods.replaceAll(method -> method instanceof SelectMaps ? new CaseInsensitiveSelectMaps() : method); | |
| 28 | + return methods; | |
| 29 | + } | |
| 30 | + | |
| 31 | + /** | |
| 32 | + * 结果行为 {@link CaseInsensitiveResultMap} 的 selectMaps | |
| 33 | + * | |
| 34 | + * 仅修改结果类型,SQL 仍由 MyBatis Plus Join 的 {@link SelectMaps} 生成,兼容 MPJ 的 Wrapper | |
| 35 | + */ | |
| 36 | + private static class CaseInsensitiveSelectMaps extends SelectMaps { | |
| 37 | + | |
| 38 | + @Override | |
| 39 | + protected MappedStatement addSelectMappedStatementForOther(Class<?> mapperClass, String id, SqlSource sqlSource, | |
| 40 | + Class<?> resultType) { | |
| 41 | + return super.addSelectMappedStatementForOther(mapperClass, id, sqlSource, CaseInsensitiveResultMap.class); | |
| 42 | + } | |
| 43 | + | |
| 44 | + } | |
| 45 | + | |
| 46 | +} | ... | ... |
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java
| ... | ... | @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 6 | 6 | import cn.iocoder.yudao.framework.common.pojo.SortablePageParam; |
| 7 | 7 | import cn.iocoder.yudao.framework.common.pojo.SortingField; |
| 8 | 8 | import cn.iocoder.yudao.framework.common.util.object.PageUtils; |
| 9 | +import cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap; | |
| 9 | 10 | import cn.iocoder.yudao.framework.mybatis.core.util.JdbcUtils; |
| 10 | 11 | import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; |
| 11 | 12 | import com.baomidou.mybatisplus.annotation.DbType; |
| ... | ... | @@ -14,6 +15,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| 14 | 15 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| 15 | 16 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| 16 | 17 | import com.baomidou.mybatisplus.core.metadata.IPage; |
| 18 | +import com.baomidou.mybatisplus.core.toolkit.Constants; | |
| 17 | 19 | import com.baomidou.mybatisplus.core.toolkit.support.SFunction; |
| 18 | 20 | import com.baomidou.mybatisplus.extension.toolkit.Db; |
| 19 | 21 | import com.github.yulichang.base.MPJBaseMapper; |
| ... | ... | @@ -23,6 +25,7 @@ import org.apache.ibatis.annotations.Param; |
| 23 | 25 | |
| 24 | 26 | import java.util.Collection; |
| 25 | 27 | import java.util.List; |
| 28 | +import java.util.Map; | |
| 26 | 29 | |
| 27 | 30 | /** |
| 28 | 31 | * 在 MyBatis Plus 的 BaseMapper 的基础上拓展,提供更多的能力 |
| ... | ... | @@ -99,6 +102,24 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> { |
| 99 | 102 | return new PageResult<>(mpPage.getRecords(), mpPage.getTotal()); |
| 100 | 103 | } |
| 101 | 104 | |
| 105 | + @Override | |
| 106 | + @SuppressWarnings("unchecked") | |
| 107 | + default Map<String, Object> selectJoinMap(@Param(Constants.WRAPPER) MPJBaseJoin<T> wrapper) { | |
| 108 | + return selectJoinOne(CaseInsensitiveResultMap.class, wrapper); | |
| 109 | + } | |
| 110 | + | |
| 111 | + @Override | |
| 112 | + @SuppressWarnings("unchecked") | |
| 113 | + default List<Map<String, Object>> selectJoinMaps(@Param(Constants.WRAPPER) MPJBaseJoin<T> wrapper) { | |
| 114 | + return (List<Map<String, Object>>) ((Object) selectJoinList(CaseInsensitiveResultMap.class, wrapper)); | |
| 115 | + } | |
| 116 | + | |
| 117 | + @Override | |
| 118 | + @SuppressWarnings({"unchecked", "rawtypes"}) | |
| 119 | + default <P extends IPage<Map<String, Object>>> P selectJoinMapsPage(P page, @Param(Constants.WRAPPER) MPJBaseJoin<T> wrapper) { | |
| 120 | + return (P) selectJoinPage((IPage) page, CaseInsensitiveResultMap.class, wrapper); | |
| 121 | + } | |
| 122 | + | |
| 102 | 123 | default T selectOne(String field, Object value) { |
| 103 | 124 | return selectOne(new QueryWrapper<T>().eq(field, value)); |
| 104 | 125 | } | ... | ... |
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/CaseInsensitiveResultMap.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.mybatis.core.util; | |
| 2 | + | |
| 3 | +import org.springframework.util.LinkedCaseInsensitiveMap; | |
| 4 | + | |
| 5 | +import java.util.Locale; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * key 大小写不敏感的 MyBatis 查询结果 Map | |
| 9 | + * | |
| 10 | + * 背景:使用 Map 接收查询结果时,key 为 JDBC 返回的列标签。PostgreSQL 等会将未加引号的列名、别名折叠为小写, | |
| 11 | + * Oracle、达梦等会折叠为大写,导致 map.get("conversationId") 这类按固定 key 取值的代码,在 MySQL 以外的数据库取不到值。 | |
| 12 | + * | |
| 13 | + * 实现:基于 {@link LinkedCaseInsensitiveMap},get / containsKey / remove 时忽略 key 的大小写, | |
| 14 | + * 同时保留原始的列标签作为 key(遍历、序列化时不变)。固定使用 {@link Locale#ROOT},避免服务器默认 Locale(例如土耳其语)影响 key 的匹配。 | |
| 15 | + * | |
| 16 | + * 使用方式:由 MyBatis 直接创建结果行,Mapper 的返回类型仍声明为 Map,业务无需感知该类 | |
| 17 | + * 1. MyBatis Plus 的 selectMaps:由 YudaoSqlInjector 注入,自动使用该类 | |
| 18 | + * 2. MyBatis Plus Join 的 selectJoinMaps 等:由 BaseMapperX 覆盖,自动使用该类 | |
| 19 | + * 3. XML SQL:将 resultType 设置为该类 | |
| 20 | + * 4. 注解 SQL:添加 @ResultMap({@link #RESULT_MAP_ID}) | |
| 21 | + * | |
| 22 | + * 注意:仅大小写不同的多个 key,会合并为一个,保留后写入的 key 和值 | |
| 23 | + * | |
| 24 | + * @author 芋道源码 | |
| 25 | + * @see <a href="https://t.zsxq.com/kPvqm">星球讨论:Map 查询结果的跨数据库兼容问题</a> | |
| 26 | + */ | |
| 27 | +public class CaseInsensitiveResultMap<V> extends LinkedCaseInsensitiveMap<V> { | |
| 28 | + | |
| 29 | + /** | |
| 30 | + * 注解 SQL 使用的 resultMap 编号 | |
| 31 | + * | |
| 32 | + * 由 YudaoMybatisAutoConfiguration 注册到 MyBatis 全局配置;包含“.”,所以不会拼接 Mapper 的 namespace | |
| 33 | + */ | |
| 34 | + public static final String RESULT_MAP_ID = "cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap"; | |
| 35 | + | |
| 36 | + /** | |
| 37 | + * 创建空的结果 Map | |
| 38 | + * | |
| 39 | + * 用途:MyBatis 通过无参构造方法,实例化结果行 | |
| 40 | + */ | |
| 41 | + public CaseInsensitiveResultMap() { | |
| 42 | + super(Locale.ROOT); | |
| 43 | + } | |
| 44 | + | |
| 45 | +} | ... | ... |
yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/core/util/CaseInsensitiveResultMapTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.mybatis.core.util; | |
| 2 | + | |
| 3 | +import cn.hutool.core.map.MapUtil; | |
| 4 | +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | |
| 5 | +import org.junit.jupiter.api.Test; | |
| 6 | +import org.springframework.util.LinkedCaseInsensitiveMap; | |
| 7 | + | |
| 8 | +import java.util.*; | |
| 9 | + | |
| 10 | +import static org.junit.jupiter.api.Assertions.*; | |
| 11 | + | |
| 12 | +/** | |
| 13 | + * {@link CaseInsensitiveResultMap} 的单元测试 | |
| 14 | + * | |
| 15 | + * @author 芋道源码 | |
| 16 | + */ | |
| 17 | +public class CaseInsensitiveResultMapTest { | |
| 18 | + | |
| 19 | + @Test | |
| 20 | + public void testGet_ignoreCase() { | |
| 21 | + // 准备参数 | |
| 22 | + CaseInsensitiveResultMap<Object> map = new CaseInsensitiveResultMap<>(); | |
| 23 | + map.put("conversationId", 1024L); | |
| 24 | + | |
| 25 | + // 调用,并断言:PostgreSQL 小写、Oracle 大写,都可以取到值 | |
| 26 | + assertEquals(1024L, map.get("conversationId")); | |
| 27 | + assertEquals(1024L, map.get("conversationid")); | |
| 28 | + assertEquals(1024L, map.get("CONVERSATIONID")); | |
| 29 | + assertTrue(map.containsKey("CONVERSATIONID")); | |
| 30 | + assertEquals(1024L, MapUtil.getLong(map, "conversationid")); | |
| 31 | + // 断言:保留原始的 key | |
| 32 | + assertEquals(Collections.singletonList("conversationId"), new ArrayList<>(map.keySet())); | |
| 33 | + } | |
| 34 | + | |
| 35 | + @Test | |
| 36 | + public void testPut_nullValue() { | |
| 37 | + // 准备参数 | |
| 38 | + CaseInsensitiveResultMap<Object> map = new CaseInsensitiveResultMap<>(); | |
| 39 | + map.put("sum_count", null); | |
| 40 | + | |
| 41 | + // 调用,并断言 | |
| 42 | + assertTrue(map.containsKey("SUM_COUNT")); | |
| 43 | + assertNull(map.get("SUM_COUNT")); | |
| 44 | + assertEquals(0, MapUtil.getInt(map, "SUM_COUNT", 0)); | |
| 45 | + } | |
| 46 | + | |
| 47 | + @Test | |
| 48 | + public void testPut_sameKeyIgnoreCase() { | |
| 49 | + // 准备参数 | |
| 50 | + CaseInsensitiveResultMap<Object> map = new CaseInsensitiveResultMap<>(); | |
| 51 | + | |
| 52 | + // 调用:仅大小写不同的 key | |
| 53 | + map.put("id", 1L); | |
| 54 | + map.put("ID", 2L); | |
| 55 | + | |
| 56 | + // 断言:合并为一个,保留后写入的 key 和值 | |
| 57 | + assertEquals(1, map.size()); | |
| 58 | + assertEquals(2L, map.get("id")); | |
| 59 | + assertEquals(Collections.singletonList("ID"), new ArrayList<>(map.keySet())); | |
| 60 | + } | |
| 61 | + | |
| 62 | + @Test | |
| 63 | + public void testGet_turkishDefaultLocale() { | |
| 64 | + Locale defaultLocale = Locale.getDefault(); | |
| 65 | + try { | |
| 66 | + // 准备参数:土耳其语下,大写 I 转小写为无点的 ı | |
| 67 | + Locale.setDefault(Locale.forLanguageTag("tr-TR")); | |
| 68 | + CaseInsensitiveResultMap<Object> map = new CaseInsensitiveResultMap<>(); | |
| 69 | + map.put("CONVERSATIONID", 1024L); | |
| 70 | + LinkedCaseInsensitiveMap<Object> defaultLocaleMap = new LinkedCaseInsensitiveMap<>(); | |
| 71 | + defaultLocaleMap.put("CONVERSATIONID", 1024L); | |
| 72 | + | |
| 73 | + // 调用,并断言:固定 Locale.ROOT,不受默认 Locale 影响 | |
| 74 | + assertEquals(1024L, map.get("conversationId")); | |
| 75 | + // 断言:使用默认 Locale 时,取不到值 | |
| 76 | + assertNull(defaultLocaleMap.get("conversationId")); | |
| 77 | + } finally { | |
| 78 | + Locale.setDefault(defaultLocale); | |
| 79 | + } | |
| 80 | + } | |
| 81 | + | |
| 82 | + @Test | |
| 83 | + public void testConvertMap_numberKey() { | |
| 84 | + // 准备参数:模拟 Oracle 返回的结果行 | |
| 85 | + List<Map<String, Object>> rows = Arrays.asList(buildRow(1024L, 3), buildRow(2048L, 5)); | |
| 86 | + | |
| 87 | + // 调用:业务转换为数字 key 的 Map | |
| 88 | + Map<Long, Integer> result = CollectionUtils.convertMap(rows, | |
| 89 | + row -> MapUtil.getLong(row, "conversationId"), | |
| 90 | + row -> MapUtil.getInt(row, "count")); | |
| 91 | + | |
| 92 | + // 断言:数字 key 的业务 Map 不受影响 | |
| 93 | + assertInstanceOf(HashMap.class, result); | |
| 94 | + assertEquals(3, result.get(1024L)); | |
| 95 | + assertEquals(5, result.get(2048L)); | |
| 96 | + } | |
| 97 | + | |
| 98 | + private static Map<String, Object> buildRow(Long conversationId, Integer count) { | |
| 99 | + CaseInsensitiveResultMap<Object> row = new CaseInsensitiveResultMap<>(); | |
| 100 | + row.put("CONVERSATIONID", conversationId); | |
| 101 | + row.put("COUNT", count); | |
| 102 | + return row; | |
| 103 | + } | |
| 104 | + | |
| 105 | +} | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/statistics/ImStatisticsManagerMapper.java
| 1 | 1 | package cn.iocoder.yudao.module.im.dal.mysql.statistics; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap; | |
| 3 | 4 | import org.apache.ibatis.annotations.Mapper; |
| 4 | 5 | import org.apache.ibatis.annotations.Param; |
| 6 | +import org.apache.ibatis.annotations.ResultMap; | |
| 5 | 7 | import org.apache.ibatis.annotations.Select; |
| 6 | 8 | |
| 7 | 9 | import java.time.LocalDateTime; |
| ... | ... | @@ -59,6 +61,7 @@ public interface ImStatisticsManagerMapper { |
| 59 | 61 | @Select("SELECT DATE(create_time) AS date, COUNT(*) AS count FROM system_users " + |
| 60 | 62 | "WHERE deleted = 0 AND create_time >= #{beginTime} AND create_time < #{endTime} " + |
| 61 | 63 | "GROUP BY DATE(create_time)") |
| 64 | + @ResultMap(CaseInsensitiveResultMap.RESULT_MAP_ID) | |
| 62 | 65 | List<Map<String, Object>> selectNewUserDailyCount(@Param("beginTime") LocalDateTime beginTime, |
| 63 | 66 | @Param("endTime") LocalDateTime endTime); |
| 64 | 67 | |
| ... | ... | @@ -74,6 +77,7 @@ public interface ImStatisticsManagerMapper { |
| 74 | 77 | " WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION + |
| 75 | 78 | " AND send_time >= #{beginTime} AND send_time < #{endTime}" + |
| 76 | 79 | ") t GROUP BY day") |
| 80 | + @ResultMap(CaseInsensitiveResultMap.RESULT_MAP_ID) | |
| 77 | 81 | List<Map<String, Object>> selectActiveUserDailyCount(@Param("beginTime") LocalDateTime beginTime, @Param("endTime") LocalDateTime endTime); |
| 78 | 82 | |
| 79 | 83 | // ==================== 群 ==================== |
| ... | ... | @@ -111,6 +115,7 @@ public interface ImStatisticsManagerMapper { |
| 111 | 115 | " GROUP BY g.id" + |
| 112 | 116 | ") t " + |
| 113 | 117 | "GROUP BY `range`") |
| 118 | + @ResultMap(CaseInsensitiveResultMap.RESULT_MAP_ID) | |
| 114 | 119 | List<Map<String, Object>> selectGroupSizeDistribution(); |
| 115 | 120 | |
| 116 | 121 | // ==================== 消息 ==================== |
| ... | ... | @@ -140,6 +145,7 @@ public interface ImStatisticsManagerMapper { |
| 140 | 145 | "WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION + |
| 141 | 146 | " AND send_time >= #{beginTime} AND send_time < #{endTime} " + |
| 142 | 147 | "GROUP BY DATE(send_time)") |
| 148 | + @ResultMap(CaseInsensitiveResultMap.RESULT_MAP_ID) | |
| 143 | 149 | List<Map<String, Object>> selectPrivateMessageDailyCount(@Param("beginTime") LocalDateTime beginTime, |
| 144 | 150 | @Param("endTime") LocalDateTime endTime); |
| 145 | 151 | |
| ... | ... | @@ -150,6 +156,7 @@ public interface ImStatisticsManagerMapper { |
| 150 | 156 | "WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION + |
| 151 | 157 | " AND send_time >= #{beginTime} AND send_time < #{endTime} " + |
| 152 | 158 | "GROUP BY DATE(send_time)") |
| 159 | + @ResultMap(CaseInsensitiveResultMap.RESULT_MAP_ID) | |
| 153 | 160 | List<Map<String, Object>> selectGroupMessageDailyCount(@Param("beginTime") LocalDateTime beginTime, |
| 154 | 161 | @Param("endTime") LocalDateTime endTime); |
| 155 | 162 | |
| ... | ... | @@ -167,6 +174,7 @@ public interface ImStatisticsManagerMapper { |
| 167 | 174 | " WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION + |
| 168 | 175 | " AND send_time >= #{beginTime} AND send_time < #{endTime}" + |
| 169 | 176 | ") t GROUP BY type") |
| 177 | + @ResultMap(CaseInsensitiveResultMap.RESULT_MAP_ID) | |
| 170 | 178 | List<Map<String, Object>> selectMessageTypeDistribution(@Param("beginTime") LocalDateTime beginTime, |
| 171 | 179 | @Param("endTime") LocalDateTime endTime); |
| 172 | 180 | |
| ... | ... | @@ -184,6 +192,7 @@ public interface ImStatisticsManagerMapper { |
| 184 | 192 | " WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION + |
| 185 | 193 | " AND send_time >= #{beginTime} AND send_time < #{endTime}" + |
| 186 | 194 | ") t GROUP BY user_id ORDER BY messageCount DESC LIMIT #{limit}") |
| 195 | + @ResultMap(CaseInsensitiveResultMap.RESULT_MAP_ID) | |
| 187 | 196 | List<Map<String, Object>> selectTopSenders(@Param("beginTime") LocalDateTime beginTime, |
| 188 | 197 | @Param("endTime") LocalDateTime endTime, |
| 189 | 198 | @Param("limit") int limit); | ... | ... |
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/statistics/ImStatisticsManagerMapperTest.java
| ... | ... | @@ -228,6 +228,9 @@ public class ImStatisticsManagerMapperTest extends BaseDbUnitTest { |
| 228 | 228 | assertEquals(3L, ((Number) tops.get(0).get("messageCount")).longValue()); |
| 229 | 229 | assertEquals(2L, ((Number) tops.get(1).get("userId")).longValue()); |
| 230 | 230 | assertEquals(2L, ((Number) tops.get(1).get("messageCount")).longValue()); |
| 231 | + // 断言:key 大小写不敏感,兼容 PostgreSQL 小写、Oracle 大写的列标签 | |
| 232 | + assertEquals(1L, ((Number) tops.get(0).get("userid")).longValue()); | |
| 233 | + assertEquals(3L, ((Number) tops.get(0).get("MESSAGECOUNT")).longValue()); | |
| 231 | 234 | } |
| 232 | 235 | |
| 233 | 236 | // ========== 工具方法 ========== | ... | ... |
yudao-module-mes/src/main/resources/mapper/home/MesHomeStatisticsMapper.xml
| ... | ... | @@ -3,7 +3,7 @@ |
| 3 | 3 | <mapper namespace="cn.iocoder.yudao.module.mes.dal.mysql.home.MesHomeStatisticsMapper"> |
| 4 | 4 | |
| 5 | 5 | <!-- 按状态统计工单数量 --> |
| 6 | - <select id="selectWorkOrderCountGroupByStatus" resultType="java.util.Map"> | |
| 6 | + <select id="selectWorkOrderCountGroupByStatus" resultType="cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap"> | |
| 7 | 7 | SELECT status, COUNT(*) AS count |
| 8 | 8 | FROM mes_pro_work_order |
| 9 | 9 | WHERE deleted = 0 |
| ... | ... | @@ -11,7 +11,7 @@ |
| 11 | 11 | </select> |
| 12 | 12 | |
| 13 | 13 | <!-- 统计报工产量合计 --> |
| 14 | - <select id="selectFeedbackSummary" resultType="java.util.Map"> | |
| 14 | + <select id="selectFeedbackSummary" resultType="cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap"> | |
| 15 | 15 | SELECT IFNULL(SUM(feedback_quantity), 0) AS feedbackQuantity, |
| 16 | 16 | IFNULL(SUM(qualified_quantity), 0) AS qualifiedQuantity, |
| 17 | 17 | IFNULL(SUM(unqualified_quantity), 0) AS unqualifiedQuantity |
| ... | ... | @@ -25,7 +25,7 @@ |
| 25 | 25 | </select> |
| 26 | 26 | |
| 27 | 27 | <!-- 按设备状态统计设备台数 --> |
| 28 | - <select id="selectMachineryCountGroupByStatus" resultType="java.util.Map"> | |
| 28 | + <select id="selectMachineryCountGroupByStatus" resultType="cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap"> | |
| 29 | 29 | SELECT status, COUNT(*) AS count |
| 30 | 30 | FROM mes_dv_machinery |
| 31 | 31 | WHERE deleted = 0 |
| ... | ... | @@ -51,7 +51,7 @@ |
| 51 | 51 | </select> |
| 52 | 52 | |
| 53 | 53 | <!-- 按天聚合报工产量 --> |
| 54 | - <select id="selectDailyFeedbackTrend" resultType="java.util.Map"> | |
| 54 | + <select id="selectDailyFeedbackTrend" resultType="cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap"> | |
| 55 | 55 | SELECT DATE_FORMAT(feedback_time, '%Y-%m-%d') AS date, |
| 56 | 56 | IFNULL(SUM(feedback_quantity), 0) AS quantity, |
| 57 | 57 | IFNULL(SUM(qualified_quantity), 0) AS qualifiedQuantity, | ... | ... |
yudao-module-wms/src/main/resources/mapper/home/WmsHomeStatisticsMapper.xml
| ... | ... | @@ -3,7 +3,7 @@ |
| 3 | 3 | <mapper namespace="cn.iocoder.yudao.module.wms.dal.mysql.home.WmsHomeStatisticsMapper"> |
| 4 | 4 | |
| 5 | 5 | <!-- 按单据类型和状态统计单据数量 --> |
| 6 | - <select id="selectOrderCountGroupByTypeAndStatus" resultType="java.util.Map"> | |
| 6 | + <select id="selectOrderCountGroupByTypeAndStatus" resultType="cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap"> | |
| 7 | 7 | SELECT orderType, status, SUM(count) AS count |
| 8 | 8 | FROM ( |
| 9 | 9 | SELECT ${@cn.iocoder.yudao.module.wms.enums.order.WmsOrderTypeEnum@RECEIPT.type} AS orderType, |
| ... | ... | @@ -51,7 +51,7 @@ |
| 51 | 51 | </select> |
| 52 | 52 | |
| 53 | 53 | <!-- 按天聚合单据数量 --> |
| 54 | - <select id="selectDailyOrderTrend" resultType="java.util.Map"> | |
| 54 | + <select id="selectDailyOrderTrend" resultType="cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap"> | |
| 55 | 55 | SELECT date, orderType, SUM(count) AS count |
| 56 | 56 | FROM ( |
| 57 | 57 | SELECT DATE_FORMAT(order_time, '%Y-%m-%d') AS date, |
| ... | ... | @@ -117,7 +117,7 @@ |
| 117 | 117 | </select> |
| 118 | 118 | |
| 119 | 119 | <!-- 按商品统计库存数量排行 --> |
| 120 | - <select id="selectInventoryItemRank" resultType="java.util.Map"> | |
| 120 | + <select id="selectInventoryItemRank" resultType="cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap"> | |
| 121 | 121 | SELECT item.id AS id, |
| 122 | 122 | item.name AS name, |
| 123 | 123 | IFNULL(SUM(inventory.quantity), 0) AS quantity |
| ... | ... | @@ -135,7 +135,7 @@ |
| 135 | 135 | </select> |
| 136 | 136 | |
| 137 | 137 | <!-- 按仓库统计库存数量排行 --> |
| 138 | - <select id="selectInventoryWarehouseRank" resultType="java.util.Map"> | |
| 138 | + <select id="selectInventoryWarehouseRank" resultType="cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap"> | |
| 139 | 139 | SELECT warehouse.id AS id, |
| 140 | 140 | warehouse.name AS name, |
| 141 | 141 | IFNULL(SUM(inventory.quantity), 0) AS quantity | ... | ... |