懒加载
假设我们现在有一个需求:查询订单信息,有时候需要关联查询用户信息
第一种方法:直接关联查出用户的信息
1 |
|
分析:
①、这里我们一次查询出所有的信息,需要什么信息的时候直接从查询的结果中筛选。但是如果订单和用户表都比较大的时候,这种关联查询肯定比较耗时。
②、我们的需求是有时候需要关联查询用户信息,这里不是一定需要用户信息的。即有时候不需要查询用户信息,我们也查了,程序进行了多余的耗时操作。
第二种方法:分步查询,首先查询出所有的订单信息,然后如果需要用户的信息,我们在根据查询的订单信息去关联用户信息
1 2 3 4 |
|
分析:
①、这里两步都是单表查询,执行效率比关联查询要高很多
②、分为两步,如果我们不需要关联用户信息,那么我们就不必执行第二步,程序没有进行多余的操作。
这里的第二种方法就是MyBatis的懒加载。
什么是懒加载?
通俗的讲就是按需加载,我们需要什么的时候再去进行什么操作。而且先从单表查询,需要时再从关联表去关联查询,能大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。在mybatis中,resultMap可以实现高级映射(使用association、collection实现一对一及一对多映射),association、collection具备延迟加载功能。
需求:通过查询订单信息得到关联的用户信息
分析:主表:Orders 关联表:User
(1)分别在User类中添加订单属性,在Orders类中添加用户属性
(2)在OrderMapper接口和对应的xml文件添加对应查询方法(编写主表查询映射)
<!-- 懒加载通过订单关联用户--> <select id="getOrdersById" parameterType="int" resultMap="resultOrdersUserResult"> select o.id, o.user_id, o.createtime, o.number, o.note from orders o where o.id=#{orderId} </select> <resultMap id="ordersResult" type="MavenLG.bean.Orders"> <id column="id" property="id"/> <result column="user_id" property="userId"/> <result column="number" property="number"/> <result column="createtime" property="createtime" javaType="java.util.Date" jdbcType="TIMESTAMP"/> <result column="note" property="note"/> </resultMap> <resultMap id="resultOrdersUserResult" extends="ordersResult" type="MavenLG.bean.Orders"> <association property="user" javaType="MavenLG.bean.User" fetchType="lazy" column="{id=user_id}" select="MavenLG.mapper.UserMapper.getUserById"> </association> </resultMap>
(3)在UserMapper接口和对应的xml文件添加对应查询方法(编写从表查询映射)
<!--懒加载关联--> <select id="getUserById" resultType="MavenLG.bean.User"> select * from user where id = #{id} </select>
(4)在全局配置文件中启动懒加载
结论:
执行的结果分析:分成两步分别查询SQL操作:
1、select o.id, o.user_id, o.createtime, o.number, o.note from orders o where o.id=?
2、select * from user where id = ?
缓存机制
MyBatis的缓存机制的好处在于减少数据库访问频率,提高数据库性能,MyBatis的缓存分为两种:一级缓存和二级缓存。
一级缓存:SqlSession,二级缓存:二级缓存和namespace绑定(二级缓存必须要调用close方法关闭SqlSession,二级缓存才会将查询数据保存)。
在数据操作的时候,是需要构建SQLSession对象,在SQLSession对象中有一个数据(HashMap)用户在存储数据时,不同的SQLSession是有不同的缓存的额数据区域,他们是不互相影响的。
一级缓存(SqlSession)
一级缓存是SQLSession级别缓存,默认是开启的,所以在同一个SQLSession对象操作时,对同一个SQL来执行时,只需要查询一次数据库。
缓存执行的原理:
当用户发起查询操作时,在执行器上会先查询一级缓存是否存在该查询的记录,存在则将记录直接返回给用户,当记录在缓存中不存在时,则查询数据库,将数据库返回的结果记录到缓存中,并将结果返回给用户。
示例:在测试中多次调用getUserById方法,两次传参的参数一样,且是同一个SqlSession调用的。
输出日志如下:
我们可以看出,我们进行了两次调用getUserById方法,但事实上,只查询了一次。现在,我们再进行一次查询,与之前不同的是,我们在两次查询中间进行一次更新操作:
输出日志:
我们发现,中间进行了一次变更操作,getUserById方法进行了两次查询。所以。我们可以得到以下结论:
一级缓存的写入时机和删除时机:缓存机制是对查询操作(select)起作用,对变更操作(insert\update\delete)不起作用,缓存写入的时机是查询后写入到缓存中,当发生变更操作时,会将缓存数据清除,防止再次读取缓存中的脏数据,当再次发生查询操作时,从数据库中读取数据,将数据写入缓存,保证读取的数据是最新的数据。
我们也可以进行测试,利用不同的SqlSession调用同一个方法(参数相同),会发现:仍然会进行两次查询,表明:不同SQLSession之间的缓存是相互隔离的。
二级缓存
二级缓存是在mapper层面上,多个SQLSession去操作同一个mapper对象的SQL语句,多个SQLSession是可以共用一个二级缓存,二级缓存是跨SQLSession的。二级缓存默认是不开启的,二级缓存的开启是需要在全局配置文件上进行配置,实现二级缓存时 ,mybatis要求返回的映射对象必须是可序列化的,即要求对象实现Serializable接口。
过程分析:
第一次查询id为3 的订单信息,此时先去二级缓存查找,如果查找不到,则去数据库查询,把查询后的结果存储到二级缓存中;第二次查询id为3 的订单信息,此时先去二级缓存查找,如果查找到,则直接从二级缓存中把数据取出,不去查询数据库,只要中间发生增删改操作,那么二级缓存就清空。
一级缓存与二级缓存的作用范围图:
关系:当开启了二级缓存时,那么一级缓存就失效了,大家都共享二级缓存,相当于没有一级缓存,不管干什么都是对二级缓存进行操作。这里跟hibernate的缓存有区别,不要搞混淆了。
二级缓存的使用示例:
1、在全局配置文件中开启缓存配置
<settings> <!--开启二级缓存--> <setting name="cacheEnable" value="true"/> </settings>
2、bean对象实现可序列化
public class Orders implements Serializable { //todo }
3、xml配置中开启二级缓存
<!--二级缓存配置--> <!--eviction属性:代表缓存的回收策略 mybatis提供的策略有: LRU:最近最少使用,一般长时间不用的对象 FIFO:先进先出策略,按照对象的进入缓存顺序处理 SOFT:软引用 weak:若引用 flushInterval属性:刷新的间隔实现 --> <cache eviction="LRU" flushInterval="1000" /> <select id="getOrderById" useCache="true" resultType="MavenLG.bean.Orders"> select * from orders where id = #{id} </select>
标签说明:
设置flushCache=true可以刷新当前的二级缓存,默认情况下select语句:flushCache是false,也就是默认情况下,select语句是不会刷新缓存的。如果设置成true,那么每次查询都市去数据库查询,意味着查询的二级缓存失效。insert、update、delete语句:flushCache是true,也就是默认情况下,增删改是会刷新缓存的。如果增删改设置为false,即使用二级缓存,那么如果在数据库中修改了数据,而缓存数据还是原来的,这个时候就会出现问题。所以一般不用手动设置,使用默认的即可。
设置userCache=false,可以禁用当前select语句的二级缓存,即每次查询都是去数据库中查询,默认情况下是true,即该statement使用二级缓存。
进行测试:
@Test public void testGetOrderId(){ //二级缓存测试 //创建后一个SQLSession实例 SqlSession sqlSession = sqlSessionFactory.openSession(true); OrderMapper mapper = sqlSession.getMapper(OrderMapper.class); //第一次调用getOrdersById方法 mapper.getOrderById(3); sqlSession.close(); System.out.println("第一次调用结束"); //创建后一个SQLSession实例 SqlSession sqlSession1 = sqlSessionFactory.openSession(true); OrderMapper mapper1 = sqlSession1.getMapper(OrderMapper.class); //第一次调用getOrdersById方法 mapper1.getOrderById(3); sqlSession1.close(); System.out.println("第一次调用结束"); }
输出日志:
我们可以发现:虽然是两个不同的SqlSession调用,数据库查询仍然只有一次。
总结:
二级缓存对细粒度的数据缓存效果不好,什么意思呢?
场景:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次查询都是最新的商品信息,此时如果使用二级缓存,就无法实现当一个商品发生变化只刷新该商品的缓存信息而不刷新其他商品缓存信息,因为二级缓存是mapper级别的,当一个商品的信息发送更新,所有的商品信息缓存数据都会清空。
解决此类问题,需要在业务层根据需要对数据有针对性的缓存,比如可以对经常变化的 数据操作单独放到另一个namespace的mapper中。
参考:https://www.cnblogs.com/whgk/p/6722497.html
https://www.cnblogs.com/ysocean/p/7336945.html?utm_source=debugrun&utm_medium=referral