MyBatis缓存与延迟加载机制

本文最后更新于:1 年前

MyBatis是基于JDBC的封装,使数据库操作更加便捷;MyBatis除了对JDBC操作步骤进行封装之外也对其性能进行了优化:

  • 在MyBatis引入缓存机制,用于提升MyBatis的检索效率;
  • 在MyBatis引入延迟加载机制,用于减少对数据库不必要的访问。

MyBatis缓存

MyBatis缓存分为一级缓存二级缓存

缓存工作原理

所谓缓存,就是存储数据的内存。

一级缓存

一级缓存也叫做 SqlSession 级缓存,即为每个 SqlSession 单独分配的缓存内存,无需手动开启可直接使用;多个 SqlSession 的缓存是不共享的。

特性

  1. 如果多次查询使用的是同一个SqlSession对象,则第一次查询之后数据会存放到缓存,后续的查询则直接访问缓存中存储的数据;

  2. 如果第一次查询完成之后,对查询出的对象进行修改(此修改会影响到缓存),第二次查询会直接访问缓存,造成第二次查询的结果与数据库不一致;

  3. 当我们进行在查询时想要跳过缓存直接查询数据库,则可以通过sqlSession.clearCache();来清除当前SqlSession的缓存;

  4. 如果第一次查询之后第二查询之前,使用当前的sqlsession执行了修改操作,此修改操作会使第一次查询并缓存的数据失效,因此第二次查询会再次访问数据库。

案例分析

两次查询与数据库数据不一致的原因即解决方案

二级缓存

二级缓存也称为 SqlSessionFactory 级缓存,通过同一个 factory 对象获取的 Sqlsession 可以共享二级缓存。在应用服务器中SqlSessionFactory是单例的,因此我们二级缓存可以实现全局共享。

特性特性:

  1. 二级缓存默认没有开启,需要在 mybatis-config.xml 中的 settings 标签开启

  2. 二级缓存只能缓存实现序列化接口的对象

步骤案例

  1. 在 mybatis-config.xml 开启使用二级缓存

    1
    2
    3
    <settings>
    <setting name="cacheEnabled" value="true"/>
    </settings>
  2. 在需要使用二级缓存的 Mapper 文件中配置 cache 标签使用功能二级缓存

    1
    <cache></cache>
  3. 让被缓存的实体类实现序列化接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @ToString
    public class Member implements Serializable {
    private int memberId;
    private String memberNick;
    private String memberGender;
    private int memberAge;
    private String memberCity;
    }
  4. 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    @Test
    public void testQueryMemberById(){
    SqlSessionFactory factory =MyBatisUtil.getSqlSessionFactory();
    // 1.多个SqlSession对象必须来自于同一个SqlSessionFactory
    SqlSession sqlSession1 = factory.openSession(true);
    SqlSession sqlSession2 = factory.openSession(true);
    System.out.println(sqlSession1 == sqlSession2);

    MemberDAO memberDAO1 = sqlSession1.getMapper(MemberDAO.class);
    Member member1 = memberDAO1.queryMemberById(1);
    System.out.println(member1);
    sqlSession1.commit();
    //2.第一次查询之后执行sqlSession1.commit(),才会将当前sqlsession的查询结果缓存到二级缓存

    System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");

    MemberDAO memberDAO2 = sqlSession2.getMapper(MemberDAO.class);
    Member member2 =memberDAO2.queryMemberById(1);
    System.out.println(member2);
    }

查询操作的缓存开关

1
2
3
4
5
<select id="queryMemberById" resultMap="memberMap" useCache="false">
select member_id,member_nick,member_gender,member_age,member_city
from members
where member_id=#{mid}
</select>

延迟加载

如果在MyBatis开启了延迟加载,在执行了子查询(至少查询两次及以上)时,默认只执行第一次查询,当用到子查询的查询结果时,才会触发子查询的执行;如果无需使用子查询结果,则子查询不会执行。

开启延迟加载

1
2
3
4
5
6
7
8
9
10
11
12
<resultMap id="classMap" type="Clazz">
<id column="cid" property="classId"/>
<result column="cname" property="className"/>
<result column="cdesc" property="classDesc"/>
<collection property="stus" select="com.qfedu.dao.StudentDAO.queryStudentsByCid" column="cid" fetchType="lazy"/>
</resultMap>

<select id="queryClassByCid" resultMap="classMap">
select cid,cname,cdesc
from classes
where cid=#{cid}
</select>

测试代码

1
2
3
4
5
6
7
8
9
10
11
@Test
public void queryClassByCid() {

ClassDAO classDAO = MyBatisUtil.getMapper(ClassDAO.class);
Clazz clazz = classDAO.queryClassByCid(1);
System.out.println(clazz.getClassName());

System.out.println("-----------------------------------");

System.out.println(clazz.getStus());
}

运行日志


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!