MyBatis關聯查詢 (association) 時遇到的某些問題/mybatis映射


先說下問題產生的背景:

  最近在做一個用到MyBatis的項目,其中有個業務涉及到關聯查詢,我是將兩個查詢分開來寫的,即嵌套查詢,個人感覺這樣更方便重用;

  關聯的查詢使用到了動態sql,在執行查詢時就出現了如下錯誤:Caused by: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'id' in 'class java.lang.Integer'

  因為出現了這個問題,在解決的時候一連串又發現了一些其它新的問題,比如關聯查詢時的效率問題,以及映射。通過百度,看文檔基本解決,做個筆記。

 

現在簡單的用學生和年級兩個對象來做實驗,學生有一個年級屬性,年級有一個學生集合屬性。

先把一些基礎的代碼貼出來:

學生實體:

 1 package com.lizhou.entity;  2 
 3 import java.io.Serializable;  4 
 5 /**
 6  * 學生  7  * @author bojiangzhou  8  * @date 2016年3月30日  9  */
10 public class Student implements Serializable { 11     
12     private static final long serialVersionUID = -4165939686905301187L; 13     
14     private Integer id; 15     //學生姓名
16     private String name; 17     //年齡
18     private int age; 19     //學生所屬年級
20     private Grade grade; 21 
22     //getter/setter...
23 
24 }

年級實體:

 1 package com.lizhou.entity;  2 
 3 import java.io.Serializable;  4 import java.util.ArrayList;  5 import java.util.List;  6 
 7 /**
 8  * 年級  9  * @author bojiangzhou 10  * @date 2016年3月30日 11  */
12 public class Grade implements Serializable { 13     
14     private static final long serialVersionUID = -3469035301959740223L; 15 
16     private Integer id; 17     //年級名稱
18     private String name; 19     //年級下的學生集合
20     private List<Student> studentList = new ArrayList<Student>(); 21 
22     //getter/setter...
23     
24 }

學生數據接口:

 1 package com.lizhou.dao;  2 
 3 import java.util.List;  4 
 5 import com.lizhou.entity.Student;  6 
 7 /**
 8  * 數據層  9  * @author bojiangzhou 10  * @date 2016年3月30日 11  */
12 public interface StudentDao { 13     
14     /**
15  * 獲取學生信息 16  * @param student 17  * @return
18      */
19  Student getStudent(Student student); 20     
21     /**
22  * 獲取學生集合 23  * @param student 24  * @return
25      */
26     List<Student> getStudentList(Student student); 27 }

年級數據接口:

 1 package com.lizhou.dao;  2 
 3 import com.lizhou.entity.Grade;  4 
 5 /**
 6  * 數據層  7  * @author bojiangzhou  8  * @date 2016年3月30日  9  */
10 public interface GradeDao { 11     
12     /**
13  * 獲取某個年級 14  * @param grade 15  * @return
16      */
17  Grade getGrade(Grade grade); 18     
19     /**
20  * 獲取年級集合 21  * @param grade 22  * @return
23      */
24  Grade getGradeList(Grade grade); 25     
26 }

加載配置文件獲取SqlSession工具類:

 1 package com.lizhou.tools;  2 
 3 import java.io.IOException;  4 import java.io.InputStream;  5 
 6 import org.apache.ibatis.io.Resources;  7 import org.apache.ibatis.session.SqlSession;  8 import org.apache.ibatis.session.SqlSessionFactory;  9 import org.apache.ibatis.session.SqlSessionFactoryBuilder; 10 
11 /**
12  * 加載配置文件獲取SqlSession工具類 13  * @author bojiangzhou 14  * @date 2016年3月30日 15  */
16 public class SqlSessionFactoryTool { 17     
18     private static SqlSessionFactory sqlSessionFactory; 19     
20     public static SqlSessionFactory getSqlSessionFactory(){ 21         if(sqlSessionFactory == null){ 22             try { 23                 InputStream is = Resources.getResourceAsStream("mybatis-config.xml"); 24                 sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); 25             } catch (IOException e) { 26  e.printStackTrace(); 27  } 28  } 29         return sqlSessionFactory; 30  } 31     
32     public static SqlSession openSession(){ 33         return getSqlSessionFactory().openSession(); 34  } 35     
36 }

