3.4 使用foreach元素完成复杂查询
在实际开发中,有时会遇到这样的情况:假设在一个用户表中有1000条数据,现在需要将id值小于100的用户信息全部查询出来,这要怎么做呢?有人会说,“我可以一条一条查出来”,那如果查询200、300甚至更多时也要一条一条查吗?这显然是不可取的。有的人会想到,可以在Java方法中使用循环,将查询方法放在循环语句中,然后通过条件循环的方式查询出所需的数据。这种查询方式虽然可行,但每执行一次循环语句都需要向数据库发送一条查询SQL,其查询效率是非常低的。那么还有其他更好的方法吗?能不能通过SQL语句来执行这种查询呢?
其实,MyBatis框架中已经提供了一种用于数组和集合循环遍历的方式,那就是使用<foreach>元素,完全可以解决上述类似的问题。
<foreach>元素通常在构建in条件语句时使用,其使用方式如下。
在上述代码中,使用了<foreach>元素对传入的集合进行遍历并进行动态SQL组装。关于<foreach>元素中基本属性的描述具体如下。
(1)item:表示集合中每一个元素进行迭代时的别名(如“roleIds”)。
(2)index:指定一个名称,用于表示在迭代过程中每次迭代的位置(此处省略,未指定)。
(3)open:表示该语句以什么开始(既然是in条件语句,必然以“(”开始)。
(4)separator:表示每次进行迭代时以什么符号作为分隔符(既然是in条件语句,必然是以“,”作为分隔符)。
(5)close:表示该语句以什么结束(既然是in条件语句,必然是以“)”结束)。
(6)collection:表示最关键且最容易出错的属性,需格外注意。该属性必须指定,不同情况下该属性的值是不一样的,它主要有3种情况。
①若入参为单参数且参数类型是一个List时,collection属性值为list。
②若入参为单参数且参数类型是一个数组时,collection属性值为array(此处传入参数lnteger[] rolelds为数组类型,故collection属性值设为“array”)。
③若传入参数为多参数,就需要将其封装为一个Map进行处理。
可以将任何可迭代对象(如列表、集合等)和任何字典或数组对象传递给<foreach>作为集合参数。当使用可迭代对象或数组时,index指当前迭代的次数,item的值是本次迭代获取的元素。当使用字典(或Map.Entry对象的集合)时,index是键,item是值。
在前两个小节中,已经学习使用动态SQL的元素if、where、trim来处理一些简单的查询操作,那么对于一些SQL语句中含有in条件需要迭代条件集合来生成的情况,就可使用foreach标签来实现SQL条件的迭代。
3.4.1 MyBatis框架入参为数组类型的foreach迭代
foreach的基本用法和属性,foreach主要用在构建in条件语句中,可以在SQL语句中迭代一个集合。它的属性主要有item、index、collection、separator、close、open。下面通过一个根据指定角色列表来获取用户信息列表的示例进行详细介绍。
先修改UserMapper.java,以增加接口方法,即根据传入的用户角色列表获取该角色列表下的用户信息,参数为角色列表(roleIds),该参数类型为整型数组。见示例21。
【示例21】 UserMapper.java
根据需求分析,SQL语句应该为select * from tb_user where userRole in (角色1,角色2,角色3,...),in为角色列表。修改UserMapper.xml,以增加相应的getUserByArray,见示例22。
【示例22】 UserMapper.xml
对于SQL条件循环(in语句),需要使用foreach标签。最后修改测试类,以增加测试方法,见示例23。
【示例23】 UserMapperTest.java
在上述代码中,封装角色列表数组入参,并运行测试方法,输出的结果正确。
在上述示例中,发现UserMapper.xml的select: getUserByRoleId_foreach_array中并没有指定parameterType,这样也是没有问题的。因为配置文件中的parameterType是可以不配置的,MyBatis会自动把它封装成一个Map进行传入,但是也需要注意:若入参为collection时,不能直接传入collection对象,需要先将其转换为List或者数组才能传入,具体原因可参看MyBatis源码的相关内容。
3.4.2 MyBatis框架入参为List类型的foreach迭代
在上个示例中,实现通过指定的角色列表获得相应的用户信息列表,其方法参数为一个数组,现在更改参数类型,通过传入一个List实例来实现同样的需求。
修改UserMapper.java,以增加接口方法(根据传入的用户角色列表获取该角色列表下的用户信息),参数为角色列表(roleIds),该参数类型为List。见示例24。
【示例24】 UserMapper.java
修改UserMapper.xml,以增加相应的getUserByList,见示例25。
【示例25】 UserMapper.xml
在上述代码中,foreach的大部分属性设置跟示例24基本一致,由于角色列表入参使用的是List,故collection属性值为“list”。最后修改测试类,以增加测试方法testGetUserByList(),见示例26。
【示例26】 UserMapperTest.java
该测试方法中,把参数角色列表roleIds封装成List进行入参即可。
测试运行后,其结果正确。
foreach非常强大,允许指定一个集合,并可指定开始和结束的字符,也可加入一个分隔符到迭代器中,并能够智能处理该分隔符,且不会出现多余的分隔符。
3.4.3 技能训练1
上机练习6 获取指定供应商列表下的订单列表(foreach)
需求说明
(1)指定供应商列表(1~n个),获取这些供应商的订单列表信息。
(2)要求使用foreach实现,参数类型为数组。
(3)完成之后,把参数类型改为List。
(1)在BillMapper.java中增加接口方法,该方法入参为供应商列表,类型为数组。
(2)在BillMapper.xml中增加查询SQL语句,使用动态SQL的foreach,注意collection属性的设置为array。
(3)增加测试方法,并进行相应的测试。
(4)完成之后,修改入参类型为List,且修改SQL语句相应的collection属性为list,并增加测试方法进行相应的测试。
3.4.4 MyBatis框架入参为Map类型的foreach迭代
在示例25和示例26中,MyBatis框架入参均为一个参数,若有多个参数入参该如何处理呢?如示例26中需求更改为增加一个参数gender,要求查询指定性别和用户角色列表下所有用户的信息列表。
除了使用介绍过的@Param注解,还可以按照介绍collection属性时,提过的第三种情况:若入参为多个参数时,就需要把它们封装为一个Map进行处理。此处就可以采用这种处理方式来解决此需求。
先修改UserMapper.java,以增加接口方法,即根据传入的用户角色列表和性别获取相应的用户信息。见示例27。
【示例27】 UserMapper.java
修改UserMapper.xml,以增加相应的getUserByMap,见示例28。
【示例28】 UserMapper.xml
在测试方法中,把用户角色列表(roleList)和性别(gender)两个参数封装成一个Map(conditionMap)进行方法入参,见示例29。
【示例29】 UserMapperTest.java
在上述代码中,由于入参为Map,那么在SQL语句中就需根据key分别获得相应的value值,如SQL语句中#{gender}获取的是Map中key为“gender”的性别条件,而collection: "rolelds"获取的是Map中key为“rolelds”角色id的集合。
最后完成测试方法并运行测试,其正确结果如下:
通过对foreach标签的collection属性学习,发现不管传入的是单参数还是多参数,都可以得到有效解决。若单参数入参时,是否可以封装成Map进行入参呢?答案是肯定的,单参数也可以封装成Map进行入参。实际上,MyBatis在进行参数入参时,都会把它封装成一个Map,而Map的key是参数名,对应的参数值就是Map的value。若参数为集合时,Map的key会根据传入的是List还是数组对象,相应地指定为“list”或者“array”。现在就更改之前的演示示例,即根据用户角色列表,获取该角色列表下用户列表的信息,此处参数不使用List或者数组,直接封装成Map来实现。
修改UserMapper.java,以增加接口方法,见示例30。
【示例30】 UserMapper.java
修改UserMapper.xml,以增加相应的getUserByRMap,见示例31。
【示例31】 UserMapper.xml
在以下代码中,把用户角色列表(roleList)这个参数封装成Map(roleMap)进行方法入参。这样的好处是可以自由指定Map的key,此处指定roleMap的key为rKey,见示例32。
【示例32】 UserMapperTest.java
在上述代码中,注意collection的属性值不再是list,而是设置roleMap的key,即rKey,最后增加测试方法进行测试,其结果如下:
测试运行后,其结果正确。
小结:
(1)MyBatis框架接收的参数类型:基本类型、对象、List、数组、Map。
(2)无论MyBatis框架的入参是哪种参数类型都会将参数放在一个Map中,对于单参入参的情况如下。
①若入参为基本类型:变量名作为key,变量值为value,此时生成的Map只有一个。
②若入参为对象:对象的属性名作为key,属性值为value。
③若入参为List:默认“list”作为key,该List即为value。
④若入参为数组:默认“array”作为key,该数组即为value。
⑤若入参为Map:键值不变。
3.4.5 技能训练2
上机练习7 获取多参数的订单列表(foreach)
需求说明
根据订单编码(模糊查询)和指定的供应商列表(1~n个),获取相应的订单列表信息。
多参数:封装成Map入参。
在使用<foreach>元素时最关键也是最容易出错的就是collection属性。该属性是必须指定的,而且在不同情况下,该属性的值是不一样的。主要有以下3种情况。
(1)如果传入的是单参数且参数类型是一个数组或者List时,collection属性值分别为array和list(或collection)。
(2)如果传入的参数是多个时,就需要把它们封装成一个Map了。当然单参数也可以封装成Map集合,这时collection属性值就为Map的键。
(3)如果传入的参数是POJO包装类时,collection属性值就为该包装类中需要进行遍历的数组或集合的属性名。
所以在设置collection属性值时,必须按照实际情况配置,否则程序就会出现异常,如将上述<foreach>元素中collection的属性值设置为array时,则程序执行后将出现异常。