MyBatis(七)、缓存和延迟加载

MyBatis缓存

什么是缓存

存在内存中的临时数据。

为什么使用缓存

减少和数据库的交互次数,提高执行效率。

什么情况数据使用缓存

  • 适用于缓存
    • 经常查询并且不经常改变的
    • 数据的正确与否对最终结果影响不大的。[ 数据不一致 ]
  • 不适用于缓存
    • 经常改变的数据
    • 数据的正确与否对最终影响很大的 [ 库存,汇率,股市牌价 ]

一级缓存与二级缓存

一级缓存:

指的是SqlSession对象的缓存。

当我们执行查询后,查询的结果会同时存入到SqlSession的一块Map区域中。

当我们再次查询同样的数据,mybatis会先去sqlsession中查询是否有,有的话直接拿出来用。

当SqlSession对象消失,mybatis的一级缓存也就消失了。

@Test
public void testFindById() {
//6.执行操作
User user = userDao.findById(41);
System.out.println("第一次查询的用户:"+user);
User user2 = userDao.findById(41);
System.out.println("第二次查询用户:"+user2);
System.out.println(user == user2);  // true
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kaCzi6rL-1584553584750)(https://note.youdao.com/yws/res/27152/02C8D6A625CD4C2FA9E935997368A6B3)]

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

@Test
public void testFirstLevelCache(){
 User user1 = userDao.findById(41);
 System.out.println(user1);
// sqlSession.close();
 //再次获取 SqlSession 对象
// sqlSession = factory.openSession();
 sqlSession.clearCache();//此方法也可以清空缓存
 userDao = sqlSession.getMapper(IUserDao.class);
 User user2 = userDao.findById(41);
 System.out.println(user2);
 System.out.println(user1 == user2);
 }

  • 第一次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,如果没有,从数据库查
    询用户信息。
  • 得到用户信息,将用户信息存储到一级缓存中。
  • 如果 sqlSession 去执行 commit 操作(执行插入、更新、删除),清空 SqlSession 中的一级缓存,这样
    做的目的为了让缓存中存储的是最新的信息,避免脏读。
  • 第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直接从缓存
    中获取用户信息。
测试数据同步
 @Test
 public void testClearlCache(){
 //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);
 }

二级缓存

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

指的是Mybatis中SqlSessionFactory对象的缓存,由同一个SqlSessionFactory对象创建的SqlSession共享缓存。

二级缓存的使用步骤:

  • 1.让Mybatis框架支持二级缓存(在SqlMapConfig.xml中配置)
  • 2.让当前的映射文件支持二级缓存(在mapper.xml中配置)
  • 3.让当前的操作支持二级缓存(在select标签中配置)
1.
<settings>
<!-- 开启二级缓存的支持 -->
<setting name="cacheEnabled" value="true"/>
</settings>
因为 cacheEnabled 的取值默认就为 true,所以这一步可以省略不配置。为 true 代表开启二级缓存;为
false 代表不开启二级缓存。
2.
<cache>标签表示当前这个 mapper 映射将使用二级缓存,区分的标准就看 mapper 的 namespace 值。
<?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.itheima.dao.IUserDao">
<!-- 开启二级缓存的支持 -->
<cache></cache>
</mapper>
3.
<!-- 根据 id 查询 -->
<select id="findById" resultType="user" parameterType="int" useCache="true">
select * from user where id = #{uid}
</select>
将 UserDao.xml 映射文件中的<select>标签中设置 useCache=”true”代表当前这个 statement 要使用
二级缓存,如果不使用二级缓存可以设置为 false。
注意:针对每次查询都需要最新的数据 sql,要设置成 useCache=false,禁用二级缓存。
 @Test
public void testFirstLevelCache(){
 SqlSession sqlSession1 = factory.openSession();
 IUserDao dao1 = sqlSession1.getMapper(IUserDao.class);
 User user1 = dao1.findById(41);
 System.out.println(user1);
 sqlSession1.close();//一级缓存消失
 SqlSession sqlSession2 = factory.openSession();
 IUserDao dao2 = sqlSession2.getMapper(IUserDao.class);
 User user2 = dao2.findById(41);
 System.out.println(user2);
 sqlSession2.close();
 System.out.println(user1 == user2);  // false
 }
}

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

为什么两个对象地址不一样,因为二级缓存缓存的是数据不是堆对象

当我们在使用二级缓存时,
所缓存的类一定要实现 java.io.Serializable 接口,
这种就可以使用序列化方式来保存对象。
/**
* 
* <p>Title: User</p>
* <p>Description: 用户的实体类</p>
* <p>Company: http://www.itheima.com/ </p>
*/
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
}

MyBatis延迟加载

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

  • 好处:

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

  • 坏处:

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

四种表关系中
  • 一对多,多对多
    • 通常情况下采用延迟加载
  • 多对一,一对一
    • 通常情况下采用立即加载

因为采用延迟加载了,sql语句就不能立即查出来所有,先查出一个表,再关联;并且resultMap不用立即封装进去集合了

开启延迟加载

<!-- 我们需要在 Mybatis 的配置文件 SqlMapConfig.xml 文件中添加延迟加载的配置。
开启延迟加载的支持 -->
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
使用 assocation [一对一]实现延迟加载
<?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.itheima.dao.IAccountDao">
<!-- 建立对应关系 -->
<resultMap type="account" id="accountMap">
<id column="aid" property="id"/>
<result column="uid" property="uid"/>
<result column="money" property="money"/>
<!-- 它是用于指定从表方的引用实体属性的 -->
<association property="user" javaType="user"
select="com.itheima.dao.IUserDao.findById"
column="uid">
</association>
</resultMap>
<select id="findAll" resultMap="accountMap">
select * from account
</select>
</mapper>
select: 填写我们要调用的 select 映射的 id 
column :[ 必须写 ] 填写我们要传递给 select 映射的参数


<?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.itheima.dao.IUserDao">
<!-- 根据 id 查询 -->
<select id="findById" resultType="user" parameterType="int" >
select * from user where id = #{uid}
</select>
</mapper>


使用 Collection [一对多]实现延迟加载
<resultMap type="user" id="userMap">
<id column="id" property="id"></id>
<result column="username" property="username"/>
<result column="address" property="address"/>
<result column="sex" property="sex"/>
<result column="birthday" property="birthday"/>
<!-- collection 是用于建立一对多中集合属性的对应关系
ofType 用于指定集合元素的数据类型
select 是用于指定查询账户的唯一标识(账户的 dao 全限定类名加上方法名称)
column 是用于指定使用哪个字段的值作为条件查询
-->
<collection property="accounts" ofType="account"
select="com.itheima.dao.IAccountDao.findByUid"
column="id">
</collection>
</resultMap>
<!-- 配置查询所有操作 -->
<select id="findAll" resultMap="userMap">
select * from user
</select>

<collection>标签:
主要用于加载关联的集合对象
select 属性:
用于指定查询 account 列表的 sql 语句,所以填写的是该 sql 映射的 id
column 属性:
用于指定 select 属性的 sql 语句的参数来源,上面的参数来自于 user 的 id 列,所以就写成 id 这一
个字段名了


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

总结:在用的时候去调用对方
配置文件中的方法

posted @ 2020-03-19 01:47  Biturd  阅读(18)  评论(0编辑  收藏  举报