mybatis - 延迟、缓存

本文详细介绍了MyBatis框架中的延迟加载策略(一对一和一对多)以及缓存机制(一级和二级缓存),包括如何配置和处理脏读、事务性等问题。
摘要由CSDN通过智能技术生成

一、延迟加载

在需要用到数据时才去加载,又称为懒加载,先从单表查询,需要时再从关联表中去关联查询。

1. 关联对象加载的时机
i) 直接加载:执行完对主加载对象的select语句,马上执行对关联对象的select查询。
ii) 侵入式延迟:执行对主加载对象的查询时,不会执行对关联对象的查询。但当要访问主加载对象的详情时,就会马上执行关联对象的select查询。。
iii) 深度延迟:只有当真正访问关联对象的详情时,才会执行对关联对象的select查询。

注:延迟加载的应用要求,关联对象的查询与主加载对象的查询必须是分别进行的select语句,不能是使用多表连接所进行的select查询。

需要在config.xml中配置

 <settings> 
  	<setting name="lazyLoadingEnabled" value="true"/>     <!--开启延迟加载 -->
  	<!-- 下面如果为true,则是侵入式延迟-->
  	<setting name="aggressiveLazyLoading" value="false"/>  
</settings> 

2. 一对一延迟加载

	<!-- 使用resultMap实现一对一延迟加载 -->
	<select id="queryPersonOneByOne2"  resultMap = "person_class_lazyload_map" >
		select * from person 
	</select>	
	<resultMap type= "Person" id="person_class_lazyload_map">
		<!-- 学生的信息 -->
		<id property="id" column="id"/>
	  	<result property="name" column="name"/>
	  	<result property="age" column="age"/>

	  	<!-- 一对一时,对象成员使用association映射, javaType指定该属性的类型
	  		此次采用延迟加载,在加载学生时,并不立即加载班级信息-->
	  	 <!-- 班级,通过select在需要的时候查班级 -->
	  	<association property = "classid"  javaType="Classes" 	
                         select="org.mapper.classMapper.queryClassById"  column = "classid">	
	  	</association>
	</resultMap>

注:conlumn 用于指定select 属性的sql 语句的参数来源,在一对一时,常指外键classid,在一对多时,常指自身id

若想使用延迟加载,则需要将查询方式改成单独的查询方式。在classMapper.xml中

	<!-- 查询班级信息 -->
	<select id="queryClassById" parameterType="int" resultType="Classes">
		<!-- 查询学生对应的班级 -->
		select * from class where id=#{classid}
	</select>

3. 一对多延迟加载

<select id="findAll"  resultMap = "class_person_lazyload_map" >
	select * from Classes
</select>	
<resultMap type= "Classes" id="class_person_lazyload_map">
	<!-- 班级的信息 -->
	<id property="id" column="id"/>

  	<collection property = "persons"  ofType="Person" 	
                     select="org.mapper.personMapper.querypersonById"  column = "id">	
  	</collection>
</resultMap>

注:conlumn 用于指定select 属性的sql 语句的参数来源,在一对一时,常指外键classid,在一对多时,常指自身id

	<!-- 查询学生信息 -->
	<select id="querypersonById" parameterType="int" resultType="Person">
		<!-- 查询学生对应的班级 -->
		select * from person where classid=#{classid}
	</select>

延迟加载原理实现:
延迟加载主要是通过cglib动态代理的形式实现,创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,执行数据加载。MyBatis延迟加载主要使用:Javassist,Cglib实现,类图展示:
在这里插入图片描述

二、缓存机制

为减少数据库的查询次数,分为一级缓存和二级缓存

1. 一级缓存:自动开启

一级缓存是SqlSession级别的缓存,只要SqlSession没有flush或者close,它就存在。如果是相同的SQL语句,会优先命中一级缓存,避免直接对数据库进行查询,提高性能。
在这里插入图片描述

每个SqlSession中持有了Executor,每个Executor中有一个LocalCache。当用户发起查询时,根据当前执行的语句生成MappedStatement,在Local Cache进行查询,如果缓存命中,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入Local Cache,最后返回结果给用户。

两个级别

  • session 级别的缓存,在同一个 sqlSession 内,对同样的查询将不再查询数据库,直接从缓存中。
  • statement 级别的缓存,每次查询结束都会清掉一级缓存。

小结

  • MyBatis一级缓存的生命周期和SqlSession一致。
  • MyBatis一级缓存内部设计简单,只是一个没有容量限定的HashMap。
  • MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement。
  • SqlSession中执行了任何一个操作(update()、delete()、insert()) ,都会清空local Cache对象的数据,但是该对象可以继续使用;原因:在BaseExecutor中的update()方法中调用clearLocalCache()方法,清除本地缓存。

2. 二级缓存:用到装饰器模式

范围是namespace 级别的,可以被多个SqlSession 共享(只要是同一个接口里面的相同方法,都可以共享),生命周期和应用同步。MyBatis查询数据的顺序是:二级缓存 —> 一级缓存 —> 数据库。

MyBatis 用了一个装饰器的类来维护,就是CachingExecutor。如果启用了二级缓存,MyBatis 在创建Executor 对象的时候会对Executor 进行装饰。
在这里插入图片描述

  • 映射语句文件中的所有 select 语句的结果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。

原因:在CachingExecutor 的update()方法里面会调用下面方法:

private void flushCacheIfRequired(MappedStatement ms) {
 	Cache cache = ms.getCache();    
  	if (cache != null && ms.isFlushCacheRequired()) {      
      	tcm.clear(cache);
   }
}

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

2.1 配置
i) 在config.xml配置文件中开启二级缓存。

 <setting name="cacheEnabled" value="true"/>

ii) 在MyBatis的映射XML中配置cache或者 cache-ref ,开启二级缓存的支持。

<cache/>

注:如果某些查询方法对数据的实时性要求很高,不需要二级缓存,可在单个sql上显式关闭二级缓存(默认是true):

<select id="selectBlog" resultMap="BaseResultMap" useCache="false">

2.2 脏读问题
MyBatis的二级缓存不适应用于映射文件中存在多表查询的情况。通常为每个单表创建单独的映射文件,由于MyBatis的二级缓存是基于namespace的,多表查询语句所在的namspace无法感应到其他namespace中的语句对多表查询中涉及的表进行的修改,引发脏数据问题。

解决:
使用Cache ref,eg: 让ClassMapper引用StudenMapper命名空间,这样两个映射文件对应的Sql操作都使用的是同一块缓存了。
即在StudentMapper.xml映射文件中配置:

2.3 事务性:事务不提交,二级缓存不存在

TransactionalCacheManager中持有了一个Map:

private Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();

这个Map保存了Cache和用TransactionalCache包装后的Cache的映射关系。

TransactionalCache实现了Cache接口,CachingExecutor会默认使用他包装初始生成的Cache,作用是如果事务提交,对缓存的操作才会生效,如果事务回滚或者不提交事务,则不对缓存产生影响。

  • 27
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值