本文檔講述MyBatis映射器較為高級(jí)的部分撑柔。
級(jí)聯(lián)
級(jí)聯(lián)是resultMap中的配置获询,用來(lái)實(shí)現(xiàn)一對(duì)一或一對(duì)多的連表查詢掖棉。
級(jí)聯(lián)不是必須的聂抢,使用級(jí)聯(lián)的好處是獲取關(guān)聯(lián)數(shù)據(jù)十分便捷拯钻,但是級(jí)聯(lián)過(guò)多會(huì)增加系統(tǒng)的復(fù)雜度帖努,降低系統(tǒng)的性能。因此當(dāng)層級(jí)超過(guò)3層是粪般,就不再考慮使用級(jí)聯(lián)拼余。
MyBatis中的級(jí)聯(lián)分為3種:
- 鑒別器(discriminator):它是一個(gè)根據(jù)某些條件決定采用具體實(shí)現(xiàn)類級(jí)聯(lián)的方案。
- 一對(duì)一(association):一對(duì)一的級(jí)聯(lián)亩歹。
- 一對(duì)多(collection): 一對(duì)多的級(jí)聯(lián)匙监。
先給出例子。創(chuàng)建3張表:教師表小作、班級(jí)表和學(xué)生表亭姥。假設(shè)一個(gè)班只有一個(gè)負(fù)責(zé)老師,但有多個(gè)學(xué)生顾稀。那么班級(jí)和老師就是一對(duì)一达罗,班級(jí)和學(xué)生就是一對(duì)多。
CREATE TABLE t_teachers(
t_id INT PRIMARY KEY AUTO_INCREMENT,
t_name VARCHAR(20)
)ENGINE=INNODB DEFAULT CHARSET=utf8;
CREATE TABLE t_classes(
c_id INT PRIMARY KEY AUTO_INCREMENT,
c_name VARCHAR(20),
teacher_id INT
)ENGINE=INNODB DEFAULT CHARSET=utf8;
ALTER TABLE t_class ADD CONSTRAINT fk_teacher_id FOREIGN KEY (teacher_id) REFERENCES t_teacher(t_id);
CREATE TABLE t_students(
s_id INT PRIMARY KEY AUTO_INCREMENT,
s_name VARCHAR(20),
class_id INT
)ENGINE=INNODB DEFAULT CHARSET=utf8;
在表里面插入幾條數(shù)據(jù)
INSERT INTO t_teacher(t_name) VALUES('張老師');
INSERT INTO t_teacher(t_name) VALUES('蔡老師');
INSERT INTO t_class(c_name, teacher_id) VALUES('318班', 1);
INSERT INTO t_class(c_name, teacher_id) VALUES('319班', 2);
INSERT INTO t_students(s_name, class_id) VALUES('張三', 1);
INSERT INTO t_students(s_name, class_id) VALUES('李四', 1);
INSERT INTO t_students(s_name, class_id) VALUES('小明', 1);
INSERT INTO t_students(s_name, class_id) VALUES('王五', 2);
INSERT INTO t_students(s_name, class_id) VALUES('小紅', 2);
創(chuàng)建實(shí)體類Teacher静秆,Student和ClassInfo粮揉。
package com.wyk.mybatisDemo.pojo;
public class Teacher {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Teacher [id=" + id + ", name=" + name + "]";
}
}
package com.wyk.mybatisDemo.pojo;
public class Student {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return "Student[id=" + id + ", name=" + name + "]";
}
}
package com.wyk.mybatisDemo.pojo;
import java.util.List;
public class ClassInfo {
private int id;
private String name;
private Teacher teacher;
private List<Student> students;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
public List<Student> getStudent() {
return students;
}
public void setStudent(List<Student> students) {
this.students = students;
}
public String toString() {
return "ClassInfo [id=" + id + ", name=" + name + ", teacher=" + teacher +
", students=" + students +"]";
}
}
可以看到,ClassInfo類中包含一個(gè)Teacher對(duì)象和一個(gè)Student的List诡宗。定義查詢ClassInfo信息的方法滔蝉,有2種方式,先創(chuàng)建接口塔沃。
package com.wyk.mybatisDemo.mapper;
import com.wyk.mybatisDemo.pojo.ClassInfo;
public interface SchoolMapper {
public ClassInfo getClassInfo(int id);
public ClassInfo getClassInfo2(int id);
}
一種方法是通過(guò)聯(lián)表查詢獲取結(jié)果蝠引,另一種方法是多次查詢阳谍。具體方法請(qǐng)看映射器文件。
<?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.wyk.mybatisDemo.mapper.SchoolMapper">
<resultMap type="com.wyk.mybatisDemo.pojo.ClassInfo" id="classMap">
<id property="id" column="c_id" />
<result property="name" column="c_name" />
<association property="teacher" javaType="com.wyk.mybatisDemo.pojo.Teacher">
<id property="id" column="t_id" />
<result property="name" column="t_name" />
</association>
<collection property="students" ofType="com.wyk.mybatisDemo.pojo.Student">
<id property="id" column="s_id" />
<result property="name" column="s_name" />
</collection>
</resultMap>
<!-- 方法一 聯(lián)表查詢 -->
<select id="getClassInfo" parameterType="int" resultMap="classMap">
select c.c_id, c.c_name, t.t_id, t.t_name , s.s_id, s.s_name
from t_classes c, t_teachers t, t_students s
where c.teacher_id = t.t_id and c.c_id = s.class_id and c.c_id=#{id}
</select>
<resultMap type="com.wyk.mybatisDemo.pojo.ClassInfo" id="classMap2">
<id property="id" column="c_id" />
<result property="name" column="c_name" />
<association property="teacher" column="teacher_id" select="getTeacher" />
<collection property="students" ofType="com.wyk.mybatisDemo.pojo.Student"
column="c_id" select="getStudent"></collection>
</resultMap>
<!-- 方法二 多次查詢 -->
<select id="getClassInfo2" parameterType="int" resultMap="classMap2">
select c_id, c_name, teacher_id from t_classes where c_id=#{id}
</select>
<select id="getTeacher" parameterType="int" resultType="com.wyk.mybatisDemo.pojo.Teacher">
select t_id id, t_name name from t_teachers where t_id=#{id}
</select>
<select id="getStudent" parameterType="int" resultType="com.wyk.mybatisDemo.pojo.Student">
select s_id id, s_name name from t_students where class_id=#{id}
</select>
</mapper>
在單元測(cè)試中運(yùn)行一下螃概,查看效果矫夯。
@Test
public void testGetClassInfo() {
SqlSession sqlSession = DataConnection.openSqlSession();
SchoolMapper schoolMapper = sqlSession.getMapper(SchoolMapper.class);
ClassInfo classInfo = schoolMapper.getClassInfo(1);
System.out.println(classInfo);
ClassInfo classInfo2 = schoolMapper.getClassInfo2(1);
System.out.println(classInfo2);
}
鑒別器的使用在實(shí)際中用的較少,就沒(méi)有寫(xiě)吊洼,主要是根據(jù)某些條件決定采用哪個(gè)類训貌。
延遲加載
關(guān)注上一小節(jié)的方式二,每當(dāng)查詢一個(gè)班級(jí)信息冒窍,都要查詢N個(gè)學(xué)生信息递沪,會(huì)造成很大的資源浪費(fèi),尤其是我們并不想知道學(xué)生信息的時(shí)候综液。
為了應(yīng)對(duì)上述的N+1問(wèn)題款慨,MyBatis提供了延遲加載功能。settings配置中有2個(gè)元素可以配置級(jí)聯(lián)谬莹。
- lazyLoadingEnabled檩奠,延遲加載的全局開(kāi)關(guān),默認(rèn)為false附帽。
- aggressiveLazyLoading, 層級(jí)延遲加載開(kāi)關(guān)埠戳,處于同一個(gè)層級(jí)的關(guān)聯(lián)表會(huì)同時(shí)延遲加載,或者同時(shí)被加載蕉扮。
使用延遲加載首先要引用對(duì)應(yīng)的jar包c(diǎn)glib整胃。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>${mybatis.version}</version>
</dependency>
<settings>
<setting name="lazyLoadingEnabled" value="true" />
<setting name="aggressiveLazyLoading" value="true" />
</settings>
上面的配置屬于全局配置,會(huì)出現(xiàn)一個(gè)問(wèn)題慢显,同一個(gè)層級(jí)的級(jí)聯(lián)表也存在需求性的差異爪模,同一級(jí)的某個(gè)數(shù)據(jù)庫(kù)表的數(shù)據(jù)或許不是我們經(jīng)常使用的,那么上述的配置也會(huì)影響系統(tǒng)的性能荚藻。
fetchType屬性可以處理全局定義無(wú)法處理的問(wèn)題屋灌,進(jìn)行自定義,出現(xiàn)在級(jí)聯(lián)元素association和collection中应狱,有2個(gè)可選值:
- eager共郭,獲取當(dāng)前實(shí)體后立即加載對(duì)應(yīng)的數(shù)據(jù)。
- lazy疾呻,獲取當(dāng)前實(shí)體后延遲加載對(duì)應(yīng)的數(shù)據(jù)除嘹。
<collection property="students" ofType="com.wyk.mybatisDemo.pojo.Student" fetchType="eager">
<id property="id" column="s_id" />
<result property="name" column="s_name" />
</collection>
fetchType屬性會(huì)忽略全局配置項(xiàng)lazyLoadingEnabled和aggressiveLazyLoading。
說(shuō)了這么多岸蜗,感覺(jué)最好還是使用上一小節(jié)的方法一連接查詢尉咕,而不是方法二嵌套查詢比較好。
緩存
MyBatis中擁有一級(jí)緩存和二級(jí)緩存璃岳,其中一級(jí)緩存是在SqlSession上的緩存年缎,二級(jí)緩存是在SqlSessionFactory上的緩存悔捶。
一級(jí)緩存
MyBatis默認(rèn)開(kāi)啟一級(jí)緩存,而無(wú)需進(jìn)行任何配置单芜。
還是上面的例子蜕该,編寫(xiě)一個(gè)查找的測(cè)試用例。
@Test
public void testFirstLevelCache() {
SqlSession sqlSession = null;
try {
sqlSession = DataConnection.openSqlSession();
SchoolMapper schoolMapper = sqlSession.getMapper(SchoolMapper.class);
ClassInfo classInfo1= schoolMapper.getClassInfo2(1);
logger.info("再獲取一次實(shí)體...");
ClassInfo classInfo2 = schoolMapper.getClassInfo2(1);
} catch (Exception e) {
logger.info(e.getMessage(), e);
} finally {
if(sqlSession != null) {
sqlSession.close();
}
}
}
例子里進(jìn)行了兩次查詢洲鸠,查看控制臺(tái)堂淡,發(fā)現(xiàn)只執(zhí)行了一次SQL語(yǔ)句。
每次查詢結(jié)果MyBatis都會(huì)進(jìn)行緩存扒腕,因此第二次查詢的時(shí)候可以直接獲取結(jié)果绢淀。但是如果執(zhí)行了增、刪袜匿、改等操作更啄,則緩存會(huì)刷新稚疹。
二級(jí)緩存
將測(cè)試用例改為創(chuàng)建2個(gè)sqlSessoin,如下所示居灯。
@Test
public void testSecondLevelCache() {
SqlSession sqlSession1 = null;
SqlSession sqlSession2 = null;
try {
sqlSession1 = DataConnection.openSqlSession();
SchoolMapper schoolMapper1 = sqlSession1.getMapper(SchoolMapper.class);
ClassInfo classInfo1= schoolMapper1.getClassInfo2(1);
//需要提交,MyBatis才會(huì)緩存對(duì)象到SqlSessionFactory
sqlSession1.commit();
logger.info("再獲取一次實(shí)體...");
sqlSession2 = DataConnection.openSqlSession();
SchoolMapper schoolMapper2 = sqlSession2.getMapper(SchoolMapper.class);
ClassInfo classInfo2= schoolMapper2.getClassInfo2(1);
sqlSession2.commit();
} catch (Exception e) {
logger.info(e.getMessage(), e);
} finally {
if(sqlSession1 != null) {
sqlSession1.close();
}
if(sqlSession2 != null) {
sqlSession2.close();
}
}
}
commit方法是為了將方法提交到SqlSessionFactory内狗。查看結(jié)果怪嫌,發(fā)現(xiàn)進(jìn)行了兩次查詢。
在映射器文件中添加二級(jí)緩存配置cache柳沙。
<cache />
再次運(yùn)行用例岩灭,查看結(jié)果,發(fā)現(xiàn)命中緩存,只查詢了一次赂鲤。
緩存配置項(xiàng)
前面是通用配置噪径,還可以為每個(gè)語(yǔ)句單獨(dú)配置。下面是各個(gè)語(yǔ)句的默認(rèn)配置数初,可以根據(jù)實(shí)際需求修改找爱。
<select ... flushCache="false" useCache="true" />
<insert ... flushCache="true" />
<update ... flushCache="true" />
<delete ... flushCache="true" />
另外,MyBatis還可以使用Redis等外部緩存泡孩。
存儲(chǔ)過(guò)程
待補(bǔ)充车摄。