Mybatis foreach 라벨 의 부적 절 한 사용 으로 인 한 이상 원인 에 대한 분석

13712 단어 mybatisforeach
이상 발생 필드 및 이상 정보
지난주 에 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 류 GoodsRoomnight 30days Mapper.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>
GoodsRoomnight 30days Mapper.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
이상 한 테스트 코드 재현 GoodsRoomnight 30days Test.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();
 }
 }
}
뜸 을 들 여 다 보지 말고 이상 한 원인 을 생각해 보 세 요.
이상 과정 및 이상 분석 찾기
프로젝트 에서 DAO 방법의 매개 변수 클래스 와 반환 결과 클래스 는 키 와 키 에 대응 하 는 값 을 자주 포함 하기 때문에 중복 정의 클래스 를 피하 기 위해 저 는 Map.Entry 인 터 페 이 스 를 실현 하 는 KeyValue 범 형 클래스 를 정 의 했 습 니 다.구체 적 으로 상단 을 보십시오.
4.567914.방법 은 이 범 형 류 를 사용 했다.운행 후에 본문 에서 언급 하기 시작 한 이상 한 정 보 를 던 졌 다.
이상 한 정 보 를 보고 Mybatis 가 팬 형 에 대한 지원 이 좋 지 않 은 지 에 중점 을 두 었 다.동료(@승 남)에 게 물 어 보 니 동료 가 자신의 기계 에서 시험 해 보 니 이상 이 없 었 다.이상 하 네.코드 를 자세히 살 펴 보 니 다른 점 은 바로 나의 KeyValue 범 형 류 가 Map.Entry 인 터 페 이 스 를 실현 한 것 이다.이 때 는 my batis 홈 페이지 에서 foreach 태그 에 대한 설명 을 모 릅 니 다.
모든 교체 가능 한 대상(예 를 들 어 목록,집합 등)과 그 어떠한 사전 이나 배열 대상 을 foreach 에 집합 매개 변수 로 전달 할 수 있 습 니 다.교체 가능 한 대상 이나 배열 을 사용 할 때 index 는 현재 교체 횟수 이 고 item 의 값 은 이번 교체 에서 얻 은 요소 입 니 다.사전(또는 Map.Entry 대상 의 집합)을 사용 할 때 index 는 키 이 고 item 은 값 입 니 다.
다음은 debug 를 통 해 이상 이 발생 하 는 구체 적 인 원인 을 살 펴 보 자.그래서 맵.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 이 고 my batis 는 key 값 을 foreach 의 index 속성 에 부 여 했 습 니 다.value 값 을 item 속성 에 부 여 했 습 니 다.여기 서 값 이 5 인 Integer 대상 을 item 속성 에 부 여 했 습 니 다.그래서 GoodsRoomnight 30days Mapper.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 라벨 의 사용 이 부적 절하 여 이상 을 초래 한 원인 에 대한 분석 입 니 다.여러분 께 도움 이 되 기 를 바 랍 니 다.궁금 한 점 이 있 으 시 면 저 에 게 메 시 지 를 남 겨 주세요.편집장 님 께 서 제때에 답 해 드 리 겠 습 니 다.여기 서도 저희 사이트 에 대한 여러분 의 지지 에 감 사 드 립 니 다!

좋은 웹페이지 즐겨찾기