MyBatis中如何用foreach标签映射实体类的List类型参数?
我最近遇到了一个MyBatis的问题,场景是这样的:我有一个Car实体类,里面包含一个List类型的carType属性,代码如下:
public class Car{ private List carType; private String carName; public void setCarType(List carType){ this.carType = carType; } public List getCarType(){ return carType; } }
在调用DAO接口前,我给这个属性赋值(这里要吐槽下自己一开始写的赋值代码有语法错误,已经修正了):
Car car = new Car(); car.setCarType(Arrays.asList(1,2,3)); List<Car> list = carDao.car(car);
然后我想用MyBatis的foreach标签来生成IN条件的SQL,XML配置写的是:
<select id='car' parameterType="com.example.Car"> select car_name, car_type from tb_car where car_type in <foreach item="item" collection="carType" separator="," open="(" close=")" index=""> #{item} </foreach> </select>
本来期望生成的SQL是:
select car_name, car_type from tb_car where car_type in (1, 2, 3)
结果调试的时候直接报错了:
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException: ### Error querying database. Cause: java.lang.IllegalArgumentException: invalid comparison: java.util.ArrayList and java.lang.String ### Cause: java.lang.IllegalArgumentException: invalid comparison: java.util.ArrayList and java.lang.String
我知道用HashMap传参能解决,但就想搞明白怎么在当前基于实体类传参的写法下修复这个问题。
问题根源分析
这个错误的核心是MyBatis在解析未指定泛型的原始List属性时,无法准确识别其类型,内部做类型校验的时候把ArrayList和String搞混了。另外还有几个小细节也踩坑了:
- 实体类里的
carType是无泛型的原始List,MyBatis没法确定它存的是什么类型; - DAO接口的参数没加
@Param注解,MyBatis解析参数的时候可能有歧义; - 最开始的Java赋值代码写错了(
car.setCarType = [1,2,3];),这在Java里根本不合法,还好后来改过来了。
实体类传参的修复方案
方案1:给List加泛型+用@Param明确参数
这是最稳妥的做法,步骤如下:
- 给实体类的
carType添加泛型:
public class Car{ private List<Integer> carType; // 指定泛型为Integer,和传入的数值类型匹配 private String carName; public void setCarType(List<Integer> carType){ this.carType = carType; } public List<Integer> getCarType(){ return carType; } }
- 给DAO接口方法的参数加
@Param注解:
这样MyBatis能明确知道参数的名称,避免解析歧义:
public interface CarDao { List<Car> car(@Param("car") Car car); }
- 调整XML里foreach的collection路径:
因为参数是封装在car对象里的,所以要写成car.carType:
<select id='car' parameterType="com.example.Car" resultType="com.example.Car"> select car_name, car_type from tb_car where car_type in <foreach item="item" collection="car.carType" separator="," open="(" close=")" index=""> #{item} </foreach> </select>
方案2:不加@Param,直接用实体类属性名
如果不想加@Param注解,MyBatis会默认把实体类作为唯一的参数对象,此时collection直接写属性名carType就行,但还是建议给List加泛型,避免类型识别问题:
DAO接口方法保持原样:
List<Car> car(Car car);
XML调整后:
<select id='car' parameterType="com.example.Car" resultType="com.example.Car"> select car_name, car_type from tb_car where car_type in <foreach item="item" collection="carType" separator="," open="(" close=")" index=""> #{item} </foreach> </select>
额外注意事项
- 如果你用的是MyBatis 3.4.0以上的版本,对原始类型集合的支持有所优化,但还是推荐用带泛型的集合,从根源上避免类型问题;
- 要确保数据库里
tb_car表的car_type字段类型和你传入的Integer类型匹配,不然可能会出现类型转换的新问题。
内容的提问来源于stack exchange,提问作者gnalog

