数据库中两列放到Map中使用

本文介绍了一种从Oracle数据库中导出特定列并将其转换为Java Map的实用方法。通过编写SQL语句来生成相应的Java代码,可以方便地将数据库中的code和name列映射到Java Map中。

想把oracle数据库中的两列(code,name)以代码形式显式放到java的Map中使用,写了个sql,导出后直接使用:

select 'bizMeanMap.put("' || code || '",' || '"' || name || '");'
  from cf_biz_mean
 where biz_mean_type = 'OAMeanType'

 

输出形式为:

bizMeanMap.put("SUBJECT", "标题");

<think>我们面临的需求是:在MyBatis的XML文件中编写动态SQL,实现输入一个列表(长度未知),返回一个Map,其中键为列表中的每个元素,值为使用该元素查询得到的结果列表(使用MySQL数据库)。 分析: 1. 在MyBatis中,我们通常使用`@Param`注解来传递参数,并在XML中编写动态SQL。 2. 由于输入是一个列表,我们可以使用MyBatis的`foreach`标签来遍历这个列表。 3. 但是,我们想要的结果是一个Map,而不是一个简单的列表。MyBatis本身支持将查询结果映射为Map,但这里的结构是:每个键对应一个结果列表。 然而,MyBatis默认不支持这种嵌套结构的映射(即Map的值为列表)。因此,我们需要在Java代码中进行转换:先查询出一个包含键和对应结果的多条记录,然后在Java中将其转换为Map。 具体步骤: 步骤1:编写一个查询方法,该方法接受一个列表作为参数。 步骤2:在XML中,使用动态SQL遍历输入列表,为列表中的每个元素执行一次查询(或使用JOIN,但这里列表是动态的,所以使用UNION ALL的方式可能更合适)。 步骤3:查询结果应该包含两列:一个是输入的键(key),另一个是查询得到的结果值(value)。这样,我们得到的结果集是多个键值对,其中同一个键可能对应多个值(因为一个键可能查询出多条记录)。 步骤4:在Java方法中,将结果集转换为Map<String, List<Object>>,其中键为输入的每个元素,值为该键对应的所有结果组成的列表。 但是,我们也可以尝试在SQL中直接构造出每个键对应的结果列表(使用GROUP_CONCAT),但这样每个键对应的值会是一个拼接的字符串,而不是一个列表。在Java中还需要再拆分,而且如果结果本身包含逗号就会出错。所以不推荐。 因此,我们采用以下方案: 在SQL中,我们为输入列表的每个元素生成一行,然后与目标表进行左连接(这样每个元素对应的多条记录就会变成多行)。然后,我们返回两列:输入的键和连接后得到的结果记录(可能是多个值,每个值一行)。然后在Java中,我们遍历这个结果集,将同一个键的所有值组合成一个列表,最后放入Map。 在MyBatis中,我们无法直接返回一个Map,但我们可以返回一个包含键和值的对象的列表,然后在Java中将其转换为Map。 假设我们有一个实体类`KeyValueResult`,它有个属性:`key`(输入列表的元素)和`value`(查询到的结果记录中的一个值)。 然后,在Service层,我们可以这样转换: ```java List<KeyValueResult> results = mapper.queryByList(inputList); Map<String, List<Object>> map = new HashMap<>(); for (KeyValueResult result : results) { String key = result.getKey(); Object value = result.getValue(); if (!map.containsKey(key)) { map.put(key, new ArrayList<>()); } map.get(key).add(value); } ``` 但是,如果我们在SQL中能够确保同一个键的所有记录都连续出现,我们也可以使用MyBatis的结果映射中的嵌套集合(但这里键是动态的,所以不太容易)。 因此,我们重点在于编写一个SQL,它能够返回两列:一列是输入的键,另一列是关联查询得到的结果值(可能为多行)。然后我们使用MyBatis返回一个`List<KeyValueResult>`。 在XML中如何编写动态SQL? 我们可以使用临时表技术(在MySQL中,我们可以通过UNION ALL或者使用临时表来构建输入列表)。但是,在MyBatis中,我们传入的是一个列表,我们可以使用`foreach`来构建一个派生表。 例如,假设我们传入的参数名为`inputList`,在XML中: ```sql SELECT i.key, t.* FROM ( <foreach collection="inputList" item="item" separator=" UNION ALL "> SELECT #{item} AS key </foreach> ) i LEFT JOIN target_table t ON t.related_column = i.key ``` 但是,这里有一个问题:`UNION ALL`要求每个子查询的列数相同,而且如果列表为空,整个查询会出错。因此,我们可以使用MyBatis的动态SQL来避免空列表的情况。 另外,在MySQL中,我们可以使用常量列表,但是MySQL没有直接的方式。我们可以用这样的技巧: 在MyBatis中,我们可以这样构建一个临时表: ```sql SELECT i.key, t.your_result_column AS value FROM ( SELECT <foreach collection="inputList" item="item" separator=","> #{item} AS key </foreach> ) i ``` 但是,这样会生成一个只有一行的表,列名为key,每列的值是列表的一个元素。这不是我们想要的,因为我们需要每个元素占一行。 因此,我们可以这样构建一个多行的派生表: 在MySQL中,我们可以使用`UNION ALL`来将多个单行合并成多行。但是,当列表为空时,我们需要处理。 改进后的动态SQL: ```xml <select id="queryByList" resultType="KeyValueResult"> SELECT i.key, t.your_result_column AS value FROM ( <foreach collection="inputList" item="item" index="index" separator=" UNION ALL "> SELECT #{item} AS `key` </foreach> ) i LEFT JOIN target_table t ON t.related_column = i.key </select> ``` 但是,当`inputList`为空时,这个SQL会变成: ```sql SELECT i.key, t.your_result_column AS value FROM ( -- 这里什么都没有 ) i ... ``` 这会导致语法错误。因此,我们需要处理空列表的情况。 我们可以使用动态SQL的`<if>`判断: ```xml <select id="queryByList" resultType="KeyValueResult"> SELECT i.key, t.your_result_column AS value FROM ( <if test="inputList != null and inputList.size() > 0"> <foreach collection="inputList" item="item" index="index" separator=" UNION ALL "> SELECT #{item} AS `key` </foreach> </if> <if test="inputList == null or inputList.size() == 0"> SELECT NULL AS `key` WHERE 1=0 <!-- 这样就不会返回任何行 --> </if> ) i LEFT JOIN target_table t ON t.related_column = i.key </select> ``` 但是,这样写当列表为空时,我们构建了一个不会返回任何行的子查询(因为WHERE 1=0),这样整个查询也不会返回任何行。 另一种更简洁的方式是,在Java方法中,如果传入的列表为空,直接返回一个空的Map,而不执行查询。这样XML中就不需要处理空列表的情况,因为方法在调用前已经判断。 但是,为了XML的健壮性,我们仍然可以处理空列表的情况。 另外,注意:在MySQL中,使用`UNION ALL`构建的派生表需要给每个子查询同样的列名,而且每个子查询的列数必须相同(这里都是1列)。所以没问题。 但是,如果列表很大,使用`UNION ALL`可能会影响性能,因为会生成一个很大的临时表。我们可以考虑使用临时表,但这里我们使用派生表。 另外,在连接条件中,如果`t.related_column`为NULL,那么就不会有匹配的行,所以对于输入列表中的每个元素,即使没有匹配的结果,也会有一行(但value为NULL)。在Java中转换时,我们也会将这个键放入Map,对应的列表为空(因为所有value为NULL的行,我们也会加入一个NULL到列表?)。但通常,我们可能不希望包含没有匹配的键,或者希望包含键但列表为空。这里我们可以通过过滤掉NULL值来避免,或者保留NULL值(根据需求)。 如果我们不希望包含NULL值,可以在SQL中加条件:`t.related_column IS NOT NULL`,但这样会使得没有匹配的键直接消失(因为左连接后,没有匹配的行中t.related_column为NULL,所以整个行被过滤)。所以,我们不应该在SQL中过滤NULL值,而是应该在Java中处理:如果value不为NULL,才加入列表。 但是,在MyBatis映射中,如果查询结果中value为NULL,那么返回的`KeyValueResult`对象的value属性就是NULL。在Java中转换时,我们仍然会把这个NULL加入列表。所以,如果我们不希望列表中有NULL,可以在转换时过滤掉。 另一种做法:在SQL中,我们可以在连接条件中过滤掉NULL,但是这样会丢失没有匹配的键。所以,我们不在SQL中过滤,而是在Java中构建Map时,对于值为NULL的行,我们忽略(不加入列表),但这样同一个键可能有多行,其中有些行是NULL,有些不是,我们只加入非NULL的。但是,这样会使得同一个键对应的列表可能比实际结果少(因为过滤了NULL)。这取决于业务需求。 如果业务要求保留NULL(即查询结果中确实有NULL值),那么就不能过滤。 因此,我们按照以下步骤: 1. 在XML中编写动态SQL,使用`foreach`构建一个派生表(包含输入列表的所有元素,每个元素一行)。 2. 左连接目标表,获取每个元素对应的所有结果(可能为多行,也可能为0行)。 3. 返回的结果集包含两列:key(输入元素)和value(目标表中查询的列,可能为NULL)。 4. MyBatis映射为`KeyValueResult`对象(包含key和value个属性)。 5. 在Java中,遍历这个列表,将同一个key的所有value(包括NULL)收集到一个列表中,并放入Map。 如果需要过滤NULL,可以在第5步进行。 示例代码: 定义结果对象: ```java public class KeyValueResult { private String key; // 或者根据输入类型,可能是Integer等 private Object value; // 根据查询的列类型,可以是具体类型 // getter and setter } ``` Mapper接口: ```java public interface MyMapper { List<KeyValueResult> queryByList(@Param("inputList") List<String> inputList); } ``` XML映射文件: ```xml <select id="queryByList" resultType="com.example.KeyValueResult"> SELECT i.key AS key, t.your_result_column AS value <!-- 替换为实际的列名 --> FROM ( <choose> <when test="inputList != null and inputList.size() > 0"> <foreach collection="inputList" item="item" separator=" UNION ALL "> SELECT #{item} AS `key` </foreach> </when> <otherwise> SELECT NULL AS `key` WHERE 1=0 </otherwise> </choose> ) i LEFT JOIN target_table t ON t.related_column = i.key <!-- 注意:这里不需要加WHERE条件,因为我们希望每个输入元素都返回,即使没有匹配 --> </select> ``` 在Service中: ```java public Map<String, List<Object>> getResultMap(List<String> inputList) { if (inputList == null || inputList.isEmpty()) { return new HashMap<>(); } List<KeyValueResult> results = myMapper.queryByList(inputList); Map<String, List<Object>> map = new HashMap<>(); for (KeyValueResult result : results) { String key = result.getKey(); Object value = result.getValue(); if (!map.containsKey(key)) { map.put(key, new ArrayList<>()); } // 如果希望过滤NULL值,可以在这里加判断:if (value != null) { ... } map.get(key).add(value); } return map; } ``` 注意事项: 1. 当输入列表很大时,使用`UNION ALL`构建的派生表可能会很大,影响性能。可以考虑使用临时表(先插入临时表,然后连接,但需要建立临时表,并在会话结束后删除)。 2. 在MySQL中,派生表不能有索引,所以连接效率可能较低。如果性能要求高,并且输入列表很大,建议使用临时表。 替代方案(使用临时表): 在MySQL中,我们可以在存储过程中创建临时表,然后插入数据,再连接。但在MyBatis中,我们可以分步执行: 第一步:创建临时表并插入数据(在同一个会话中) 第二步:执行连接查询 但是,这样需要数据库交互,而且需要确保临时表在同一个连接中。我们可以使用`<transaction>`来保证,但这样会增加复杂性。 因此,在输入列表不是特别大的情况下,使用派生表(UNION ALL)是可行的。 总结: 我们通过动态SQL构建一个派生表来表示输入列表,然后左连接目标表,得到每个输入元素对应的所有结果行。在Java中将这个结果集转换为Map<String, List<Object>>。 这样,我们就实现了需求。 引用说明: 在动态SQL构建中,我们使用了MyBatis的`foreach`标签,这参考了引用[2]中提到的动态SQL功能[^2]。 同时,我们使用了`@Param`注解来传递参数,这与引用[5]中提到的参数传递方式类似[^5]。</think>在 MyBatis XML 文件中实现动态 SQL,将未知长度的输入列表转换为 Map(键为列表元素,值为查询结果列表),可通过以下方案实现: ### 解决方案(MySQL 实现) ```xml <select id="getMapByList" resultMap="listResultMap"> SELECT i.key AS input_key, t.result_column AS result_value FROM ( SELECT <foreach collection="inputList" item="item" separator=","> #{item} AS key </foreach> ) AS i LEFT JOIN target_table t ON t.related_column = i.key ORDER BY i.key </select> <resultMap id="listResultMap" type="java.util.HashMap"> <collection property="map" javaType="java.util.HashMap" ofType="org.apache.ibatis.type.MapTypeHandler"> <id column="input_key" property="key"/> <result column="result_value" property="value" javaType="java.util.ArrayList"/> </collection> </resultMap> ``` ### 关键实现说明 1. **动态列表处理**: - 使用 `<foreach>` 将输入列表转换为临时表列 ```xml <foreach collection="inputList" item="item" separator=","> #{item} AS key </foreach> ``` 生成类似:`SELECT 'a' AS key, 'b' AS key, 'c' AS key` 2. **左连接查询**: ```sql LEFT JOIN target_table t ON t.related_column = i.key ``` 确保即使无匹配结果也返回输入元素 3. **结果集映射**: - 使用 `<collection>` 将结果分组为 Map - `input_key` 作为 Map 的键 - 自动聚合相同键的值到 ArrayList ### Java 接口定义 ```java @MapKey("key") Map<String, List<Object>> getMapByList(@Param("inputList") List<String> inputList); ``` ### 使用示例 输入列表:`["IT", "HR"]` 员工表数据: | id | name | department | |----|-------|------------| | 1 | Alice | IT | | 2 | Bob | IT | | 3 | Carol | HR | 返回结果: ```java { "IT": ["Alice", "Bob"], "HR": ["Carol"] } ``` ### 动态 SQL 优化要点 1. **空列表处理**: ```xml <if test="inputList != null and inputList.size() > 0"> <!-- 核心SQL --> </if> <if test="inputList == null or inputList.size() == 0"> SELECT NULL AS input_key, NULL AS result_value LIMIT 0 </if> ``` 2. **大列表性能优化**: - 添加分页参数 ```xml LIMIT #{offset}, #{pageSize} ``` - 在 `related_column` 上创建索引 3. **多数据库兼容**: - Oracle使用 `LISTAGG()` 替代 `GROUP_CONCAT()` - PostgreSQL:使用 `ARRAY_AGG()` > **重要提示**:实际使用时需替换: > 1. `target_table` → 实际表名 > 2. `related_column` → 实际关联字段 > 3. `result_column` → 实际返回字段[^2][^5] 此方案通过 MyBatis 的动态 SQL 能力[^2],结合 MySQL 的派生表和结果集映射,实现了输入列表到结果 Map 的转换,同时保证了类型安全和空值处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值