Mybatis学习(七)
9.延迟加载
9.1 什么是延迟加载
resultMap中的association和collection标签具有延迟加载的功能。
延迟加载的意思是说,在关联查询时,利用延迟加载,先加载主信息。需要关联信息时再去按需加载关联信息。这样会大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
9.2 设置延迟加载
mybatis默认是不开启延迟加载功能的,我们需要手动开启.需要在SqlMapConfig.xml文件中,在<settings>标签中开启延迟加载功能.
| 设置项 | 描述 | 允许值 | 默认值 |
| lazyLoadingEnabled | 全局性设置懒加载。如果设为'false',则所有相关联的都会被初始化加载。 | true|false | false |
| aggressiveLazyLoading | 当设置为'true'的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载。 | true|false | true |
开启延迟加载
1 <settings> 2 <setting name="lazyLoadingEnabled" value="true"/> 3 <setting name="aggressiveLazyLoading" value="false"/> 4 </settings>
9.3 需求
查询订单信息,关联查询用户信息(对用户信息的加载要求是按需加载)
先实现一个statement完成查询订单信息的需求
在实现一个statement完成查询用户信息的需求
9.4 映射文件
<resultMap type="com.jxdd.mybatis.po.OrdersExt" id="LazyLoadingRstMap"> <!-- 订单信息 --> <id column="id" property="id" /> <result column="user_id" property="userId" /> <result column="number" property="number" /> <!-- 用户信息 (一对一 ) --> <!-- select:关联查询的sql对应的statement的id --> <!-- column:select关联的statement的输入参数由哪个列的查询结果去传入 --> <association property="user" column="user_id" select="com.jxdd.mybatis.service.UserMapper.findUserById"></association> </resultMap> <!-- 延迟加载 --> <select id="findOrdersUserLazyLoading" resultMap="LazyLoadingRstMap"> SELECT * FROM orders </select>
上边先去执行findOrdersUserLazyLoading,当需要去查询用户的时候再去执行findUserById,通过resultMap的定义将延迟加载执行配置起来。
9.5 mapper接口
1 // 查询订单信息,延迟加载关联查询的用户信息 2 public List<Orders> findOrdersUserLazyLoading();
9.6 测试代码
1 @Test 2 public void findOrdersUserLazyLoadingTest(){ 3 // 创建sqlSession 4 SqlSession sqlSession = sqlSessionFactory.openSession(); 5 6 // 通过SqlSession构造usermapper的代理对象 7 OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class); 8 List<OrdersExt> list = ordersMapper.findOrdersUserLazyLoading(); 9 //按需加载用户信息 10 for (OrdersExt ordersExt : list) { 11 System.out.println(ordersExt.getUser()); 12 } 13 // 释放SqlSession 14 sqlSession.close(); 15 }
如果注释了按需加载

如果按需加载用户信息

使用延迟加载方法,先去查询简单的sql(最好单表,也可以关联查询),再去按需要加载关联查询的其它信息。
10.查询缓存
10.1 mybatis缓存分析
mybatis提供查询缓存,如果缓存中有数据就不用从数据库中获取,用于减轻数据压力,提高系统性能。

一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
10.2 一级缓存
10.2.1 原理

第一次发起查询用户id为1的用户信息,先去找缓存是否有id为1的用户信息,如果没有从数据库查询用户信息.
如果sqlSession去执行commit操作(执行插入、更新、删除),清空sqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读.
第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息.
mybatis默认支持一级缓存
10.2.2 测试
1 @Test 2 public void oneLevelCacheTest(){ 3 SqlSession sqlSession = sqlSessionFactory.openSession(); 4 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 5 User user = userMapper.findUserById(1); 6 System.out.println(user); 7 User user2 = userMapper.findUserById(1); 8 System.out.println(user2); 9 sqlSession.close(); 10 }

10.2.3 测试2
1 @Test 2 public void oneLevelCacheTest2() { 3 SqlSession sqlSession = sqlSessionFactory.openSession(); 4 UserMapper mapper = sqlSession.getMapper(UserMapper.class); 5 // 第一次查询ID为1的用户,去缓存找,找不到就去查找数据库 6 User user1 = mapper.findUserById(1); 7 System.out.println(user1); 8 9 User user = new User(); 10 user.setUsername("qt1"); 11 user.setAddress("北京北京"); 12 //执行增删改操作,清空缓存 13 mapper.insertUser(user); 14 15 // 第二次查询ID为1的用户 16 User user2 = mapper.findUserById(1); 17 System.out.println(user2); 18 19 sqlSession.close(); 20 }

10.2.4 应用
正式开发,是将mybatis和spring进行整合开发,事务控制在service中。
一个service方法中包括 很多mapper方法调用。
service{
//开始执行时,开启事务,创建SqlSession对象
//第一次调用mapper的方法findUserById(1)
//第二次调用mapper的方法findUserById(1),从一级缓存中取数据
//方法结束,sqlSession关闭
}
如果是执行两次service调用查询相同 的用户信息,不走一级缓存,因为session方法结束,sqlSession就关闭,一级缓存就清空。
10.3 二级缓存
10.3.1 原理

二级缓存是mapper级别的。
第一次调用mapper下的SQL去查询用户信息。查询到的信息会存到该mapper对应的二级缓存区域内。
第二次调用相同namespace下的mapper映射文件中相同的SQL去查询用户信息。会去对应的二级缓存内取结果。
如果调用相同namespace下的mapper映射文件中的增删改SQL,并执行了commit操作。此时会清空该namespace下的二级缓存。
Mybatis默认是没有开启二级缓存
10.3.2 开启二级缓存
1. 在核心配置文件SqlMapConfig.xml中加入以下内容(开启二级缓存总开关):在settings标签中添加以下内容:
1 <!-- 开启二级缓存总开关 --> 2 <setting name="cacheEnabled" value="true"/>
2.在UserMapper映射文件中,加入以下内容,开启二级缓存:
1 <!-- 开启本mapper下的namespace的二级缓存,默认使用的是mybatis提供的PerpetualCache --> 2 <cache></cache>
10.3.3 实现序列化
由于二级缓存的数据不一定都是存储到内存中,它的存储介质多种多样,所以需要给缓存的对象执行序列化。
如果该类存在父类,那么父类也要实现序列化。
1 package com.jxdd.mybatis.po; 2 3 import java.io.Serializable; 4 import java.util.Date; 5 6 public class User implements Serializable{ 7 /** 8 * 9 */ 10 private static final long serialVersionUID = 1L; 11 private int id; 12 private String username; 13 private String sex; 14 private Date birthday; 15 private String address; 16 17 public int getId() { 18 return id; 19 } 20 21 public void setId(int id) { 22 this.id = id; 23 } 24 25 public String getUsername() { 26 return username; 27 } 28 29 public void setUsername(String username) { 30 this.username = username; 31 } 32 33 public String getSex() { 34 return sex; 35 } 36 37 public void setSex(String sex) { 38 this.sex = sex; 39 } 40 41 public Date getBirthday() { 42 return birthday; 43 } 44 45 public void setBirthday(Date birthday) { 46 this.birthday = birthday; 47 } 48 49 public String getAddress() { 50 return address; 51 } 52 53 public void setAddress(String address) { 54 this.address = address; 55 } 56 57 @Override 58 public String toString() { 59 return "User [id=" + id + ", username=" + username + ", sex=" + sex 60 + ", birthday=" + birthday + ", address=" + address + "]"; 61 } 62 }
10.3.4 测试1
1 @Test 2 public void twoLevelCacheTest() { 3 SqlSession sqlSession1 = sqlSessionFactory.openSession(); 4 SqlSession sqlSession2 = sqlSessionFactory.openSession(); 5 6 UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class); 7 UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class); 8 // 第一次查询ID为1的用户,去缓存找,找不到就去查找数据库 9 User user1 = mapper1.findUserById(1); 10 System.out.println(user1); 11 // 关闭SqlSession1 12 sqlSession1.close(); 13 14 // 第二次查询ID为1的用户 15 User user2 = mapper2.findUserById(1); 16 System.out.println(user2); 17 // 关闭SqlSession2 18 sqlSession2.close(); 19 }

Cache Hit Radio : 缓存命中率
第一次缓存中没有记录,则命中率0.0;
第二次缓存中有记录,则命中率0.5(访问两次,有一次命中)
10.3.5 测试2
1 @Test 2 public void twoLevelCacheTest2() { 3 SqlSession sqlSession1 = sqlSessionFactory.openSession(); 4 SqlSession sqlSession2 = sqlSessionFactory.openSession(); 5 SqlSession sqlSession3 = sqlSessionFactory.openSession(); 6 7 UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class); 8 UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class); 9 UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class); 10 // 第一次查询ID为1的用户,去缓存找,找不到就去查找数据库 11 User user1 = mapper1.findUserById(1); 12 System.out.println(user1); 13 // 关闭SqlSession1 14 sqlSession1.close(); 15 16 //修改查询出来的user1对象,作为插入语句的参数 17 user1.setUsername("qt2"); 18 user1.setAddress("qqqq"); 19 20 mapper3.insertUser(user1); 21 22 // 提交事务 23 sqlSession3.commit(); 24 // 关闭SqlSession3 25 sqlSession3.close(); 26 27 // 第二次查询ID为1的用户 28 User user2 = mapper2.findUserById(1); 29 System.out.println(user2); 30 // 关闭SqlSession2 31 sqlSession2.close(); 32 }

根据执行结果可以,在执行了commit操作后,二级缓存清空了
10.3.6 禁用二级缓存
该statement中设置userCache=false,可以禁用当前select语句的二级缓存,即每次查询都是去数据库中查询,默认情况下是true,即该statement使用二级缓存
10.3.7 刷新二级缓存
该statement中设置flushCache=true可以刷新当前的二级缓存,默认情况下如果是select语句,那么flushCache是false。如果是insert、update、delete语句,那么flushCache是true。
如果查询语句设置成true,那么每次查询都是去数据库查询,即意味着该查询的二级缓存失效。
如果查询语句设置成false,即使用二级缓存,那么如果在数据库中修改了数据,而缓存数据还是原来的,这个时候就会出现脏读。
10.3.8 使用场景
使用场景:对于访问响应速度要求高,但是实时性不高的查询,可以采用二级缓存技术。
注意:在使用二级缓存的时候,要设置一下刷新间隔(cache标签中有一个flashInterval属性)来定时刷新二级缓存,这个刷新间隔根据具体需求来设置,比如设置30分钟、60分钟等,单位为毫秒。
10.3.9 局限性
Mybatis二级缓存对细粒度的数据级别的缓存实现不好。
场景:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次查询都是最新的商品信息,此时如果使用二级缓存,就无法实现当一个商品发生变化只刷新该商品的缓存信息而不刷新其他商品缓存信息,因为二级缓存是mapper级别的,当一个商品的信息发
送更新,所有的商品信息缓存数据都会清空。
解决此类问题,需要在业务层根据需要对数据有针对性的缓存。
比如可以对经常变化的 数据操作单独放到另一个namespace的mapper中。

浙公网安备 33010602011771号