上次我們講到了MyBatis的一對一關系的表示,簡單回顧一下一對一關系就是一個學生只有一個學生證。那么什么是一對多關系呢?一個學生有多個課程這就是一對多的關系。我們結合上一章中的學生和學生證,在此基礎上新增一個課程表和課程成績表。學生對應課程表是一對多的關系,在學生確定的情況下課程表對應課程成績是一對一的關系。我們先來看看我們所假設的場景數據結構的設計。
數據庫的ER圖如下(因為對數據庫還處於菜鳥階段……所以可能ER圖繪制有誤,但不影響我們講解MyBatis一對多關系的級聯):
再看看數據庫的物理模型包含哪些字段:
數據庫的設計就差不多了,接下來是設計我們的POJO類:
首先是Student類:
1 package day_8_mybatis.pojo; 2 3 import java.util.List; 4 5 /** 6 * @author turbo 7 * 8 * 2016年11月4日 9 */ 10 public class Student { 11 private int id; 12 private String name; 13 private String sex; 14 private SelfCard selfCard; //學生和學生證是一對一的關系,所以存放一個對學生證類的引用 15 private List<CourseScore> courseScoreList; //我們在一開始就提到過學生和課程是一對多的關系,所以學生POJO類中對課程類字段就是一個List用來存放學生的課程成績。 16 //省略set/get方法 17 }
注意,我們有一個List字段是對課程成績的引用而不是課程的引用。為什么呢?因為在我們數據庫設計中,學生和課程是通過課程成績聯系起來的。
接着是我們的CourseScore類:
1 package day_8_mybatis.pojo; 2 3 /** 4 * @author turbo 5 * 6 * 2016年11月4日 7 */ 8 public class CourseScore { 9 private int id; 10 private int studentId; 11 private Course course; //在學生id確認的情況下,課程和成績是一對一的關系。 12 private String score; 13 //省略set/get方法 14 }
最后是Course類:
1 package day_8_mybatis.pojo; 2 3 /** 4 * @author turbo 5 * 6 * 2016年11月4日 7 */ 8 public class Course { 9 private int id; 10 private String courseName; 11 private String note; 12 //省略set/get方法 13 }
現在我們是要通過一個學生ID,就能查詢出這個學生的課程、以及對應課程的成績。這個怎么來實現呢?在使用MyBatis為我們提供的級聯前,我們先來梳理一下從邏輯上是怎么一步一步查詢出來的。
我們要通過學生id查詢出學生的基本信息(包括課程以及對應的成績),但在學生POJO類中有一個對課程成績的List引用(暫時忽略學生證),也就是說我們無法一條簡單的sql語句(無join的sql語句)查詢出結果。但是!我們可以通過student_id在課程成績表中查詢出該學生的相應課程id(注意此時還是id),但我們此時還是沒辦法知道具體的課程名,再利用我們上一步中student_id查詢出的course_id通過課程表再來查詢出對應的課程名。重新梳理一下:
- 通過student_id在t_student表中查詢學生基本信息(name,sex)
- 通過student_id在t_course_score表中查詢學生對應的course_id
- 通過course_id在t_course表中查詢課程
那我們現在就從最底層做起,也就是通過course_id查詢出具體課程,因為這不會涉及到其他表。
1 package day_8_mybatis.mapper; 2 3 import day_8_mybatis.pojo.Course; 4 5 /** 6 * @author turbo 7 * 8 * 2016年11月4日 9 */ 10 public interface CourseMapper { 11 Course getCourse(int id); //此id為course_id 12 }
再來看看mapper映射,也是非常簡單。
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="day_8_mybatis.mapper.CourseMapper"> 6 <select id="getCourse" parameterType="int" resultType="day_8_mybatis.pojo.Course"> 7 select id, course_name as courseName, note from t_course where id = #{id} 8 </select> 9 </mapper>
現在已經寫好了通過course_id來查詢出具體課程。那么就是倒着走到第2步,通過student_id在t_course_score表中查詢學生對應的course_id,在最開始說過,在學生確定的情況下,課程和課程成績是一對一的關系,關於一對一的關系我們在上一篇已經講過,不妨再重溫一下。
1 package day_8_mybatis.mapper; 2 3 import day_8_mybatis.pojo.CourseScore; 4 5 /** 6 * @author turbo 7 * 8 * 2016年11月4日 9 */ 10 public interface CourseScoreMapper { 11 CourseScore findCourseScoreByStudentId(int id); 12 }
再來看看mapper映射,也是非常簡單。
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="day_8_mybatis.mapper.CourseScoreMapper"> 6 <resultMap id="courseScoreMap" type="day_8_mybatis.pojo.CourseScore"> 7 <id property="id" column="id"/> 8 <result property="studentId" column="student_id"/> 9 <result property="score" column="score"/> 10 <association property="course" column="course_id" select="day_8_mybatis.mapper.CourseMapper.getCourse" /> <!--將查詢出來的course_id交給CouseMapper來查詢出具體課程信息--> 11 </resultMap> 12 13 <select id="findCourseScoreByStudentId" parameterType="int" resultMap="courseScoreMap"> 14 select id, student_id, course_id, score from t_course_score where student_id = #{id} 15 </select> 16 </mapper>
最后一步,也就是第1步,才進入正題MyBatis的一對多collection級聯關系。
1 package day_8_mybatis.mapper; 2 3 import day_8_mybatis.pojo.Student; 4 5 /** 6 * @author turbo 7 * 8 * 2016年11月4日 9 */ 10 public interface StudentMapper { 11 Student getStudent(int id); 12 }
關鍵在mapper映射中。
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="day_8_mybatis.mapper.StudentMapper"> 6 <resultMap type="day_8_mybatis.pojo.Student" id="studentMap"> 7 <id property="id" column="id"/> 8 <result property="name" column="name"/> 9 <result property="sex" column="sex"/> 10 <association property="selfCard" column="id" select="day_8_mybatis.mapper.SelfCardMapper.findSelfCardByStudentId"/> 11 <collection property="courseScoreList" column="id" select="day_8_mybatis.mapper.CourseScoreMapper.findCourseScoreByStudentId" /> 12 </resultMap> 13 <select id="getStudent" parameterType="int" resultMap="studentMap"> 14 select id, name, sex from t_student where id = #{id} 15 </select> 16 </mapper>
請好好仔細品味品味,仔細回顧整個查詢的邏輯過程。collection就是MyBatis為我們提供的第二個級聯關系——一對多。
最后上我們的測試代碼:
1 package day_8_mybatis; 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 9 import day_8_mybatis.mapper.StudentMapper; 10 import day_8_mybatis.pojo.Student; 11 import day_8_mybatis.util.SessionFactory2; 12 13 /** 14 * 客戶端 15 * @author turbo 16 * 17 * 2016年11月4日 18 */ 19 public class Main { 20 21 /** 22 * @param args 23 * @throws IOException 24 */ 25 public static void main(String[] args) throws Exception { 26 String resource = "day_8_mybatis/mybatis-config.xml"; //獲取mybatis配置文件路徑 27 InputStream inputStream = Resources.getResourceAsStream(resource); 28 SqlSession sqlSession = SessionFactory2.getInstance(inputStream).openSession(); 29 StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); 30 Student student = studentMapper.getStudent(1); 31 System.out.println("學生:" + student.getName() + " 課程:" + student.getCourseScoreList().get(0).getCourse().getCourseName() + " 分數:" + student.getCourseScoreList().get(0).getScore()); 32 33 } 34 35 }
//還是把day_8_mybatis.util.SessionFactory2代碼貼出來吧,SqlSessionFactory用到了單例模式,這也是MyBatis官方文檔所提倡的,具體可以移步之前寫的幾個關鍵類的作用域問題,《SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession作用域(Scope)和生命周期》,也可移步至《單例模式》、《再說單例模式的線程安全問題》了解單例模式。
1 package day_8_mybatis.util; 2 3 import java.io.InputStream; 4 5 import org.apache.ibatis.session.SqlSessionFactory; 6 import org.apache.ibatis.session.SqlSessionFactoryBuilder; 7 8 /** 9 * @author turbo 10 * 11 * 2016年10月26日 12 */ 13 public class SessionFactory2 { 14 private static SqlSessionFactory sqlSessionFactory; 15 16 public static synchronized SqlSessionFactory getInstance(InputStream inputStream){ 17 if (null == sqlSessionFactory){ 18 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 19 } 20 21 return sqlSessionFactory; 22 } 23 }