Mybatis延迟加载和查询缓存
一、延迟加载
resultMap 可以实现高级映射 (使用 association、collection 实现一对一及一对多映射),association、collection 具备延迟加载功能。
延迟加载:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
在 mybatis 核心配置文件中配置:
lazyLoadingEnabled、aggressiveLazyLoading
设置项 |
描述 |
允许值 |
默认值 |
lazyLoadingEnabled |
全局性设置懒加载。如果设为‘false’,则所有相关联的都会被初始化加载。 |
true | false |
false |
aggressiveLazyLoading |
当设置为‘true’的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载。 |
true | false |
true |
<settings><span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">setting </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="lazyLoadingEnabled"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="true"</span><span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">setting </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="aggressiveLazyLoading"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="false"</span><span style="color: rgba(0, 0, 255, 1)">/></span>
</settings>
场合:
当只有部分记录需要关联查询其它信息时,此时可按需延迟加载,需要关联查询时再向数据库发出 sql,以提高数据库性能。
当全部需要关联查询信息时,此时不用延迟加载,直接将关联查询信息全部返回即可,可使用 resultType 或 resultMap 完成映射。
二、查询缓存
Mybatis 提供查询缓存,用于减轻数据压力,提高数据库压力。
Mybatis 提供一级缓存和二级缓存。
在操作数据库时需要构造 SqlSession 对象,在对象中有一个数据结构 (HashMap) 用于缓存数据。
不同的 SqlSession 之间的缓存数据区域是互相不影响的。
Mybatis 一级缓存的作用域是同一个 SqlSession,在同一个 sqlSession 中两次执行相同的 sql 语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个 sqlSession 结束后该 sqlSession 中的一级缓存也就不存在了。Mybatis 默认开启一级缓存。
Mybatis 二级缓存是多个 SqlSession 共享的,其作用域是 mapper 的同一个 namespace,不同的 sqlSession 两次执行相同 namespace 下的 sql 语句且向 sql 中传递参数也相同即最终执行相同的 sql 语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。Mybatis 默认没有开启二级缓存需要在 setting 全局参数中配置开启二级缓存。
1、一级缓存
第一次发起查询,先去找缓存中是否有 id 为 1 的用户信息,如果没有,从数据库中查询用户信息。
得到用户信息,将用户信息存储到一级缓存中。
第二次发起查询用户 id 为 1 的用户信息,先去缓存中是否有 id 为 1 的用户信息,缓存中有,直接从缓存中获取用户信息。
如果 SqlSession 去执行 commit 操作 (执行插入、更新、删除),清空 SqlSession 中的一级缓存。目的是为了让缓存中存储的是最新的信息,避免脏读。
Mybatis 默认支持一级缓存,不需要在配置文件中配置。
Mybatis 内部存储缓存使用一个 HashMap,key 为 hashCode+sqlId+Sql 语句。value 为从查询出来映射生成的 java 对象。
应用场景:
2、二级缓存
SqlSession1 去查询用户 id 为 1 的用户信息,查询到用户信息会将查询数据存储到二级缓存中。
SqlSession2 去查询用户 id 为 1 的用户信息,去缓存中找是否存在数据,如果存在直接从缓存中取出数据。
二级缓存区域是根据 mapper 的 namespace 划分的,相同 namespace 的 mapper 查询数据放在同一个区域,如果使用 mapper 代理方法每个 mapper 的 namespace 都不同,此时可以理解为二级缓存区域是根据 mapper 划分。
每次查询会先从缓存区域找,如果找不到从数据库查询,查询到数据将数据写入缓存。
Mybatis 内部存储缓存使用一个 HashMap,key 为 hashCode+sqlId+Sql 语句。value 为从查询出来映射生成的 java 对象
sqlSession 执行 insert、update、delete 等操作 commit 提交后会清空缓存区域。
开启二级缓存:
在核心配置文件 SqlMapConfig.xml 中加入
<setting name="cacheEnabled" value="true"/>
描述 |
允许值 |
默认值 |
|
cacheEnabled |
对在此配置文件下的所有 cache 进行全局性开 / 关设置。 |
true false |
true |
要在你的 Mapper 映射文件中添加一行: <cache /> ,表示此 mapper 开启二级缓存。
二级缓存需要查询结果映射的 pojo 对象实现 java.io.Serializable 接口实现序列化和反序列化操作,注意如果存在父类、成员 pojo 都需要实现序列化接口。
为了将缓存数据取出执行反序列化,因为二级缓存存储介质多种多样,不一定在内存。
禁用二级缓存:
在 statement 中设置 useCache=false 可以禁用当前 select 语句的二级缓存,即每次查询都会发出 sql 去查询,默认情况是 true,即该 sql 使用二级缓存。
<select id="findOrderListResultMap" resultMap="ordersUserMap" useCache="false">
刷新缓存 (就是清空缓存):
设置 statement 配置中的 flushCache="true" 属性,默认情况下为 true 即刷新缓存,如果改成 false 则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。
<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User" flushCache="true">
应用场景:
对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用 mybatis 二级缓存技术降低数据库访问量,提高访问速度,业务场景比如:耗时较高的统计分析 sql、电话账单查询 sql 等。
实现方法如下:通过设置刷新间隔时间,由 mybatis 每隔一段时间自动清空缓存,根据数据变化频率设置缓存刷新间隔 flushInterval,比如设置为 30 分钟、60 分钟、24 小时等,根据需求而定。
局限性:
mybatis 二级缓存对细粒度的数据级别的缓存实现不好,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用 mybatis 的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为 mybaits 的二级缓存区域以 mapper 为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存。