MyBatis框架:延迟加载策略和缓存

  • Mybatis 延迟加载策略
    • 1.1 何为延迟加载?
    • 1.2 实现需求
    • 1.3 使用association实现延迟加载
      • 1.3.1 账户的持久层DAO接口
      • 1.3.2 账户的持久层映射文件
      • 1.3.3 用户的持久层接口和映射文件
      • 1.3.4 开启MyBatis的延迟加载策略
      • 1.3.5 编写测试只查账户信息不查用户信息
    • 1.4 使用Collection实现延迟加载
      • 1.4.1 在 User 实体类中加入 List属性
      • 1.4.2 编写用户和账户持久层接口的方法
      • 1.4.3 编写用户持久层映射配置
      • 1.4.4 编写账户持久层映射配置
      • 1.4.5 测试只加载用户信息
  • Mybatis 缓存
    • 2.1 Mybatis 一级缓存
      • 2.1.1 证明一级缓存的存在
        • 2.1.1.1 编写用户持久层 Dao 接口
        • 2.1.1.2 编写用户持久层映射文件
        • 2.1.1.3 编写测试方法
      • 2.1.2 一级缓存的分析
      • 2.1.3 测试一级缓存的清空
    • 2.2 Mybatis 二级缓存
      • 2.2.1 二级缓存结构图
      • 2.2.2 二级缓存的开启与关闭
        • 2.2.2.1 第一步:在 SqlMapConfig.xml 文件开启二级缓存
        • 2.2.2.2 第二步:配置相关的 Mapper 映射文件
        • 2.2.2.3 第三步:配置 statement 上面的 useCache 属性
      • 2.2.3 二级缓存测试
      • 2.2.4 二级缓存注意事项

实际开发过程中很多时候我们并不需要总是在加载用户信息时就一定要加载他的账户信息。此时就是我们所说的延迟加载。

Mybatis 延迟加载策略

1.1 何为延迟加载?

延迟加载:就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称为懒加载。

好处:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度快。

坏处:因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。

1.2 实现需求

需求:查询账户(Account)信息并且关联查询用户(User)信息。如果先查询账户(Account)信息就可以满足要求,当需要查询用户(User)信息时,再去查询用户(User)信息。把对用户(User)信息的按需去查询就是延迟加载。

主要是通过association、collection实现一对一和一对多映射,association、collection具备延迟加载功能。

1.3 使用association实现延迟加载

1.3.1 账户的持久层DAO接口

public interface IAccountDao {/**
* 查询所有账户,同时获取账户的所属用户名称以及它的地址信息
* @return
*/
List<Account> findAll();
}

1.3.2 账户的持久层映射文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.dao.IAccountDao"><!--定义封装account和user的resultMap--><resultMap id="accountUserMap" type="account"><id property="id" column="id"/><result property="uid" column="uid"/><result property="money" column="money"/><!--一对一的关系映射:配置封装user的内容select属性指定的内容:查询用户的唯一标识column属性指定的内容:用户根据id查询时,所需要的参数的值--><association property="user" column="uid" javaType="user" select="com.itheima.dao.IUserDao.findById"/></resultMap><!--查询所有--><select id="findAll" resultMap="accountUserMap">select *from account</select>
</mapper>

1.3.3 用户的持久层接口和映射文件

