Mybaits生命域、结果集映射、缓存

本文深入解析MyBatis的生命周期管理、结果集映射机制及缓存策略。介绍不同组件的最佳作用范围,展示如何通过ResultMap处理多表关联查询,并探讨缓存如何提升查询效率。

一、生命域和作用周期

理解我们目前已经讨论过的不同作用域和生命周期类是至关重要的,因为错误的使用会导致非常严重的并发问题。

我们可以先画一个流程图,分析一下Mybatis的执行过程!
MyBatis执行过程
作用域理解

  • SqlSessionFactoryBuilder 的作用在于创建 SqlSessionFactory,创建成功后,SqlSessionFactoryBuilder 就失去了作用,所以它只能存在于创建 SqlSessionFactory 的方法中,而不要让其长期存在。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)

  • SqlSessionFactory 可以被认为是一个数据库连接池,它的作用是创建 SqlSession 接口对象。因为 MyBatis 的本质就是 Java 对数据库的操作,所以 SqlSessionFactory 的生命周期存在于整个 MyBatis 的应用之中,所以一旦创建了 SqlSessionFactory,就要长期保存它,直至不再使用 MyBatis 应用,所以可以认为 SqlSessionFactory 的生命周期就等同于 MyBatis 的应用周期

  • 由于 SqlSessionFactory 是一个对数据库的连接池,所以它占据着数据库的连接资源。如果创建多个 SqlSessionFactory,那么就存在多个数据库连接池,这样不利于对数据库资源的控制,也会导致数据库连接资源被消耗光,出现系统宕机等情况,所以尽量避免发生这样的情况。

  • 因此在一般的应用中我们往往希望 SqlSessionFactory 作为一个单例,让它在应用中被共享。所以说 SqlSessionFactory 的最佳作用域是应用作用域

  • 如果说 SqlSessionFactory 相当于数据库连接池,那么 SqlSession 就相当于一个数据库连接(Connection 对象),你可以在一个事务里面执行多条 SQL,然后通过它的 commit、rollback 等方法,提交或者回滚事务。所以它应该存活在一个业务请求中,处理完整个请求后,应该关闭这条连接,让它归还给 SqlSessionFactory,否则数据库资源就很快被耗费精光,系统就会瘫痪,所以用 try…catch…finally… 语句来保证其正确关闭。

  • 所以 SqlSession 的最佳的作用域是请求或方法作用域
    在这里插入图片描述

二、结果集映射(ResultMap)

  • 假设有两个表,一个Teacher表,一个Student表,一个老师会教多个学生,所以老师跟学生的关系是一对多,学生跟老师的关系是多对一
    表结构

1、一对一或多对一

实体类

@Data //GET,SET,ToString,有参,无参构造
// 老师实体类
public class Teacher {
   private int id;
   private String name;
}
@Data
// 学生实体类
public class Student {
   private int id;
   private String name;
   //多个学生可以是同一个老师,即多对一
   private Teacher teacher;
}

Mapper接口对应的Mapper.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.kuang.mapper.StudentMapper">

   <!--
   需求:获取所有学生及对应老师的信息
   思路:
       1. 获取所有学生的信息
       2. 根据获取的学生信息的老师ID->获取该老师的信息
       3. 思考问题,这样学生的结果集中应该包含老师,该如何处理呢,数据库中我们一般使用关联查询?
           1. 做一个结果集映射:StudentTeacher
           2. StudentTeacher结果集的类型为 Student
           3. 学生中老师的属性为teacher,对应数据库中为tid。
              多个 [1,...)学生关联一个老师=> 一对一,一对多
           4. 查看官网找到:association – 一个复杂类型的关联;使用它来处理关联查询
   -->
   <select id="getStudents" resultMap="StudentTeacher">
    	select * from student
   </select>
   <resultMap id="StudentTeacher" type="Student">
   <!--association对应的是Student类中的teacher对象,property对应的是成员变量名,tid是作为getTeacher查询的条件,javaType代表类型为Teacher-->
       <association property="teacher"  column="tid" javaType="Teacher" select="getTeacher"/>
   </resultMap>
   <!--
   这里传递过来的id,只有一个属性的时候,下面可以写任何值
   association中column多参数配置:
       column="{key=value,key=value}"
       其实就是键值对的形式,key是传给下个sql的取值名称,value是片段一中sql查询的字段名。
   -->
   <select id="getTeacher" resultType="teacher">
      select * from teacher where id = #{id}
   </select>