測試類:

 1 package com.lizhou.service;  2 
 3 import java.util.LinkedHashMap;  4 import java.util.List;  5 import java.util.Map;  6 
 7 import org.apache.ibatis.session.SqlSession;  8 import org.junit.After;  9 import org.junit.Before; 10 import org.junit.Test; 11 
12 import com.lizhou.entity.Grade; 13 import com.lizhou.entity.Student; 14 import com.lizhou.dao.GradeDao; 15 import com.lizhou.dao.StudentDao; 16 import com.lizhou.tools.SqlSessionFactoryTool; 17 
18 /**
19  * 測試類 20  * @author bojiangzhou 21  * @date 2016年3月30日 22  */
23 public class TestService { 24     
25     private SqlSession sqlSession = null; 26     
27     private StudentDao studentDao = null; 28     private GradeDao gradeDao = null; 29     
30     /**
31  * 測試方法前調用 32  * @throws Exception 33      */
34  @Before 35     public void setUp() throws Exception { 36         sqlSession = SqlSessionFactoryTool.openSession(); 37         studentDao = sqlSession.getMapper(StudentDao.class); 38         gradeDao = sqlSession.getMapper(GradeDao.class); 39  } 40     
41     /**
42  * 測試方法后調用 43  * @throws Exception 44      */
45  @After 46     public void tearDown() throws Exception { 47  sqlSession.close(); 48  } 49     
50     /**
51  * 測試方法 52      */
53  @Test 54     public void test() { 55         
56  } 57 
58 }

 

**************************************************************************************************************************

一、mybatis嵌套查詢(即SQL語句分離的)時報類似於There is no getter for property named 'id' in 'class java.lang.Integer' 的錯誤

先看看產生問題的與學生和年級對應的映射配置:

StudentMapper.xml:這里是通過association查詢年級的(第10行代碼)。

 1 <?xml version="1.0" encoding="UTF-8" ?>
 2 <!DOCTYPE mapper  3 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  4 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 5 <mapper namespace="com.lizhou.dao.StudentDao">
 6     
 7     <resultMap type="Student" id="studentResult">
 8         <id property="id" column="id"/>
 9         
10         <association property="grade" column="gradeId" select="com.lizhou.dao.GradeDao.getGrade"></association>
11     </resultMap>
12     
13     <select id="getStudent" resultMap="studentResult">
14  SELECT * FROM student 15         <where>
16             <if test="id != null">
17  AND id=#{id} 18             </if>
19         </where>
20     </select>
21     
22 </mapper> 

GradeMapper.xml:動態Sql,本意是想如果有一個查詢所有年級和根據id來查詢年級的業務時就可以使用同一個查詢了。

注意這里年級還有一個學生集合沒有映射到gradeResult里面,如果映射了會循環查詢最終導致棧溢出的,后面再說。

 1 <?xml version="1.0" encoding="UTF-8" ?>
 2 <!DOCTYPE mapper  3 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  4 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 5 <mapper namespace="com.lizhou.dao.GradeDao">
 6     
 7     <resultMap type="Grade" id="gradeResult">
 8         <id property="id" column="id"/>
 9         
10     </resultMap>
11     
12     <select id="getGrade" parameterType="Grade" resultMap="gradeResult">
13  SELECT * FROM grade 14         <where>
15             <if test="id != null">
16  AND id=#{id} 17             </if>
18         </where>
19     </select>
20     
21 </mapper> 

測試代碼:

1 @Test 2 public void test() { 3     Student student = new Student(); 4     student.setId(1); 5     //根據id查詢學生
6     student = studentDao.getStudent(student); 7     
8  System.out.println(student); 9 }

 

我的本意是各自的業務分開來寫,我要查詢學生時就調用getStudent,要查詢年級時就調用getGrade,而我要查詢學生的同時還要查詢其關聯的年級,這樣我也可以調用getGrade來完成,這樣能很好的實現重用。

