目录
MyBatis二级缓存出现的原因
使用mybatis自带的二级缓存
mybatis配置文件中settings开启二级缓存
@CacheNameSpace使用二级缓存
@Cacheable添加缓存
@CacheEvict清除指定区域的缓存
测试
debug打印日志
PerpetualCache的实现原理
外接第三方缓存
pom.xml中添加mybatis-redis依赖
resource资源文件夹下配置redis.properties
二级缓存的应用场景
二级缓存缺点
在一级缓存中,不同session进行相同SQL查询的时候,是查询两次数据库的。显然这是一种浪费,既然SQL查询相同,就没有必要再次查库了,直接利用缓存数据即可,这种思想就是MyBatis二级缓存的初衷。
另外,Spring和MyBatis整合时,每次查询之后都要进行关闭sqlsession,关闭之后数据被清空。所以MyBatis和Spring整合之后,一级缓存是没有意义的。如果开启二级缓存,关闭sqlsession后,会把该sqlsession一级缓存中的数据添加到mapper namespace的二级缓存中。这样,缓存在sqlsession关闭之后依然存在。
mybatis的cache包下提供Cache接口用于接入第三方缓存,自带一个默认使用的二级缓存PerpetualCache
@Mapper
@CacheNamespace //使用默认Cache实现类:PerpetualCache
public interface UserDAO {
}
@Mapper
@CacheNamespace
public interface UserDAO {
@Cacheable(value = "depa", key = "'selectById'") // 一般value设置查询的表名,key设置查询方法名字符串,注意''
@Select("select * from depa where did = #{did}")
public Depa selectById(int did) ;
}
@Mapper
@CacheNamespace
public interface UserDAO {
@Cacheable(value = "depa", key = "'selectById'") // 一般value设置查询的表名,key设置查询方法名字符串,注意''
@Select("select * from depa where did = #{did}")
public Depa selectById(int did) ;
@CacheEvict(value = "depa", key = "selectById") // 任何增,删,改的操纵都将缓存删除缓存
@Update("update depa set dname = #{param1} where did = #{param2}")
public void updataById(String dname, int did);
}
@Test
public void testCach() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-conf.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 查询
SqlSession sqlSession = sqlSessionFactory.openSession();
UserDAO mapper = sqlSession.getMapper(UserDAO.class);
Depa depa = mapper.selectById(1);
System.out.println(depa);
sqlSession.close(); // 必须提交或者关闭sqlSession,二级缓存才会开始生效
System.out.println("==========================================================================");
// 拿到新的sqlSession, 查询相同的内容
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserDAO mapper2 = sqlSession2.getMapper(UserDAO.class);
depa = mapper2.selectById(1);
System.out.println(depa);
System.out.println("==========================================================================");
// 修改
mapper2.updataById("修改部",1);
System.out.println("==========================================================================");
// 再次查询相同的内容
depa = mapper2.selectById(1);
System.out.println(depa);
sqlSession2.close();
}
第一次查询,缓存中没有相应的结果,cache击中率0.0,发送sql从数据库中查询。
13:26:29.682 [main] DEBUG com.eleven.dao.UserDAO - Cache Hit Ratio [com.eleven.dao.UserDAO]: 0.0
13:26:29.687 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
13:26:29.687 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Checked out connection 878861517 from pool.
13:26:29.688 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@34625ccd]
13:26:29.693 [main] DEBUG com.eleven.dao.UserDAO.selectById - ==> Preparing: select * from depa where did = ?
13:26:29.731 [main] DEBUG com.eleven.dao.UserDAO.selectById - ==> Parameters: 1(Integer)
13:26:29.771 [main] DEBUG com.eleven.dao.UserDAO.selectById - <== Total: 1
Depa{did=1, dname='素拓部'}
13:26:29.788 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@34625ccd]
13:26:29.789 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@34625ccd]
13:26:29.789 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 878861517 to pool.
==========================================================================
再一次查询相同的内容,cache击中率0.5,从缓存中获取结果
13:28:05.290 [main] DEBUG com.eleven.dao.UserDAO - Cache Hit Ratio [com.eleven.dao.UserDAO]: 0.5
Depa{did=1, dname='素拓部'}
进行修改,将清空缓存
13:28:34.120 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
13:28:34.121 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Checked out connection 878861517 from pool.
13:28:34.122 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@34625ccd]
13:28:34.131 [main] DEBUG com.eleven.dao.UserDAO.updataById - ==> Preparing: update depa set dname = ? where did = ?
13:28:34.137 [main] DEBUG com.eleven.dao.UserDAO.updataById - ==> Parameters: 修改部(String), 1(Integer)
13:28:34.158 [main] DEBUG com.eleven.dao.UserDAO.updataById - <== Updates: 1
再一次查询相同的内容,cache击中率0.67,但是缓存中结果被清空,所以重新发送sql查询数据库
13:29:34.161 [main] DEBUG com.eleven.dao.UserDAO - Cache Hit Ratio [com.eleven.dao.UserDAO]: 0.6666666666666666
13:29:34.162 [main] DEBUG com.eleven.dao.UserDAO.selectById - ==> Preparing: select * from depa where did = ?
13:29:34.163 [main] DEBUG com.eleven.dao.UserDAO.selectById - ==> Parameters: 1(Integer)
13:29:34.172 [main] DEBUG com.eleven.dao.UserDAO.selectById - <== Total: 1
Depa{did=1, dname='修改部'}
PerpetualCache仅仅是包装了HashMap,并且没有实现接口的锁功能,,不支持多线程
并且,mybatis自带的PerpetualCache二级缓存不支持分布式系统
假如用户的查询请求发送给了服务器1,查询结果保存在缓存1中,下一次同样的查询请求发送给了服务器2,并不能从缓存1中读取。所以分布式系统需要结合分布式缓存解决这个问题,不同服务器的查询结果在分布式缓存中集中管理。
注解的方式接入redis缓存
org.mybatis.caches
mybatis-redis
1.0.0-beta2
redis.host=localhost
redis.port=6379
redis.pass=
redis.database=0
进行前面同样的测试,借助redis可视化工具,可以看到db0中有一个com.eleven.dao.UserDAO的缓存,记录了一个hash类型的key-value。key中记录了查询方法名,sql语句,参数和environment的id等信息,value为查询结果。
mybatis二级缓存技术可降低数据库访问量,提高访问速度,适用于
比如耗时较高的统计分析sql、电话账单查询sql等。
通过设置刷新间隔时间,由mybatis每隔一段时间自动清空缓存,根据数据变化频率设置缓存刷新间隔flushInterval,比如设置为30分钟、60分钟、24小时等,根据需求而定。
mybatis二级缓存对细粒度的数据级别的缓存实现不好,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为mybaits的二级缓存区域以mapper为单位划分的,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题可能需要在业务层根据需求对数据有针对性缓存。