一、延迟加载
在需要用到数据时才去加载,又称为懒加载,先从单表查询,需要时再从关联表中去关联查询。
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,作用是如果事务提交,对缓存的操作才会生效,如果事务回滚或者不提交事务,则不对缓存产生影响。