package com.itheima.dao;import com.itheima.domain.User;import java.util.List;public interface IUserDao {User findById(Integer userId);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.dao.IUserDao"><select id="findById" parameterType="Integer" resultType="com.itheima.domain.User">select * from user where id = #{uid}</select>
</mapper>

1.3.4 开启MyBatis的延迟加载策略

<!--配置参数--><settings><!--开启Mybatis支持延迟加载--><setting name="lazyLoadingEnabled" value="true"/><setting name="aggressiveLazyLoading" value="false"/></settings>

1.3.5 编写测试只查账户信息不查用户信息

public class IAccountDaoTest {private InputStream in = null;private SqlSessionFactoryBuilder builder = null;private SqlSessionFactory factory = null;private SqlSession sqlSession = null;private IAccountDao accountDao = null;@Before    //在测试方法执行之前执行public void setUp() throws Exception {//1.读取配置文件in = Resources.getResourceAsStream("SqlMapConfig.xml");//2.创建构建者对象builder = new SqlSessionFactoryBuilder();//3.创建 SqlSession 工厂对象factory = builder.build(in);sqlSession = factory.openSession();accountDao = sqlSession.getMapper(IAccountDao.class);}@Afterpublic void tearDown() throws Exception {sqlSession.commit();sqlSession.close();in.close();}@Testpublic void findAll() {List<Account> accounts = accountDao.findAll();for (Account account : accounts) {System.out.println(account);
//            System.out.println(account.getUser());}}
}

测试结果如下:

我们发现,因为本次只是将 Account对象查询出来放入 List 集合中,并没有涉及到 User对象,所以就没有发出 SQL 语句查询账户所关联的 User 对象的查询。

1.4 使用Collection实现延迟加载

同样我们也可以在一对多关系配置的<collection>结点中配置延迟加载策略。
<collection>结点中也有select属性,column属性。

需求:
完成加载用户对象时,查询该用户所拥有的账户信息。

1.4.1 在 User 实体类中加入 List属性

public class User implements Serializable {private Integer id;private String username;private Date birthday;private String sex;private String address;//一对多关系映射,主表实体应该包含从表实体的集合引用private List<Account> accounts;}

1.4.2 编写用户和账户持久层接口的方法

package com.itheima.dao;import com.itheima.domain.User;import java.util.List;public interface IUserDao {List<User> findAll();User findById(Integer userId);
}
package com.itheima.dao;import com.itheima.domain.Account;import java.util.List;public interface IAccountDao {/*** 查询所有账户,同时还要获取当前账户的所属用户信息** @return*/List<Account> findAll();/*** 根据用户id查询账户信息** @param uid* @return*/List<Account> findAccountByUid(Integer uid);
}

1.4.3 编写用户持久层映射配置

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.dao.IUserDao"><!--定义User的resultMap信息--><resultMap id="userAccountMap" type="user"><id property="id" column="id"/><result property="username" column="username"/><result property="address" column="address"/><result property="sex" column="sex"/><result property="birthday" column="birthday"/><!--配置user对象中accounts集合的映射--><collection property="accounts" ofType="account"select="com.itheima.dao.IAccountDao.findAccountByUid" column="id"/></resultMap><select id="findAll" resultMap="userAccountMap">select * from user u left outer join account a on u.id = a.uid</select><select id="findById" parameterType="Integer" resultType="com.itheima.domain.User">select * from user where id = #{uid}</select>
</mapper>

1.4.4 编写账户持久层映射配置

<!--根据用户id查询账户列表--><select id="findAccountByUid" resultType="account" parameterType="int">select * from account where uid=#{uid}</select>

1.4.5 测试只加载用户信息

public class IUserDaoTest {private InputStream in = null;private SqlSessionFactoryBuilder builder = null;private SqlSessionFactory factory = null;private SqlSession sqlSession = null;private IUserDao userDao = null;@Beforepublic void setUp() throws Exception {in = Resources.getResourceAsStream("SqlMapConfig.xml");builder = new SqlSessionFactoryBuilder();factory = builder.build(in);sqlSession = factory.openSession();userDao = sqlSession.getMapper(IUserDao.class);}@Afterpublic void tearDown() throws Exception {sqlSession.commit();sqlSession.close();in.close();}@Testpublic void findAll() {List<User> userList = userDao.findAll();for (User user : userList) {System.out.println(user);
//            System.out.println(user.getAccounts());}}
}

测试结果如下:我们发现并没有加载 Account 账户信息。

Mybatis 缓存

像大多数的持久化框架一样,Mybatis 也提供了缓存策略,通过缓存策略来减少数据库的查询次数,从而提高性能。

Mybatis 中缓存分为一级缓存,二级缓存。

2.1 Mybatis 一级缓存

2.1.1 证明一级缓存的存在

一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flush 或 close,它就存在。

2.1.1.1 编写用户持久层 Dao 接口

public interface IUserDao {User findById(Integer userId);
}

2.1.1.2 编写用户持久层映射文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.dao.IUserDao"><select id="findById" parameterType="Integer" resultType="com.itheima.domain.User">select * from user where id = #{uid}</select>
</mapper>

2.1.1.3 编写测试方法

public class IUserDaoTest {private InputStream in = null;private SqlSessionFactoryBuilder builder = null;private SqlSessionFactory factory = null;private SqlSession sqlSession = null;private IUserDao userDao = null;@Beforepublic void setUp() throws Exception {in = Resources.getResourceAsStream("SqlMapConfig.xml");builder = new SqlSessionFactoryBuilder();factory = builder.build(in);sqlSession = factory.openSession();userDao = sqlSession.getMapper(IUserDao.class);}@Afterpublic void tearDown() throws Exception {sqlSession.commit();sqlSession.close();in.close();}@Testpublic void findAll() {List<User> userList = userDao.findAll();for (User user : userList) {System.out.println(user);}}/*** 测试一级缓存*/@Testpublic void testFirstLevelCache() {User u1 = userDao.findById(41);System.out.println(u1);User u2 = userDao.findById(41);System.out.println(u2);System.out.println(u1 == u2);//true}
}

测试结果如下:

我们可以发现,虽然在上面的代码中我们查询了两次,但最后只执行了一次数据库操作,这就是 Mybatis 提供给我们的一级缓存在起作用了。因为一级缓存的存在,导致第二次查询 id 为 41 的记录时,并没有发出 sql 语句从数据库中查询数据,而是从一级缓存中查询。

2.1.2 一级缓存的分析

一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。

第一次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,如果没有,从数据库查询用户信息。

得到用户信息,将用户信息存储到一级缓存中。

如果 sqlSession 去执行 commit 操作(执行插入、更新、删除),清空 SqlSession 中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。

第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直接从缓存中获取用户信息。

2.1.3 测试一级缓存的清空

public class IUserDaoTest {private InputStream in = null;private SqlSessionFactoryBuilder builder = null;private SqlSessionFactory factory = null;private SqlSession sqlSession = null;private IUserDao userDao = null;@Beforepublic void setUp() throws Exception {in = Resources.getResourceAsStream("SqlMapConfig.xml");builder = new SqlSessionFactoryBuilder();factory = builder.build(in);sqlSession = factory.openSession();userDao = sqlSession.getMapper(IUserDao.class);}@Afterpublic void tearDown() throws Exception {sqlSession.commit();sqlSession.close();in.close();}/*** 测试一级缓存*/@Testpublic void testFirstLevelCache() {User u1 = userDao.findById(41);System.out.println(u1);User u2 = userDao.findById(41);System.out.println(u2);System.out.println(u1 == u2);//true}@Testpublic void testFirstLevelCache1() {User u1 = userDao.findById(41);System.out.println(u1);sqlSession.close();//再次获取session对象sqlSession = factory.openSession();userDao = sqlSession.getMapper(IUserDao.class);User u2 = userDao.findById(41);System.out.println(u2);System.out.println(u1 == u2);//false}@Testpublic void testFirstLevelCache2() {User u1 = userDao.findById(41);System.out.println(u1);sqlSession.clearCache();User u2 = userDao.findById(41);System.out.println(u2);System.out.println(u1 == u2);//false}@Testpublic void testClearCache() {//1.根据id查询用户User user1 = userDao.findById(41);System.out.println(user1);//2.更新用户信息user1.setUsername("update user clear cache");user1.setAddress("北京海淀");userDao.updateUser(user1);//3.再次查询id为41的用户User user2 = userDao.findById(41);System.out.println(user2);System.out.println(user1 == user2);}
}

当执行sqlSession.close()后,再次获取sqlSession并查询id=41的User对象时,又重新执行了sql语句,从数据库进行了查询操作。

2.2 Mybatis 二级缓存

二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个 SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。

2.2.1 二级缓存结构图


首先开启 mybatis 的二级缓存。

sqlSession1 去查询用户信息,查询到用户信息会将查询数据存储到二级缓存中。

如果 SqlSession3 去执行相同 mapper 映射下 sql,执行 commit 提交,将会清空该 mapper 映射下的二级缓存区域的数据。

sqlSession2 去查询与 sqlSession1 相同的用户信息,首先会去缓存中找是否存在数据,如果存在直接从缓存中取出数据。

2.2.2 二级缓存的开启与关闭

2.2.2.1 第一步:在 SqlMapConfig.xml 文件开启二级缓存

<!--配置参数--><settings><setting name="cacheEnabled" value="true"/></settings>

因为 cacheEnabled 的取值默认就为 true,所以这一步可以省略不配置。为 true 代表开启二级缓存;为 false 代表不开启二级缓存。

2.2.2.2 第二步:配置相关的 Mapper 映射文件

标签表示当前这个 mapper 映射将使用二级缓存,区分的标准就看 mapper 的 namespace 值。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.dao.IUserDao"><!--开启user支持二级缓存--><cache/>
</mapper>

2.2.2.3 第三步:配置 statement 上面的 useCache 属性

    <select id="findById" parameterType="Integer" resultType="user" useCache="true">select *from userwhere id = #{uid}</select>

将 UserDao.xml 映射文件中的标签中设置 useCache=”true”代表当前这个 statement 要使用二级缓存,如果不使用二级缓存可以设置为 false。

注意:针对每次查询都需要最新的数据 sql,要设置成 useCache=false,禁用二级缓存。

2.2.3 二级缓存测试

public class secondLevelCacheTest {private InputStream in = null;private SqlSessionFactoryBuilder builder = null;private SqlSessionFactory factory = null;@Beforepublic void setUp() throws Exception {in = Resources.getResourceAsStream("SqlMapConfig.xml");builder = new SqlSessionFactoryBuilder();factory = builder.build(in);}@Afterpublic void tearDown() throws Exception {in.close();}/*** 测试一级缓存*/@Testpublic void testSecondLevelCache() {SqlSession sqlSession1 = factory.openSession();IUserDao userDao1 = sqlSession1.getMapper(IUserDao.class);User u1 = userDao1.findById(41);System.out.println(u1);sqlSession1.close();//一级缓存消失SqlSession sqlSession2 = factory.openSession();IUserDao userDao2 = sqlSession2.getMapper(IUserDao.class);User u2 = userDao2.findById(41);System.out.println(u2);sqlSession2.close();System.out.println(u1 == u2);//false}
}

经过上面的测试,我们发现执行了两次查询,并且在执行第一次查询后,我们关闭了一级缓存,再去执行第二次查询时,我们发现并没有对数据库发出 sql 语句,所以此时的数据就只能是来自于我们所说的二级缓存。

2.2.4 二级缓存注意事项

当我们在使用二级缓存时,所缓存的类一定要实现 java.io.Serializable 接口,这种就可以使用序列化方式来保存对象。

public class User implements Serializable {private Integer id;private String username;private Date birthday;private String sex;private String address;
}

MyBatis框架:延迟加载策策略、一级缓存、二级缓存相关推荐

  1. MyBatis 一级缓存二级缓存详解

    相关内容: 架构师系列内容:架构师学习笔记(持续更新) MyBatis 缓存详解 cache 缓存 缓存是一般的ORM 框架都会提供的功能,目的就是提升查询的效率和减少数据库的压力.跟Hibernat ...

  2. 深入浅出 MyBatis 的一级、二级缓存机制

    一.MyBatis 缓存 缓存就是内存中的数据,常常来自对数据库查询结果的保存.使用缓存,我们可以避免频繁与数据库进行交互,从而提高响应速度. MyBatis 也提供了对缓存的支持,分为一级缓存和二级 ...

  3. Mybatis3.4.x技术内幕(二十二):Mybatis一级、二级缓存原理分析

    2019独角兽企业重金招聘Python工程师标准>>> Mybatis的一级缓存,指的是SqlSession级别的缓存,默认开启:Mybatis的二级缓存,指的是SqlSession ...

  4. 【mybatis】Mybatis中的一级、二级缓存

    [mybatis]简介 [mybatis]mybatis & mybatis-plus & hibernate的区别 [mybatis]核心成员分析 [mybatis]Mybatis的 ...

  5. JAVAWEB开发之Hibernate详解(三)——Hibernate的检索方式、抓取策略以及利用二级缓存进行优化、解决数据库事务并发问题

    Hibernate的检索方式  Hibernate提供了以下几种检索对象的方式: 导航对象图检索方式:根据已经加载的对象导航到其他对象. OID检索方式:按照对象的OID来检索对象. HQL检索方式: ...

  6. mybatis缓存二级缓存_MyBatis缓存与Apache Ignite的陷阱

    mybatis缓存二级缓存 一周前,MyBatis和Apache ignite 宣布支持apache ignite作为MyBatis缓存(L2缓存). 从技术上讲,MyBatis支持两个级别的缓存: ...

  7. 一级缓存--二级缓存详解

    一级缓存 由于一级缓存的作用域是SqlSession内部,但是SqlSession的生命周期非常短暂,所以一级缓存对于查询效率的提升很有限.而要提升效率,需要使用二级缓存. 二级缓存 1.开启全局开关 ...

  8. mybatis的缓存机制(一级缓存二级缓存和刷新缓存)和mybatis整合ehcache

    1      查询缓存 1.1  什么是查询缓存 mybatis提供查询缓存,用于减轻数据压力,提高数据库性能. mybaits提供一级缓存,和二级缓存. 一级缓存是SqlSession级别的缓存.在 ...

  9. mybatis教程--查询缓存(一级缓存二级缓存和整合ehcache)

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

最新文章

  1. matlab 通过矩阵变换使图像旋转平移_opencv图像处理——几何变换
  2. 转载的Web.config详解
  3. Leetcode 560. Subarray Sum Equals K
  4. 深度学习核心技术精讲100篇(四十四)-深度召回在招聘推荐中的挑战和实践
  5. spark实验遇到的问题
  6. js原型链。。fuck
  7. m1芯片Mac安装pandas库(Rosetta2转译版)
  8. 【MySQL】Java.sql.SQLException Incorrect string value: \xF0\x9F\x98\x8D\xE8\xBE...
  9. Python从序列中选择k个不重复元素
  10. struts2+spring 项目中配置tiles的解决方案
  11. udp客户端 Java_java UDP通信客户端与服务器端实例分析
  12. Excel常用函数及操作_脑图
  13. 制作WIN7+XP+DOS+PE多系统启动光盘
  14. 拼接、比较与计算——哥德尔读后之十四
  15. WIN10 热点 无法连接或连接不上或手机连上了电脑没网 等问题
  16. 不撞南墙不回头——浅谈深度优先搜索(DFS)
  17. 叮叮获取所有用户信息_钉钉小程序获取用户信息
  18. Python.对鸢尾花数据集进行可视化操作,对数据分析
  19. python文件编译为pyc后运行
  20. Django搭建个人博客:重置用户密码

热门文章

  1. MSIL实用指南-返回结果
  2. centos6.8下安装破解quartus prime16.0以及modelsim ae安装
  3. NOIP2013Day1T3 表示只能过一个点
  4. 从统计局抓取2016年最新的全国区县数据!!
  5. 禁止遮罩层以下屏幕滑动----正解(更新版)
  6. POJ 2301 Beat the Spread!
  7. SQL Server 2005中, 创建维护计划时出现错误信息: 创建维护计划 失败
  8. POJ 3621 Sightseeing Cows
  9. MFC 字符串截取成数组 wcstok
  10. C# Winform只能输入数字的TextBox---补充