美文网首页
Mybatis学习笔记(四):映射器介绍(下)

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

作者: 简单一点点 | 来源:发表于2019-01-02 16:46 被阅读1次

本文档讲述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等外部缓存。

存储过程

待补充。

相关文章

网友评论

      本文标题:Mybatis学习笔记(四):映射器介绍(下)

      本文链接:https://www.haomeiwen.com/subject/wpozlqtx.html