但結果並不是這樣的,報錯:Caused by: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'id' in 'class java.lang.Integer'

剛開始真的沒明白怎么回事,在Integer中找不到id屬性??

我將GradeMapper.xml中的select語句改成如下時,就可以:

 1 <mapper namespace="com.lizhou.dao.GradeDao">
 2     
 3     <resultMap type="Grade" id="gradeResult">
 4         <id property="id" column="id"/>
 5         
 6     </resultMap>
 7     
 8     <select id="getGrade" parameterType="Grade" resultMap="gradeResult">
 9  SELECT * FROM grade WHERE id=#{id} 10     </select>
11     
12 </mapper> 

百度了很久,原因是在查詢到一條記錄的時候就會將id注入,而且不管#{id}的名稱,就是說你隨便寫也能查出結果來,第9行代碼:

 1 <mapper namespace="com.lizhou.dao.GradeDao">
 2     
 3     <resultMap type="Grade" id="gradeResult">
 4         <id property="id" column="id"/>
 5         
 6     </resultMap>
 7     
 8     <select id="getGrade" parameterType="Grade" resultMap="gradeResult">
 9  SELECT * FROM grade WHERE id=#{id-xxxxxx} 10     </select>
11     
12 </mapper>

 

結論》》聯合查詢時:

不管輸入參數名稱是什么,mybatis最終會執行:

效果為:select * from grade where id =resultSet.getInt("id");

所以我之前用動態sql時去判斷id,其實是不存在id的,所以導致那樣的錯誤!!

這里參考原文解釋:MyBatis中Association聯合select使用

------------------------------------------------------------------------------------------------------------------------------------------------------------------

二、嵌套結果查詢導致的循環查詢棧溢出

如果這樣使用聯合查詢,其實還存在另外一個問題:學生下有年級,年級下有學生集合,這樣查詢下去只有棧溢出了....

看代碼:

StudentMapper.xml:年級也有映射

 1 <mapper namespace="com.lizhou.dao.StudentDao">
 2     
 3     <resultMap type="Student" id="studentResult">
 4         <id property="id" column="id"/>
 5         
 6         <association property="grade" column="gradeId" select="com.lizhou.dao.GradeDao.getGrade"></association>
 7     </resultMap>
 8     
 9     <select id="getStudent" resultMap="studentResult">
10  SELECT * FROM student WHERE gradeId=#{gradeId} 11     </select>
12     
13 </mapper> 

GradeMapper.xml:注意此時映射了學生集合的,第6行代碼

 1 <mapper namespace="com.lizhou.dao.GradeDao">
 2     
 3     <resultMap type="Grade" id="gradeResult">
 4         <id property="id" column="id"/>
 5         
 6         <collection property="studentList" column="id" ofType="Student" select="com.lizhou.dao.StudentDao.getStudent"></collection>
 7     </resultMap>
 8     
 9     <select id="getGrade" parameterType="Grade" resultMap="gradeResult">
10  SELECT * FROM grade WHERE id=#{id} 11     </select>
12     
13 </mapper> 

不管是查詢學生還是年級,結果報錯:java.lang.StackOverflowError

 

這樣看來,最終都還是要分開寫,就是說我想查詢年級和查詢學生時聯合查詢年級要寫兩個select才行,並沒能達到重用的目的。

但是相信程序員都是有強迫症的,這樣有點不爽,我就又百度,看能不能很好的重用,結果是沒有百度到。

但是在看文檔的時候,發現了另一個問題:聯合查詢分開寫查詢語句是很消耗性能的,明明可以只用執行一次查詢,卻執行了兩次甚至更多次。

文檔是這樣說的:這種方式很簡單,但是對於大型數據集合和列表將不會表現很好。問題就是我們熟知的“N+1 查詢問題”

N+1 查詢問題可以是這樣引起的:

1. 你執行了一個單獨的 SQL 語句來獲取結果列表(就是“+1”)

2.對返回的每條記錄,你執行了一個查詢語句來為每個加載細節(就是“N”)

這個問題會導致成百上千的 SQL 語句被執行。這通常不是期望的

------------------------------------------------------------------------------------------------------------------------------------------------------------------

