全网整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:400-708-3566

Mybatis foreach标签使用不当导致异常的原因浅析

异常产生场景及异常信息

上周,由于在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标签语法报错的解决  # 实现了  # 用了  # 给了  # 迭代  # 还不  # 就把  # 大悟  # 抛出  # 小编  # 自己的  # 连接池  # 是在  # 看了  # 也就  # 在此  # 这就是  # 这就  # 可在  # 给大家  # 此类 


相关文章: 建站之星代理如何优化在线客服效率?  如何高效完成自助建站业务培训?  江苏网站制作公司有哪些,江苏书法考级官方网站?  公司网站制作需要多少钱,找人做公司网站需要多少钱?  极客网站有哪些,DoNews、36氪、爱范儿、虎嗅、雷锋网、极客公园这些互联网媒体网站有什么差异?  香港服务器部署网站为何提示未备案?  如何彻底删除建站之星生成的Banner?  黑客入侵网站服务器的常见手法有哪些?  常州企业建站如何选择最佳模板?  制作网站的基本流程,设计网站的软件是什么?  建站之星手机一键生成:多端自适应+小程序开发快速建站指南  独立制作一个网站多少钱,建立网站需要花多少钱?  常州企业网站制作公司,全国继续教育网怎么登录?  大连网站设计制作招聘信息,大连投诉网站有哪些?  公司门户网站制作流程,华为官网怎么做?  如何在服务器上配置二级域名建站?  武清网站制作公司,天津武清个人营业执照注销查询系统网站?  建站之星后台管理如何实现高效配置?  代购小票制作网站有哪些,购物小票的简要说明?  网站制作中优化长尾关键字挖掘的技巧,建一个视频网站需要多少钱?  建站主机是否等同于虚拟主机?  C#如何使用XPathNavigator高效查询XML  代刷网站制作软件,别人代刷火车票靠谱吗?  建站之星五站合一营销型网站搭建攻略,流量入口全覆盖优化指南  如何选择网络建站服务器?高效建站必看指南  如何选择CMS系统实现快速建站与SEO优化?  教程网站设计制作软件,怎么创建自己的一个网站?  详解一款开源免费的.NET文档操作组件DocX(.NET组件介绍之一)  沈阳制作网站公司排名,沈阳装饰协会官方网站?  模具网站制作流程,如何找模具客户?  怎么制作一个起泡网,水泡粪全漏粪育肥舍冬季氨气超过25ppm,可以有哪些措施降低舍内氨气水平?  宝塔建站后网页无法访问如何解决?  c++怎么编写动态链接库dll_c++ __declspec(dllexport)导出与调用【方法】  宝华建站服务条款解析:五站合一功能与SEO优化设置指南  如何制作算命网站,怎么注册算命网站?  子杰智能建站系统|零代码开发与AI生成SEO优化指南  GML (Geography Markup Language)是什么,它如何用XML来表示地理空间信息?  如何选择靠谱的建站公司加盟品牌?  宝塔建站教程:一键部署配置流程与SEO优化实战指南  南京做网站制作公司,南京哈发网络有限公司,公司怎么样,做网页美工DIV+CSS待遇怎么样?  ,网站推广常用方法?  如何通过VPS搭建网站快速盈利?  Android使用GridView实现日历的简单功能  如何配置WinSCP新建站点的密钥验证步骤?  建设网站制作价格,怎样建立自己的公司网站?  太平洋网站制作公司,网络用语太平洋是什么意思?  如何选购建站域名与空间?自助平台全解析  Dapper的Execute方法的返回值是什么意思 Dapper Execute返回值详解  如何在宝塔面板中修改默认建站目录?  如何选择高效便捷的WAP商城建站系统? 

您的项目需求

*请认真填写需求信息,我们会在24小时内与您取得联系。