</mapper>

注意点说明

<resultMap id="StudentTeacher" type="Student">
   <!--association关联属性 property属性名 javaType属性类型 column在多的一方的表中的列名-->
   <association property="teacher"  column="{id=tid,name=tid}" javaType="Teacher" select="getTeacher"/>
</resultMap>
<!--
这里传递过来的id,只有一个属性的时候,下面可以写任何值
association中column多参数配置:
   column="{key=value,key=value}"
   其实就是键值对的形式,key是传给下个sql的取值名称,value是片段一中sql查询的字段名。
-->
<select id="getTeacher" resultType="teacher">
  select * from teacher where id = #{id} and name = #{name}
</select>

这种查询方式等价于嵌套查询

select s.*, (select t.name from teacher t where t.id = s.tid) as tname from student s;

当然,也可以通过表连接的方式查询

<!--
思路:
   1. 直接查询出结果,进行结果集的映射
-->
<select id="getStudents2" resultMap="StudentTeacher2" >
  select s.id sid, s.name sname , t.name tname
  from student s,teacher t
  where s.tid = t.id
</select>

<resultMap id="StudentTeacher2" type="Student">
	<!--property对应的是实体类中的成员变量,column对应的是查询结果的列名或别名-->
	<!--id代表主键-->
   <id property="id" column="sid"/>
   <!--result即普通成员属性-->
   <result property="name" column="sname"/>
   <!--association对应的是Student类中的teacher对象-->
   <association property="teacher" javaType="Teacher">
       <result property="name" column="tname"/>
   </association>
</resultMap>

2、一对多

实体类

@Data
public class Student {
   private int id;
   private String name;
   private int tid;
}
@Data
public class Teacher {
   private int id;
   private String name;
   //一个老师多个学生
   private List<Student> students;
}

Mapper接口对应的Mapper.xml配置文件

<select id="getTeacher2" resultMap="TeacherStudent2">
select * from teacher where id = #{id}
</select>
<resultMap id="TeacherStudent2" type="Teacher">
   <!--collection对应的是Teacher对象中的students成员变量,类型为List<Student>-->
   <collection property="students" javaType="ArrayList" ofType="Student" column="id" select="getStudentByTeacherId"/>
</resultMap>
<select id="getStudentByTeacherId" resultType="Student">
  select * from student where tid = #{id}
</select>
<mapper namespace="com.kuang.mapper.TeacherMapper">

   <!--
   思路:
       1. 从学生表和老师表中查出学生id,学生姓名,老师姓名
       2. 对查询出来的操作做结果集映射
           1. 集合的话,使用collection!
               JavaType和ofType都是用来指定对象类型的
               JavaType是用来指定pojo中属性的类型
               ofType指定的是映射到list集合属性中pojo的类型。
   -->
   <select id="getTeacher" resultMap="TeacherStudent">
      select s.id sid, s.name sname , t.name tname, t.id tid
      from student s,teacher t
      where s.tid = t.id and t.id=#{id}
   </select>

   <resultMap id="TeacherStudent" type="Teacher">
       <result  property="name" column="tname"/>
       <!--collection对应的是Teacher对象中的students成员变量,类型为List<Student>-->
       <collection property="students" ofType="Student">
           <result property="id" column="sid" />
           <result property="name" column="sname" />
           <result property="tid" column="tid" />
       </collection>
   </resultMap>
</mapper>

三、Mybatis缓存

简介

1、什么是缓存 [ Cache ]?

  • 存在内存中的临时数据。

  • 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。

