标签:
MyBatis 和 Hibernate 一样具有 一级缓存 和 二级缓存。
1. 一级缓存:MyBatis 一级缓存的作用域 是 同一个SqlSession。
写一个 查询 User 的例子:
<!-- user 查询 --> <select id="findUserById" parameterType="int" resultType="user"> select * from users where userId = #{user_id} </select>
//测试一级缓存 @Test public void testCache1() throws Exception { //获取SqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class); //第一次查询 User user = new User(); user.setUserId(100108); System.out.println("-----------------第一次查询,从数据库获取--------------------"); User getUser = orderMapper.findUserById(user.getUserId()); System.out.println(getUser); System.out.println("-----------------第二次查询,从缓存中获取--------------------"); //再次执行同样的查询 User getUser2 = orderMapper.findUserById(user.getUserId()); System.out.println(getUser2); }
执行结果:
从结果中可以看出:第一次查询,在数据库中执行了 SQL。 而第二次并没有通过查询数据库得到结果,
而是从缓存中获取的。那么缓存的机制是什么样的?
SqlSession 缓存机制:执行一个SQL的时候,MyBatis 会首先去 缓存(PerpetualCache)中 根据 key 查询
结果集,a. 如果没有则去查询数据库,查询成功后,将结果写入 cache 中;b. 如果有,则直接从缓存中
取出结果集返回。
MyBatis 内部存储 缓存使用的是一个 HashMap<CacheKey , Object>, key 为 hashCode + sqlId + Sql语句
的格式, value 则是查询出来映射生成的 Java 对象。
问题:如果在第二次查询之前,更新了这个 User 的信息,那么第二次查询是从缓存中取出数据还是再查询一次?
结果是显而易见的,在 更新、删除操作的时,cache 会被清空。所以会再查询一次。
2. 二级缓存:即查询缓存,它的作用域是一个 mapper 的namespace,即在同一个 namespace 中的查询sql
可以从缓存中获取数据。二级缓存是可以跨 SqlSession 的。
a. 开启二级缓存:在SqlMapConfig.xml 的 <settings>...</settings>中加入
<!-- 开启二级缓存 --> <setting name="cacheEnabled" value="true"/>
属性名 | 描述 | 允许值 | 默认值 |
cacheEnabled | 对在此配置文件下所有的 cache 进行全局性开/关设置 | true / false | true |
b. 在 mapper.xml 文件中添加 <cache /> 开启缓存
<mapper namespace="mybatis_b.mapper.OrderMapper"> <!-- 开启本 mapper 的二级缓存 --> <cache /> </mapper>
c. Java 调用,这里用两个 SqlSession 来实现二级缓存的跨 SqlSession
//测试二级缓存 @Test public void testCache2() throws Exception { //获取SqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class); SqlSession sqlSession2 = sqlSessionFactory.openSession(); OrderMapper orderMapper2 = sqlSession2.getMapper(OrderMapper.class); //第一次查询 User user = new User(); user.setUserId(100108); System.out.println("-----------------第一次查询,从数据库获取--------------------"); User getUser = orderMapper.findUserById(user.getUserId()); System.out.println(getUser); //关闭session1 sqlSession.close(); System.out.println("-----------------第二次查询,从缓存中获取--------------------"); //再次执行同样的查询 User getUser2 = orderMapper2.findUserById(user.getUserId()); System.out.println(getUser2); sqlSession2.close(); }
其中,User 类要实现序列化接口。执行后发现第二次的确是从缓存中获取数据的,结果就不贴了。
问题:如果在 第一次查询完后,更新某个数据,那么第二次还会从缓存中获取数据吗?结果是否是最新?
结果:第二次依旧是从缓存中获取的,而且是最新的。因为:在 mapper 的同一个 namespace 中,如果有
其他 insert、update、delete 操作数据后需要刷新缓存,如果不执行则会出现脏读。而 mapper
的sql 中有 flushCache="true" 这个属性,默认为 true即刷新 mapper 级别的缓存。
3. Cache 的其他参数:
flushInterval:刷新间隔,可以设置为任意的正整数,表示一个合理的时间段,以毫秒为单位。默认情况是
不设置,也就是没有刷新间隔,仅在调用语句时刷新缓存。
size:引用数目,可以设置为任意的正整数,要记住你缓存的对象和 运行环境的可用内存资源,默认值是1024
readOnly:只读属性。只读的缓存会给所有的调用者返回缓存对象相同的实例。因此这些对象不能被修改。
这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化)。这会慢一些
,但是安全,因此默认是false。
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
上面这个缓存配置创建了一个FIFO缓存,并每隔60s刷新,存储结果对象或列表的512个引用,而且返回的
对象被认为是只读的,因此在不同的线程中调用者之间修改它们会导致冲突。
可用的回收策略有:
A. LRU - 最近最少使用的:移除最长时间不被使用的对象
B. FIFO - 先进先出:按对象进入缓存的顺序来移除它们。
C. SOFT - 软引用:移除基于垃圾回收器状态和软引用规则的对象。
D. WEAK - 弱引用:更积极地移除基于垃圾回收器状态和软引用规则的对象。
4. MyBatis 与 缓存框架 eacache 进行了整合,采用 eacache 框架管理缓存数据。
a. 引入缓存的依赖包,需要 mybatis-ehcache-1.0.3.jar ehcache-core-2.6.8.jar 和 slf4j-api-1.6.1.jar
b. 修改 mapper 的缓存配置:
<!-- 以下两个<cache>标签二选一,第一个可以输出日志,第二个不输出日志 --> <cache type="org.mybatis.caches.ehcache.LoggingEhcache" /> <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
有待详解。。。
标签:
原文地址:http://my.oschina.net/u/1757476/blog/509189