Mybatis foreach标签使用不当导致异常的原因浅析
发布时间 - 2026-01-10 22:11:37 点击率:次异常产生场景及异常信息

上周,由于在Mybatis的Mapper接口方法中使用实现了Map.Entry接口的泛型类,同时此方法对应的sql语句也使用了foreach标签,导致出现了异常。如下为异常信息:
org.apache.ibatis.exceptions.PersistenceException: ### Error updating database. Cause: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'key' in 'class java.lang.Integer' ### The error may involve org.guojing.test.spring.server.GoodsRoomnight30daysMapper.insertBatch-Inline ### The error occurred while setting parameters ### SQL: REPLACE INTO goods_roomnight_30days (goods_id, checkin_room_night_30days) VALUES (?, ?) , (?, ?), (?, ?), (?, ?) ### Cause: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'key' in 'class java.lang.Integer' at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30) at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:200) at org.apache.ibatis.session.defaults.DefaultSqlSession.insert(DefaultSqlSession.java:185) at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:57) at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:53) at com.sun.proxy.$Proxy4.insertBatch(Unknown Source) at org.guojing.test.spring.server.GoodsRoomnight30daysTest.test(GoodsRoomnight30daysTest.java:47) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) at org.junit.runners.ParentRunner.run(ParentRunner.java:309) at org.junit.runner.JUnitCore.run(JUnitCore.java:160) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:237) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70) Caused by: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'key' in 'class java.lang.Integer' at org.apache.ibatis.reflection.Reflector.getGetInvoker(Reflector.java:409) at org.apache.ibatis.reflection.MetaClass.getGetInvoker(MetaClass.java:164) at org.apache.ibatis.reflection.wrapper.BeanWrapper.getBeanProperty(BeanWrapper.java:162) at org.apache.ibatis.reflection.wrapper.BeanWrapper.get(BeanWrapper.java:49) at org.apache.ibatis.reflection.MetaObject.getValue(MetaObject.java:122) at org.apache.ibatis.reflection.MetaObject.getValue(MetaObject.java:119) at org.apache.ibatis.mapping.BoundSql.getAdditionalParameter(BoundSql.java:75) at org.apache.ibatis.scripting.defaults.DefaultParameterHandler.setParameters(DefaultParameterHandler.java:72) at org.apache.ibatis.executor.statement.PreparedStatementHandler.parameterize(PreparedStatementHandler.java:93) at org.apache.ibatis.executor.statement.RoutingStatementHandler.parameterize(RoutingStatementHandler.java:64) at org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor.java:86) at org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor.java:49) at org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor.java:117) at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:198) ... 29 more
由于本人对Mybatis还不是非常的了解,导致用了很长的时间去debug找具体的异常原因。
接下来介绍我将重现异常并分析导致异常的原因。
异常重现
为了重现上面的异常,写了demo。相关代码如下:
数据库表结构:
CREATE TABLE `goods_roomnight_30days` ( `goods_id` bigint(20) NOT NULL, `checkin_room_night_30days` int(11) NOT NULL DEFAULT '0' COMMENT '近30天消费间夜', PRIMARY KEY (`goods_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='goods的近30天消费间夜'
KeyValue.java 参数类:
public class KeyValue<K, V> implements Map.Entry<K, V> {
private K key;
private V value;
public KeyValue() {
}
public KeyValue(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
public JSONObject toJSONObject() {
return ReportJSONObject.newObject().append(String.valueOf(key), value);
}
@Override
public String toString() {
return toJSONObject().toJSONString();
}
}
DAO类GoodsRoomnight30daysMapper.java
public interface GoodsRoomnight30daysMapper {
int deleteByExample(GoodsRoomnight30daysExample example);
List<GoodsRoomnight30days> selectByExample(GoodsRoomnight30daysExample example);
<K, V> int insertBatch(List<KeyValue<K, V>> records);
}
mybatis-config.xml文件:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings> <setting name="cacheEnabled" value="false"/> </settings> <!-- 和spring整合后 environments配置将废除,交给spring管理--> <environments default="development"> <environment id="development"> <!-- 使用jdbc事务管理--> <transactionManager type="JDBC" /> <!-- 数据库连接池,整合后一般使用第三方的连接池--> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/hotel_report?characterEncoding=utf-8" /> <property name="username" value="test_user" /> <property name="password" value="user123" /> </dataSource> </environment> </environments> <mappers> <mapper resource="mybatis/GoodsRoomnight30daysMapper.xml"/> </mappers> </configuration>
GoodsRoomnight30daysMapper.xml文件的主要内容:
<insert id="insertBatch" parameterType="list">
REPLACE INTO goods_roomnight_30days (goods_id, checkin_room_night_30days)
VALUES
<foreach collection="list" index="index" item="item" separator=",">
(#{item.key}, #{item.value})
<!--<choose>-->
<!--<when test="item.value == null">(#{item.key}, 0)</when>-->
<!--<when test="item.value != null">(#{item.key}, #{item.value})</when>-->
<!--</choose>-->
</foreach>
</insert>
以上为重现此异常的主要代码,完整代码可在Github查看:https://github.com/misterzhou/java-demo/tree/master/test-spring/spring-server
重现异常的测试代码 GoodsRoomnight30daysTest.java:
package org.guojing.test.spring.server;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.guojing.spring.commons.KeyValue;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/**
* Created at: 2016-12-24
*
* @author guojing
*/
public class GoodsRoomnight30daysTest {
SqlSessionFactory sqlSessionFactory;
SqlSession sqlSession;
GoodsRoomnight30daysMapper goodsRoomnight30daysMapper;
@Before
public void init() throws IOException {
String resource = "mybatis/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
sqlSession = sqlSessionFactory.openSession(true);
goodsRoomnight30daysMapper = sqlSession.getMapper(GoodsRoomnight30daysMapper.class);
}
@Test
public void test() {
List<KeyValue<Long, Integer>> records = new ArrayList<>();
records.add(new KeyValue<Long, Integer>(1725L, 5));
records.add(new KeyValue<Long, Integer>(1728L, 3));
records.add(new KeyValue<Long, Integer>(1730L, null));
records.add(new KeyValue<Long, Integer>(1758L, null));
int deleted = goodsRoomnight30daysMapper.deleteByExample(new GoodsRoomnight30daysExample());
System.out.println("----- deleted row size: " + deleted);
int row = goodsRoomnight30daysMapper.insertBatch(records);
System.out.println("----- affected row: " + row);
List<GoodsRoomnight30days> result = goodsRoomnight30daysMapper.selectByExample(new GoodsRoomnight30daysExample());
for (GoodsRoomnight30days item : result) {
System.out.println(item.toString());
}
}
@After
public void after() {
if (sqlSession != null) {
sqlSession.close();
}
}
}
卖个关子,大家先不要往下看,想想导致异常的原因(熟练使用foreach标签的同学应该能看出端倪)。
查找异常过程及异常分析
在项目中,由于DAO方法的参数类和返回结果类经常会包含一个键和键对应的值,为了避免重复定义类,我定义了一个实现了Map.Entry接口的KeyValue泛型类,具体请查看上节。
GoodsRoomnight30daysMapper.insertBatch()方法就使用了此泛型类。在运行之后就抛出了本文开始提到的异常信息。
看到异常信息后,就把重点放到了是不是Mybatis对泛型的支持不够好上。问了下同事(@胜男),同事在自己的机器上试了下,发现没有异常。这就奇怪了。仔细看了下代码,发现不同之处就是我的KeyValue泛型类实现了Map.Entry接口。 此时还不知道mybatis官网对于foreach标签的说明:
可以将任何可迭代对象(如列表、集合等)和任何的字典或者数组对象传递给foreach作为集合参数。当使用可迭代对象或者数组时,index是当前迭代的次数,item的值是本次迭代获取的元素。当使用字典(或者Map.Entry对象的集合)时,index是键,item是值。
接下来就是通过debug看看,异常产生的具体原因。于是就先用实现了Map.Entry接口的KeyValue类的代码进行debug,通过异常日志可以看到异常是在 DefaultSqlSession.java:200 行抛出,那么将断点打到 DefaultSqlSession.java:197行,然后一步步向下执行,当执行到 ForEachSqlNode.java:73行时,终于焕然大悟。先看下debug调用链图:
再看看具体的代码 ForEachSqlNode.java:73(此类就是foreach标签对象的Node类):
此时具体的异常原因就很明显了,此处的 o 对象的所属的类就是KeyValue类,由于KeyValue类实现了Map.Entry接口,所以 o instance Map.Entry 为true,mybatis就把key值赋给了foreach的index属性,而把value值赋给了item属性,此处也就是把值为5的Integer对象赋给了item属性。所以 GoodsRoomnight30daysMapper.xml 中id为 insertBatch 的select标签的item属性对应的对象也就没有 item.key 和 item.value 属性,这就是最终导致异常的原因。
<insert id="insertBatch" parameterType="list">
REPLACE INTO goods_roomnight_30days (goods_id, checkin_room_night_30days)
VALUES
<foreach collection="list" index="index" item="item" separator=",">
(#{item.key}, #{item.value})
</foreach>
</insert>
以上所述是小编给大家介绍的Mybatis foreach标签使用不当导致异常的原因浅析,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!
# mybatis
# foreach
# 解决Mybatis中foreach嵌套使用if标签对象取值的问题
# 深入浅析MyBatis foreach标签
# Mybatis动态SQL foreach标签用法实例
# MyBatis动态SQL foreach标签实现批量插入的方法示例
# mybatis foreach标签的使用详解
# mybatis的foreach标签语法报错的解决
# 实现了
# 用了
# 给了
# 迭代
# 还不
# 就把
# 大悟
# 抛出
# 小编
# 自己的
# 连接池
# 是在
# 看了
# 也就
# 在此
# 这就是
# 这就
# 可在
# 给大家
# 此类
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
大连网站制作费用,大连新青年网站,五年四班里的视频怎样下载啊?
Swift中swift中的switch 语句
网站制作怎么样才能赚钱,用自己的电脑做服务器架设网站有什么利弊,能赚钱吗?
Laravel如何配置和使用缓存?(Redis代码示例)
微信小程序 wx.uploadFile无法上传解决办法
如何在阿里云通过域名搭建网站?
googleplay官方入口在哪里_Google Play官方商店快速入口指南
独立制作一个网站多少钱,建立网站需要花多少钱?
Laravel如何与Vue.js集成_Laravel + Vue前后端分离项目搭建指南
laravel怎么在请求结束后执行任务(Terminable Middleware)_laravel Terminable Middleware请求结束任务执行方法
C++时间戳转换成日期时间的步骤和示例代码
手机网站制作平台,手机靓号代理商怎么制作属于自己的手机靓号网站?
HTML5建模怎么导出为FBX格式_FBX格式兼容性及导出步骤【指南】
Python正则表达式进阶教程_复杂匹配与分组替换解析
Laravel如何正确地在控制器和模型之间分配逻辑_Laravel代码职责分离与架构建议
JavaScript中的标签模板是什么_它如何扩展字符串功能
UC浏览器如何设置启动页 UC浏览器启动页设置方法
北京网页设计制作网站有哪些,继续教育自动播放怎么设置?
Laravel项目结构怎么组织_大型Laravel应用的最佳目录结构实践
百度输入法全感官ai怎么关 百度输入法全感官皮肤关闭
JavaScript 输出显示内容(document.write、alert、innerHTML、console.log)
Win11怎么关闭资讯和兴趣_Windows11任务栏设置隐藏小组件
晋江文学城电脑版官网 晋江文学城网页版直接进入
如何在云虚拟主机上快速搭建个人网站?
Android okhttputils现在进度显示实例代码
手机怎么制作网站教程步骤,手机怎么做自己的网页链接?
实例解析Array和String方法
阿里云网站搭建费用解析:服务器价格与建站成本优化指南
微信小程序 scroll-view组件实现列表页实例代码
php嵌入式断网后怎么恢复_php检测网络重连并恢复硬件控制【操作】
如何用PHP工具快速搭建高效网站?
javascript中闭包概念与用法深入理解
如何在IIS7中新建站点?详细步骤解析
大连 网站制作,大连天途有线官网?
Laravel如何处理跨站请求伪造(CSRF)保护_Laravel表单安全机制与令牌校验
如何彻底删除建站之星生成的Banner?
如何在 React 中条件性地遍历数组并渲染元素
JS中使用new Date(str)创建时间对象不兼容firefox和ie的解决方法(两种)
php中::能调用final静态方法吗_final修饰静态方法调用规则【解答】
如何在阿里云完成域名注册与建站?
Laravel Fortify是什么,和Jetstream有什么关系
如何快速搭建高效服务器建站系统?
HTML5空格和margin有啥区别_空格与外边距的使用场景【说明】
如何在腾讯云免费申请建站?
如何用IIS7快速搭建并优化网站站点?
Claude怎样写结构化提示词_Claude结构化提示词写法【教程】
Laravel的路由模型绑定怎么用_Laravel Route Model Binding简化控制器逻辑
深圳网站制作的公司有哪些,dido官方网站?
如何在IIS中新建站点并配置端口与物理路径?
javascript中数组(Array)对象和字符串(String)对象的常用方法总结