2、为什么使用缓存?

  • 减少和数据库的交互次数,减少系统开销,提高系统效率。

3、什么样的数据能使用缓存?

  • 经常查询并且不经常改变的数据。

Mybaits缓存

  • MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。

  • MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存

    • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)

    • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。

  • 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存

一级缓存

一级缓存也叫本地缓存:

  • 与数据库同一次会话期间查询到的数据会放在本地缓存中
    • 一个SqlSession对应一个一级缓存
    • 相同的查询条件,会直接从缓存中获取数据,不会查询数据库
    • SqlSession关闭或提交后,一级缓存会被清除
    • 一级缓存失效的情况
      • SqlSession不同
      • SqlSeesion相同,查询sql不同
      • SqlSeesion相同,两次查询之间执行了增删改操作
      • SqlSeesion相同,手动清除了缓存

二级缓存

  • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存

  • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;

  • 工作机制

    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;

    • 如果当前会话关闭或提交了,这个会话对应的一级缓存就没了;如果开启了二级缓存,一级缓存中的数据会被保存到二级缓存中;

    • 同样的,同一个命名空间下

    • 一个命名空间(mapper类)对应一个二级缓存

在这里插入图片描述

一级缓存测试

 @Test
    public void test11() {
        // 声明一个SqlSession
        SqlSession sqlSession = MybatisUtil.getSqlSession();

        // 同一个sqlSession下,声明三个mapper
        UserMapper userMapper1 = sqlSession.getMapper(UserMapper.class);
        UserMapper userMapper2 = sqlSession.getMapper(UserMapper.class);
        TeacherMapper teacherMapper = sqlSession.getMapper(TeacherMapper.class);

        // sqlSession中的userMapper1查询
        List<User> users1 = userMapper1.selectUserList();

        // sqlSession2中的userMapper2查询
        List<User> users2 = userMapper2.selectUserList();
        // 结果为true,命中此sqlSession的一级缓存
        System.out.println(users1 == users2);

        // 新增一个teacher
        Teacher teacher = new Teacher();
        teacher.setId(3);
        teacher.setName("hahaha");
        teacherMapper.addTeacher(teacher);

        // sqlSession2中的userMapper2查询
        List<User> users3 = userMapper2.selectUserList();
        // 结果为false,说明此sqlSession的任何增删改操作都会影响此sqlSession的一级缓存
        System.out.println(users1 == users3);
    }

二级缓存测试

		// 声明两个SqlSession
        SqlSession sqlSession1 = MybatisUtil.getSqlSession();
        SqlSession sqlSession2 = MybatisUtil.getSqlSession();

        // 声明三个mapper
        UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
        UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
        TeacherMapper teacherMapper2 = sqlSession2.getMapper(TeacherMapper.class);

        // sqlSession1中的userMapper1查询
        List<User> users1 = userMapper1.selectUserList();
        // 查询后提交,一级缓存清除,保存到UserMapper的二级缓存中
        sqlSession1.commit();

        // sqlSession2中的userMapper2查询
        List<User> users20 = userMapper2.selectUserList();
        // 结果为true,命中UserMapper的二级缓存
        System.out.println(users1 == users20);

        // 通过sqlsession2中的teacherMapper2新增一个user
        User user = new User();
        user.setId(8);
        user.setUsername("hahahaha");
        user.setPassword("123456");
        teacherMapper2.addUser(user);
        sqlSession2.commit();

        // sqlSession2中的userMapper2查询
        List<User> users21 = userMapper2.selectUserList();
        // 结果为true,说明其他命名空间的增删改操作不会影响当前命名空间的缓存,是个需要注意的地方
        System.out.println(users1 == users21);

总结

  • 对于一级缓存,同一个SqlSeesion中,任何增删改操作都会影响此SqlSession的缓存
  • 对于二级缓存,只有同一个命名空间的增删改操作才会影响此命名空间的缓存
  • 当然,MyBatis定义了缓存接口Cache,我们可以通过实现Cache接口来自定义二级缓存
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值