1 實驗環境
本文以 Clazz (one)——Student (many) 爲例,介紹 MyBatis 中,一對多查詢。(爲避免與系統中的 java.lang.Class 類同名,將班級類定爲 Clazz)。
(1)導入 JAR 包
其中,前2個 jar 包下載地址見 → log4j-1.2.17.jar、 mybatis-3.4.1.jar,將 jar 包放入 lib 目錄下,並選中所有 jar 包,右鍵,選擇【Add to Build Path】。
(2) 工作目錄
注意:src 和 conf 目錄下的 com.mapper 包必須同名。
(3)配置文件
log4j.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<param name="Encoding" value="UTF-8" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L)\n" />
</layout>
</appender>
<logger name="java.sql">
<level value="debug" />
</logger>
<logger name="org.apache.ibatis">
<level value="info" />
</logger>
<root>
<priority value="debug" />
<appender-ref ref="STDOUT" />
</root>
</log4j:configuration>
注意:log4j.xml文件名不能隨意更改。
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>
<!-- 設置或引入資源文件 -->
<properties resource="jdbc.properties"></properties>
<!-- 設置連接數據庫的環境,default用於設置默認使用的數據庫環境 -->
<environments default="mysql">
<!-- 設置某個具體的數據庫環境 -->
<environment id="mysql">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments>
<!-- 引入映射文件 -->
<mappers>
<package name="com.mapper"/>
</mappers>
</configuration>
jdbc.properties
# K = V
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/users
jdbc.username=root
jdbc.password=0.
2 案例分析
首先在 MySQL 中創建數據庫:users,再在此數據庫中創建表:classes 和 students。
classes 包含 cid(int)、cname(varchar)2個字段,其中,cid 設置了自增,classes 表中數據如下:
students 包含 sid(int)、sname(varchar)和 cid(int) 3個字段,其中,sid 設置了自增,cid 爲外鍵,指向 classes 表中的 cid,students 表中數據如下:
首先介紹下公共的文件,主要包含 Clazz.java、Student.java、Test.java,不同的是 ClassStudentMapper.java、ClassStudentMapper.xml,將在各節分別介紹。
Clazz.java
package com.bean;
import java.util.List;
public class Clazz {
private Integer cid;
private String cname;
private List<Student> students;
public Integer getCid() {
return cid;
}
public void setCid(Integer cid) {
this.cid = cid;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
public List<Student> getStudents() {
return students;
}
public void setStudents(List<Student> students) {
this.students = students;
}
@Override
public String toString() {
return "Clazz [cid=" + cid + ", cname=" + cname + ", students=" + students + "]";
}
}
Student.java
package com.bean;
public class Student {
private Integer sid;
private String sname;
public Integer getSid() {
return sid;
}
public void setSid(Integer sid) {
this.sid = sid;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
@Override
public String toString() {
return "Student [sid=" + sid + ", sname=" + sname + "]";
}
}
Test.java
package com.test;
import java.io.IOException;
import java.io.InputStream;
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 com.bean.Clazz;
import com.mapper.ClassStudentMapper;
public class Test {
public static void main(String[] args) throws IOException {
InputStream is=Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession=sqlSessionFactory.openSession(true); //自動提交事務
//getMapper:會通過動態代理動態生成StudentMapper的代理實現類
ClassStudentMapper mapper=sqlSession.getMapper(ClassStudentMapper.class);
Clazz cla=mapper.getClassById(1);
System.out.println(cla);
}
}
2.1 方法一(collection標籤)
ClassStudentMapper.java
package com.mapper;
import com.bean.Clazz;
public interface ClassStudentMapper {
public Clazz getClassById(Integer cid);
}
ClassStudentMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mapper.ClassStudentMapper">
<resultMap type="com.bean.Clazz" id="classMap">
<id column="cid" property="cid"/>
<result column="cname" property="cname"/>
<collection property="students" ofType="com.bean.Student">
<id column="sid" property="sid"/>
<result column="sname" property="sname"/>
</collection>
</resultMap>
<!-- public Class getClassById(Integer cid); -->
<select id="getClassById" resultMap="classMap">
select c.cid,c.cname,s.sid,s.sname from classes c left join students s on c.cid=s.cid where c.cid=#{cid}
</select>
</mapper>
注意:<resultMap> 用於定義從數據庫查詢出來的數據與對象的映射關係,即:記錄屬性→對象屬性, <id> 用於定義主鍵映射關係,<result> 用於定義其他屬性映射關係,<collection> 定義了集合屬性的映射關係,ofType 爲集合元素的數據類型。
運行結果:
DEBUG 06-14 09:22:56,112 ==> Preparing: select c.cid,c.cname,s.sid,s.sname from classes c left join students s on c.cid=s.cid where c.cid=?
Clazz [cid=1, cname=一班, students=[Student [sid=1001, sname=張三], Student [sid=1004, sname=週六]]]
2.2 方法二(分步查詢)
分兩步:首先查詢班級信息,再查詢學生信息。
ClassStudentMapper.java
package com.mapper;
import java.util.List;
import com.bean.Clazz;
import com.bean.Student;
public interface ClassStudentMapper {
public Clazz getClassById(Integer cid);
public List<Student> getStudentsByCid(Integer cid);
}
ClassStudentMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mapper.ClassStudentMapper">
<!-- public List<Student> getStudentsByCid(Integer cid); -->
<select id="getStudentsByCid" resultType="com.bean.Student">
select sid,sname from students where cid=#{cid}
</select>
<resultMap type="com.bean.Clazz" id="classMap">
<id column="cid" property="cid"/>
<result column="cname" property="cname"/>
<collection property="students" ofType="com.bean.Student" select="com.mapper.ClassStudentMapper.getStudentsByCid" column="cid"/>
</resultMap>
<!-- public Class getClassById(Integer cid); -->
<select id="getClassById" resultMap="classMap">
select cid,cname from classes where cid=#{cid}
</select>
</mapper>
注意:<collection> 標籤中,select 爲第二步查詢的 SQL id,即接口的全限定名,column 爲第二步查詢的條件,此條件必須是從數據庫中查詢得到。
運行結果:
DEBUG 06-14 09:53:10,708 ==> Preparing: select cid,cname from classes where cid=?
DEBUG 06-14 09:53:10,749 ====> Preparing: select sid,sname from students where cid=?
Clazz [cid=1, cname=一班, students=[Student [sid=1001, sname=張三], Student [sid=1004, sname=週六]]]
可以看到,查詢語句分兩步執行了。
2.3 分步查詢的延遲加載
延遲加載也稱爲懶加載,是指使用時才加載,不使用就不加載,只有分步查詢才能實現延遲加載。爲查看並比較延遲加載的效果,進行了如下3個測試實驗。
測試一(沒有設置延遲加載,使用第一步查詢的數據)
Test.java
package com.test;
import java.io.IOException;
import java.io.InputStream;
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 com.bean.Clazz;
import com.mapper.ClassStudentMapper;
public class Test {
public static void main(String[] args) throws IOException {
InputStream is=Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession=sqlSessionFactory.openSession(true); //自動提交事務
//getMapper:會通過動態代理動態生成StudentMapper的代理實現類
ClassStudentMapper mapper=sqlSession.getMapper(ClassStudentMapper.class);
Clazz cla=mapper.getClassById(1);
System.out.println(cla.getCname());
}
}
運行結果:
DEBUG 06-14 10:09:06,450 ==> Preparing: select cid,cname from classes where cid=?
DEBUG 06-14 10:09:06,496 ====> Preparing: select sid,sname from students where cid=?
一班
可以看到:在沒有設置延遲加載時,只使用第一步查詢的數據,仍然會執行第二步查詢語句。
測試二(設置延遲加載,使用第一步查詢的數據)
本節 Test.java 同測試一。設置延遲加載,需要在 mybatis-config.xml 的<settings>標籤中配置,如下。
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>
<!-- 設置或引入資源文件 -->
<properties resource="jdbc.properties"></properties>
<settings>
<!-- 開啓延遲加載 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 延遲加載中,不查詢所有數據 -->
<setting name="aggressiveLazyLoading" value="flase"/>
</settings>
<!-- 設置連接數據庫的環境,default用於設置默認使用的數據庫環境 -->
<environments default="mysql">
<!-- 設置某個具體的數據庫環境 -->
<environment id="mysql">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments>
<!-- 引入映射文件 -->
<mappers>
<package name="com.mapper"/>
</mappers>
</configuration>
注意: settings 標籤必須放在 properties 標籤之後,否則會報錯:
The content of element type "configuration" must match
"(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)".
運行結果:
DEBUG 06-14 10:12:46,483 ==> Preparing: select cid,cname from classes where cid=?
一班
可以看到:在設置了延遲加載時,只使用第一步查詢的數據,不會執行第二步查詢語句。
測試三(設置延遲加載,使用第二步查詢的數據)
mybatis-config.xml 同測試二。
Test.java
package com.test;
import java.io.IOException;
import java.io.InputStream;
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 com.bean.Clazz;
import com.mapper.ClassStudentMapper;
public class Test {
public static void main(String[] args) throws IOException {
InputStream is=Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession=sqlSessionFactory.openSession(true); //自動提交事務
//getMapper:會通過動態代理動態生成StudentMapper的代理實現類
ClassStudentMapper mapper=sqlSession.getMapper(ClassStudentMapper.class);
Clazz cla=mapper.getClassById(1);
System.out.println("==========================");
System.out.println(cla.getStudents());
}
}
運行結果:
DEBUG 06-14 10:14:40,426 ==> Preparing: select cid,cname from classes where cid=?
==========================
DEBUG 06-14 10:14:40,513 ==> Preparing: select sid,sname from students where cid=?
[Student [sid=1001, sname=張三], Student [sid=1004, sname=週六]]
可以看到:在設置了延遲加載時,使用了第二步查詢的數據,纔會執行第二步查詢語句。
拓展延伸
在 mybatis-config.xml 中配置了延遲加載後,對所有的分步查詢都有效,此時可以在 ClassStudentMapper.xml 文件的<collection> 標籤中將 fetchType 屬性值設置爲 "eager"(默認值爲"lazy"),取消延遲加載,如下:
<collection property="students" ofType="com.bean.Student" select="com.mapper.ClassStudentMapper.getStudentsByCid" column="cid" fetchType="eager"/>