当前位置: 移动技术网 > IT编程>开发语言>Java > Mybatis foreach标签使用不当导致异常的原因浅析

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

2019年07月22日  | 移动技术网IT编程  | 我要评论

斯琴高娃年龄,陈庆霞事件,罗金宝传奇

异常产生场景及异常信息

上周,由于在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查看:

重现异常的测试代码 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标签使用不当导致异常的原因浅析,希望对大家有所帮助

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网