三、關聯的嵌套結果

上面的叫做[關聯的嵌套查詢]

所以自然有另一種方法----[關聯的嵌套結果](文檔上這樣說的):聯合兩張表在一起,代替了執行一個分離的語句。

看代碼:

 1 <mapper namespace="com.lizhou.dao.StudentDao">
 2     
 3     <resultMap type="Student" id="studentResult">
 4         <id property="id" column="id"/>
 5         <result property="name" column="name"/>
 6         <result property="age" column="age"/>
 7         <association property="grade" column="gradeId" javaType="Grade">
 8             <id property="id" column="id"/>
 9             <result property="name" column="name"/>
10         </association>
11     </resultMap>
12     
13     <select id="getStudent" resultMap="studentResult">
14  SELECT * FROM student LEFT OUTER JOIN grade ON grade.id=student.gradeId WHERE student.id=#{id} 15     </select>
16     
17 </mapper> 

映射的時候是將年級和學生一起映射的,這個就叫做嵌套結果映射。然后是sql語句,使用的是聯合查詢。

------------------------------------------------------------------------------------------------------------------------------------------------------------------

四、映射、效率

這里又遇到了幾個問題:

1.在之前寫resultMap映射時,我是一般不愛將屬性映射寫出來的,因為mybatis會自動映射的,但是這里不行了。

在嵌套結果映射時,哪個屬性沒有映射那么這個值就是空的,查詢出來的結果不會注入到對象中。所以必須將所有屬性都顯示映射出來。

2.先看看映射都OK時輸出的結果:Student [id=2, name=羅若亞卓落, age=22, grade=Grade [id=2, name=羅若亞卓落, studentList=[]]]

輸出結果中,grade中的數據和student中的一樣了....因為Student的id和name跟Grade的id和name屬性名一樣。

mybatis其實就是使用resultSet.getString("name")這樣的方式來獲取值的,所以聯合查詢時兩張表的字段名不能重復。

或者可以在配置文件中配置好映射和sql語句。如下:注意第8、9行代碼,重新配置column;以及sql語句。但是如果屬性過多,sql代碼可能就有點長了,所以最好還是保持字段名不一樣吧。

 1 <mapper namespace="com.lizhou.dao.StudentDao">
 2     
 3     <resultMap type="Student" id="studentResult">
 4         <id property="id" column="id"/>
 5         <result property="name" column="name"/>
 6         <result property="age" column="age"/>
 7         <association property="grade" column="gradeId" javaType="Grade">
 8             <id property="id" column="grade_id"/>
 9             <result property="name" column="grade_name"/>
10         </association>
11     </resultMap>
12     
13     <select id="getStudent" resultMap="studentResult">
14  SELECT 15  s.id, 16  s.name, 17  s.age, 18  g.id as grade_id, 19  g.name as grade_name 20  FROM student s LEFT OUTER JOIN grade g ON g.id=s.gradeId 21  WHERE s.id=#{id} 22     </select>
23     
24 </mapper>

3.最后是在文檔中看到的一個比較重要的問題,最好是要顯示映射出id。直接復制原話:

非常重要:在嵌套結果映射中 id 元素扮演了非常重要的角色。應該通常指定一個或多個屬性,它們可以用來唯一標識結果。

實際上就是如果你不使用它(id 元素) ,但是會產生一個嚴重的性能問題,不過 MyBatis 仍然可以正常工作。

選擇的屬性越少越好,它們可以唯一地標識結果。主鍵就是一個顯而易見的選擇(即便是聯合主鍵)。

 

============================================================================================

剛學完mybatis不久,現在正在做的這個項目(視頻教程,當練手)本來是struts+spring+ibatis的,我想着ibatis也算是過去式了,恰好前陣剛學了mybatis還沒實戰過,就把ibatis換成了mybatis。

不過只是知道怎么使用,很多細節性的東西還沒深入了解。所以這篇博客也只是開發中遇到的一些問題解決后做個筆記,大部分都屬於個人理解,有些可能不全面,希望有不同見解的朋友一起交流~~O(∩_∩)O~~

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM