Mybatis学习笔记(四):映射器介绍(下)

本文档讲述MyBatis映射器较为高级的部分。

级联

级联是resultMap中的配置,用来实现一对一或一对多的连表查询。

级联不是必须的,使用级联的好处是获取关联数据十分便捷,但是级联过多会增加系统的复杂度,降低系统的性能。因此当层级超过3层是,就不再考虑使用级联。

MyBatis中的级联分为3种:

  • 鉴别器(discriminator):它是一个根据某些条件决定采用具体实现类级联的方案。
  • 一对一(association):一对一的级联。
  • 一对多(collection): 一对多的级联。

先给出例子。创建3张表:教师表、班级表和学生表。假设一个班只有一个负责老师,但有多个学生。那么班级和老师就是一对一,班级和学生就是一对多。

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;

在表里面插入几条数据

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);

创建实体类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类中包含一个Teacher对象和一个Student的List。定义查询ClassInfo信息的方法,有2种方式,先创建接口。

package com.wyk.mybatisDemo.mapper;

import com.wyk.mybatisDemo.pojo.ClassInfo;

public interface SchoolMapper {

    public ClassInfo getClassInfo(int id);
    
    public ClassInfo getClassInfo2(int id);
}

一种方法是通过联表查询获取结果,另一种方法是多次查询。具体方法请看映射器文件。

<?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>
    <!-- 方法一 联表查询 -->
    <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>

在单元测试中运行一下,查看效果。

@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);
}
级联结果.png

鉴别器的使用在实际中用的较少,就没有写,主要是根据某些条件决定采用哪个类。

延迟加载

关注上一小节的方式二,每当查询一个班级信息,都要查询N个学生信息,会造成很大的资源浪费,尤其是我们并不想知道学生信息的时候。

为了应对上述的N+1问题,MyBatis提供了延迟加载功能。settings配置中有2个元素可以配置级联。

  • lazyLoadingEnabled,延迟加载的全局开关,默认为false。
  • aggressiveLazyLoading, 层级延迟加载开关,处于同一个层级的关联表会同时延迟加载,或者同时被加载。

使用延迟加载首先要引用对应的jar包cglib。

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>${mybatis.version}</version>
</dependency>
<settings>
    <setting name="lazyLoadingEnabled" value="true" />
    <setting name="aggressiveLazyLoading" value="true" />
</settings>

上面的配置属于全局配置,会出现一个问题,同一个层级的级联表也存在需求性的差异,同一级的某个数据库表的数据或许不是我们经常使用的,那么上述的配置也会影响系统的性能。

fetchType属性可以处理全局定义无法处理的问题,进行自定义,出现在级联元素association和collection中,有2个可选值:

  • eager,获取当前实体后立即加载对应的数据。
  • lazy,获取当前实体后延迟加载对应的数据。
<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属性会忽略全局配置项lazyLoadingEnabled和aggressiveLazyLoading。

说了这么多,感觉最好还是使用上一小节的方法一连接查询,而不是方法二嵌套查询比较好。

缓存

MyBatis中拥有一级缓存和二级缓存,其中一级缓存是在SqlSession上的缓存,二级缓存是在SqlSessionFactory上的缓存。

一级缓存

MyBatis默认开启一级缓存,而无需进行任何配置。

还是上面的例子,编写一个查找的测试用例。

@Test
public void testFirstLevelCache() {
    SqlSession sqlSession = null;
    try {
        sqlSession = DataConnection.openSqlSession();
        SchoolMapper schoolMapper = sqlSession.getMapper(SchoolMapper.class);
        ClassInfo classInfo1= schoolMapper.getClassInfo2(1);
        logger.info("再获取一次实体...");
        ClassInfo classInfo2 = schoolMapper.getClassInfo2(1);
    } catch (Exception e) {
        logger.info(e.getMessage(), e);
    } finally {
        if(sqlSession != null) {
            sqlSession.close();
        }
    }   
}

例子里进行了两次查询,查看控制台,发现只执行了一次SQL语句。

一级缓存结果.png

每次查询结果MyBatis都会进行缓存,因此第二次查询的时候可以直接获取结果。但是如果执行了增、删、改等操作,则缓存会刷新。

二级缓存

将测试用例改为创建2个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才会缓存对象到SqlSessionFactory
        sqlSession1.commit();
        logger.info("再获取一次实体...");
        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。查看结果,发现进行了两次查询。

二级缓存测试结果1.png

在映射器文件中添加二级缓存配置cache。

<cache />

再次运行用例,查看结果,发现命中缓存,只查询了一次。

二级缓存测试结果2.png

缓存配置项

前面是通用配置,还可以为每个语句单独配置。下面是各个语句的默认配置,可以根据实际需求修改。

<select ... flushCache="false" useCache="true" />
<insert ... flushCache="true" />
<update ... flushCache="true" />
<delete ... flushCache="true" />

另外,MyBatis还可以使用Redis等外部缓存。

存储过程

待补充。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,064评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,606评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,011评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,550评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,465评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,919评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,428评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,075评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,208评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,185评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,191评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,914评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,482评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,585评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,825评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,194评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,703评论 2 339