1.什么是查询缓存:
mybaitis也提供了查询缓存,用于减少数据库访问次数,提高了数据库的性能而存在的一种机制。有一级、二级缓存。
一级缓存:操作数据库时,会操作会话SqlSession对象,在会话对象中有个内存(数据结构HashMap),用于存储缓存数据。
不同的SqlSession他们之间的缓存数据区域是互补影响的。
二级缓存:Mapper级别的缓存,多个SqlSession去操作同一个Mapper的SQL语句。多个SQLSession操作数据库所得到数据会存储在二级缓存区域,可以共用二级缓存,二级缓存是跨SqlSession的。
一级缓存:
第一次发起查询用户id为1的用户信息,先去缓存中找是否有id为1的用户信息,如果没有,会到数据库中查询出来,并存储到一级缓存中。
如果在第一次查询和第二次查询中间有操作事务操作,一级缓存里的数据被自动清空。如果不清除,第二次查询到的还是第一次的数据。为了让缓存中永远存储最新的查询数据,也就是为了避免脏数据。
第二次发起查询用户id为1的用户信息,先去缓存中找是否有id为1的用户信息,查到有,会直接返回出来。
缓存的作用:如果缓存中有需要查询的数据,就不需要去数据库中查找,减少对数据库的访问。
一级缓存测试:
mybaitis和hibernate一样默认开启一级缓存,无须配置。
按照上面一级缓存原理去进行测试。
/**
* 首先说明一点,一级缓存就是SqlSession级别的缓存
* 一级缓存工作原理:第一次查询的时候,会先去sqlSessionX的缓存查询数据是否存在,
* 不存在去数据库查,查到放入sqlSessionX缓存。第二次查同一个ID的数据时,还是先去sqlSessionX缓存找,找到就返回出来。
*/
public static void testcache1(){
SqlSession sqlSessionX = MybaitisUtil.getSQLSession();
UserMapper userMapper = sqlSessionX.getMapper(UserMapper.class);
User user1 = userMapper.selectByuid(1);
System.out.println(user1);
User user2 = userMapper.selectByuid(2);//其他ID
System.out.println(user2);
User user3 = userMapper.selectByuid(1);
System.out.println(user3);
Mybatis.close(sqlSessionX);//关闭之后,再次创建依然是先去session找,肯定没有,去数据库查找数据。
}
打印结果:
public static void testcache1(){
SqlSession sqlSessionX = MybaitisUtil.getSQLSession();
UserMapper userMapper = sqlSessionX.getMapper(UserMapper.class);
User user1 = userMapper.selectByuid(1);
System.out.println(user1);
//进行事务操作
user1.setUsername("zhangsan");
userMapper.updateuser(user1);
sqlSessionX.commit(); //提交事务后,在缓存中的数据会被自动清除。所以,下面的查询会再次去数据库查询。
User user2 = userMapper.selectByuid(1);
System.out.println(user2);
Mybatis.close(sqlSessionX);
}
打印结果:
在spring和mybaitis的开发中,事务控制在Service层,一个Service包括了多个mapper的调用,
Service{
//开始执行时,开启事务,创建SqlSession对象,
//第一次调用mapper.findbyId(1);
//第二次调用mapper.findbyId(1);//从一级缓存读取,
//方法结束,SqlSession关闭。
}
如果是执行多次service调用查询相同用户信息,不会搞这个一级缓存,因为SqlSession方法结束,SqlSession就关闭,一级缓存当然就清空!所以,需要使用二级缓存(mapper级别的缓存)。
SqlSession1去查询id为1的用户信息,查询到的用户信息会将查询数据存储到二级缓存。
如果SqlSession3去执行相同mapper下的sql,执行commit提交,清空该mapper下的二级缓存区域的数据。
SqlSession2去查询id为2的用户信息,去缓存中查找,是否存在数据,如果存在直接从二级缓存中取出数据。
二级缓存和一级缓存的区别,二级缓存的范围大,多个sqlsession可以共享一个UserMapper的二级缓存区域。
UserMapper有一个二级缓存区域(按照namespace分)其他也有自己的缓存区域。
每一个namespace的mapper都有一个二级缓存区域,两个mapper的namespace如果相同,那么查询到的数据就会放在同一个二级缓存中。
mybatis的二级缓存的准备工作:
1、mybatis-config.xml 添加:<setting name="cacheEnabled" value="true"/>
2、Usermapper.xml 添加:<cache/>
3、pojo需要序列化。
测试代码:
/**
* 二级缓存测试:
* 二级缓存是mapper级别的缓存,创建三个usermapper对象,
* 第一个usermapper去二级缓存查看数据,没有找到去数据库查找,将数据存储在二级缓存内,第二个usermapper首先会去二级缓存,找到将值返回。
* 如果中间有事务操作,会清空缓存。
*/
public static void testcache2(){
SqlSession sqlSession1 = MybaitisUtil.getSQLSession();
SqlSession sqlSession2 = MybaitisUtil.getSQLSession();
SqlSession sqlSession3 = MybaitisUtil.getSQLSession();
SqlSession sqlSession4 = MybaitisUtil.getSQLSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
System.out.println(userMapper1.selectByuid(1));
sqlSession1.close();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
System.out.println(userMapper2.selectByuid(1));
sqlSession2.close();
//事务操作
UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class);
User user = userMapper3.selectByuid(1);
user.setUsername("李四");
userMapper3.updateuser(user);
sqlSession3.commit();//整个UsernMapper缓存被清空
sqlSession3.close();
UserMapper userMapper4 = sqlSession4.getMapper(UserMapper.class);
System.out.println(userMapper4.selectByuid(1));
sqlSession4.close();
}
打印结果:
每次查询如果需要最新数据,需要禁用二级缓存:在mapper.xml上可以添加useCache="false"。
<!--查询需要: 输入的映射参数类型parameterType=int,输出映射参数类型resultType=User 是最终查询结果的数据类型-->
<select id="selectByuid" parameterType="int" resultType="user" useCache="false"> <!-- useCache="false" 禁用二级缓存 -->
select * from user where id=#{id}
</select>
public static void testcache2(){
SqlSession sqlSession1 = MybaitisUtil.getSQLSession();
SqlSession sqlSession2 = MybaitisUtil.getSQLSession();
SqlSession sqlSession3 = MybaitisUtil.getSQLSession();
SqlSession sqlSession4 = MybaitisUtil.getSQLSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
System.out.println(userMapper1.selectByuid(1));
sqlSession1.close();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
System.out.println(userMapper2.selectByuid(1));
sqlSession2.close();
//事务操作
// UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class);
// User user = userMapper3.selectByuid(1);
// user.setUsername("李四");
// userMapper3.updateuser(user);
// sqlSession3.commit();
// sqlSession3.close();
UserMapper userMapper4 = sqlSession4.getMapper(UserMapper.class);
System.out.println(userMapper4.selectByuid(1));
sqlSession4.close();
}
打印结果:
2.分布式缓存:
如图:现在又服务器1和服务器2。用户向服务器1请求数据查询,把缓存存储在服务器1,当用户请求到服务器2的时候,发现并没有缓存,这就是一个很尴尬的事情。
mybatis需要和其他框架整合,对缓存数据进行集中管理。
为了系统的性能提升,对缓存数据进行集中的管理,使用分布式缓存。
3.应用场景和局限性
对于访问多的查询请求且用户查询结果实时性不高,此时可以采用mybatis二级缓存技术降低数据库的访问量,业务场景:耗时较高的统计分析Sql,电话账单查询sql等等,
通过设置刷新间隔时间,由mybatis每隔一段时间自动清空缓存,根据数据变化频率设置缓存刷新间隔flushinterval,比如30分钟一小时24小时。
mybaitis二级缓存粒度的数据级别缓存实现不好,比如:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品,此时就无法满足刷新当前商品信息,而不刷新其他商品,因为mybatis的二级缓存是以mapper位单位的,解决这种问题需要在业务层根据需求对数据库有针对性缓存。