一、MyBatis的加载策略和缓存机制
(一)延迟加载概述
问题
通过前面的学习,我们已经掌握了MyBatis中一对一,一对多,多对多关系的配置及实现,可以实现对象的关联查询。实际开发过程中很多时候我们并不需要总是在加载用户信息时就一定要加载他的订单信息。此时就是我们所说的延迟加载。
例如:
* 在一对多中,当我们有一个用户,它有个500个订单 在查询用户的时候,要不要把关联的订单查出来? 在查询订单的时候,要不要把关联的用户查出来?
* 回答在查询用户时,用户下的订单应该是,什么时候用,什么时候查询。 在查询订单时,订单所属的用户信息应该是随着订单一起查询出来。
延迟加载:
就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载
优点:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
缺点:因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能 造成用户等待时间变长,造成用户体验下降。
在多表中: 一对多,多对多:通常情况下采用延迟加载
一对一(多对一):通常情况下采用立即加载
* 注意:延迟加载是基于嵌套查询来实现的
(二)延迟加载实现
1、 局部延迟加载
在association和collection标签中都有一个fetchType属性,通过修改它的值,可以修改局部的加载策略。
<!-- 开启一对多 延迟加载 -->
<resultMap id="customerMap" type="com.offcn.bean.Customer">
<id column="cid" property="cid"></id>
<result column="name" property="name"></result>
<!--
fetchType="lazy" 懒加载策略
fetchType="eager" 立即加载策略
-->
<collection
property="orderList"
ofType="order"
column="id"
select="com.offcn.mapper.OrderMapper.findByCid" fetchType="lazy" >
</collection>
</resultMap>
<select id="findAll" resultMap="customerMap">
SELECT * FROM `customer`
</select>
测试结果:我们查询用户的时候在没有使用关联的订单,就不会产生查询订单的语句。
2、 全局延迟加载
在MyBatis的核心配置文件中可以使用setting标签修改全局的加载策略。
<settings>
<!--开启全局延迟加载功能-->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"></setting>
</settings>
注意
局部的加载策略优先级高于全局的加载策略。
<!-- 关闭一对一 延迟加载 -->
<resultMap id="orderMap" type="com.offcn.bean.Orders">
<id column="oid" property="oid"></id>
<result column="dis" property="dis"></result>
<!--
fetchType="lazy" 懒加载策略
fetchType="eager" 立即加载策略
-->
<association
property="customer"
column="cus_fk"
javaType="com.offcn.bean.Customer"
select="com.offcn.mapper.UserMapper.findById"
fetchType="eager">
</association>
</resultMap>
<select id="findAll" resultMap="orderMap">
SELECT * from orders
</select>
(三)缓存概述
当用户频繁查询某些固定的数据时,第一次将这些数据从数据库中查询出来,保存在缓存中。当用户再次查询这些数据时,不用再通过数据库查询,而是去缓存里面查询。减少网络连接和数据库查询带来的损耗,从而提高我们的查询效率,减少高并发访问带来的系统性能问题。
一句话概括:经常查询一些不经常发生变化的数据,使用缓存来提高查询效率。
像大多数的持久化框架一样,MyBatis也提供了缓存策略,通过缓存策略来减少数据库的查询次数,从而提高性能。MyBatis中缓存分为一级缓存,二级缓存。
(四)一级缓存
1、 一级缓存简介
一级缓存是SqlSession级别的缓存,是默认开启且无法关闭的。所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。
2、 一级缓存的代码实现
@Test
public void testOneCache() throws Exception {
SqlSession sqlSession = MyBatisUtils.openSession();
CustomerMapper customerMapper= sqlSession.getMapper(CustomerMapper.class);
Customer customer1= customerMapper.findById(41);
System.out.println("第一次查询的用户:" + customer1);
Customer customer2= customerMapper.findById(41);
System.out.println("第二次查询的用户:" + customer2);
sqlSession.close();
}
我们可以发现,虽然在上面的代码中我们查询了两次,但最后只执行了一次数据库操作,这就是MyBatis提供给我们的一级缓存在起作用了。因为一级缓存的存在,导致第二次查询id为41的记录时,并没有发出sql语句从数据库中查询数据,而是从一级缓存中查询。
分析:
一级缓存是SqlSession范围的缓存,执行SqlSession的C(增加)U(更新)D(删除)操作,或者调用clearCache()、commit()、close()方法,都会清空缓存。
(1)第一次发起查询用户id为41的用户信息,先去找缓存中是否有id为41的用户信息,如果没有,从数据库查询用户信息。
(2) 得到用户信息,将用户信息存储到一级缓存中。
(3)如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了 让缓存中存储的是最新的信息,避免脏读。
(4)第二次发起查询用户id为41的用户信息,先去找缓存中是否有id为41的用户信息,缓存中有,直接从缓存中获取用户信息。
3、 清除缓存
@Test
public void testOneCache() throws Exception {
SqlSession sqlSession = MyBatisUtils.openSession();
CustomerMapper customerMapper= sqlSession.getMapper(CustomerMapper.class);
Customer customer1= customerMapper.findById(41);
System.out.println("第一次查询的用户:" + customer1);
//调用sqlSession清除缓存的方法
sqlSession.clearCache();
Customer customer2= customerMapper.findById(41);
System.out.println("第二次查询的用户:" + customer2);
sqlSession.close();
}
(五)二级缓存
1、二级缓存简介
MyBatis的二级缓存默认是不开启的,二级缓存的开启需要进行配置,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的。 也就是要求实现Serializable接口,配置方法很简单,只需要在映射XML文件配置<cache/> 就可以开启二级缓存了。
2、二级缓存的实现
(1) 配置核心配置文件
<settings>
<!--因为cacheEnabled的取值默认就为true,所以这一步可以省略不配置。 为true代表开启二级缓存;为false代表不开启二级缓存。 -->
<setting name="cacheEnabled" value="true"/>
</settings>
(2) 配置CustomerMapper.xml文件
<mapper namespace="com.offcn.mapper.CustomerMapper">
<!--当前映射文件开启二级缓存-->
<cache></cache>
<select>标签中设置useCache=”true”代表当前这个statement要使用二级缓存。 如果不使用二级缓存可以设置为false 注意:针对每次查询都需要最新的数据sql,要设置成useCache="false",禁用二级缓存。
<select id="findById" parameterType="int"
resultType="com.offcn.bean.Customer" useCache="true" >
SELECT * FROM `customer` where cid = #{cid}
</select>
</mapper>
(3) 修改Customer实体
public class Customer implements Serializable {
private Integer cid;
private String name;
}
(4) 编码测试
@Test
public void testTwoCache() throws Exception {
SqlSession sqlSession = MyBatisUtils.openSession();
CustomerMapper customerMapper= sqlSession.getMapper(CustomerMapper.class);
Customer customer = customerMapper.findById(41);
System.out.println("第一次查询的用户:" + customer );
sqlSession.close();
SqlSession sqlSession1 = MyBatisUtils.openSession();
CustomerMapper customerMapper1 = sqlSession1.getMapper(CustomerMapper.class);
Customer customer1 = customerMapper1 .findById(41);
System.out.println("第二次查询的用户:"+customer1);
sqlSession1.close();
}
运行结果分析:
二级缓存是mapper映射级别的缓存,多个SqlSession去操作同一个Mapper映射的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
映射语句文件中的所有select语句将会被缓存。
映射语句文件中的所有insert、update和delete语句会刷新缓存。