MyBatis提供一级缓存和二级缓存机制。

一级缓存是Sqlsession级别的缓存,Sqlsession类的实例对象中有一个hashmap用于缓存数据。不同的Sqlsession实例缓存的hashmap数据区域互不影响。Mybatis默认启用一级缓存,在同一个sqlsession中多次执行相同的sql语句,第一次执行后会将数据缓存起来,后面的查询将会从缓存中读取。当一个sqlsession结束后(close),该sqlsession中缓存的数据也将不存在。

二级缓存是Mapper级别的缓存,多个sqlsession实例操作同一个Mapper配置可共享二级缓存。Mybatis默认没有启用二级缓存,需要手动配置开启二级缓存。

一张图看看一集缓存和二级缓存的区别:

一级缓存区域按sqlsession划分,当执行查询时会先从缓存区域查找,如果存在则直接返回数据,否则从数据库查询,并将结果集写入缓存区。 Mybatis一级缓存是在sqlsession内部维护一个hashmap用于存储,缓存key为hashcode+sqlid+sql,value则为查询的结果集。一级缓存在执行sqlsession.commit()后将会被清空。

一级缓存示例:

编写cacheMapper.xml配置文件

  1. <mapper namespace="com.sl.mapper.CacheMapper">
  2. <cache/>
  3. <select id="selectProductById" parameterType="int" resultType="com.sl.po.Product" >
  4. select * from products where id = #{id}
  5. </select>
  6. </mapper>

Mapper接口:

  1. public interface CacheMapper {
  2. Product selectProductById(int id);
  3. @Options(flushCache=FlushCachePolicy.TRUE)
  4. int updateProductById(Product product);
  5. }

View Code

测试方法:

  1. public class TestCacheMapperClient {

  2. SqlSessionFactory factory
    = null;
  3. @Before
  4. public void init() throws IOException {
  5. String resource = "SqlMapConfig.xml";
  6. InputStream inputStream = Resources.getResourceAsStream(resource);
  7. SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
  8. factory = builder.build(inputStream);
  9. }
  10. // 一级缓存
  11. @Test
  12. public void testSelectProductById() {
  13. SqlSession session = factory.openSession();
  14. CacheMapper mapper = session.getMapper(CacheMapper.class);
  15. Product product = mapper.selectProductById(1);
  16. System.out.println(product.getName());
  17. //执行commit 将清空一级缓存
  18. //session.commit();
  19. //再次执行查询 从一级缓存读取
  20. Product product2 = mapper.selectProductById(1);
  21. System.out.println(product.getName());
  22. // 关闭会话
  23. session.close();
  24. }
  25. }

执行第一次执行selectProductById,查询数据库,第二次执行,从缓存中读取

如果在两次查询中间执行commit,即上面的注释掉的session.commit(),则运行结果如下,显然清空了一级缓存,再次执行数据库查询

二级缓存按照mapper划分,一个mapper有一个自己的二级缓存(按照namespace区分不同缓存区域,如果多个mapper的namespace相同,则公用一个缓存区域),当多个sqlsession类实例加载相同的Mapper文件,执行mapper配置文件中的sql查询时,这些sqlsession可共享一个二级缓存。Mybatis默认没有启用二级缓存,需要自行配置。

二级缓存示例:

1. 启用二级缓存:

在mybatis置文件SqlMapConfig.xml中加入一下配置

  1. <setting name="cacheEnabled" value="true"/>

在Mapper.xml配置文件中添加cache标签

  1. <cache /> <!-- 表示此mapper开启二级缓存。-->

还可以配置其他参数,如:

  1. <cache flushInterval="60000" size="512" readOnly="true" eviction="FIFO" type=”xxxxx” />

flushInterval:刷新时间间隔,单位毫秒,不设置则没有刷新时间间隔,在执行配置了flushCache标签的sql时刷新(清空)

size:缓存原数个数,默认1024

readOnly:是否只读,默认false:mybatis将克隆一份数据返回,true:直接返回缓存数据的引用(不安全,程序如果修改,直接改了缓存项)

eviction:缓存的回收策略(LRU 、FIFO、 SOFT 、WEAK ),默认LRU

type:指定自定义缓存的全类名(实现Cache接口即可)

2. 结果集映射对象实现序列化接口

使用Mybatis二级缓存需要将sql结果集映射的pojo对象实现java.io.Serializable接口,否则将出现序列化错误。

  1. public class Product implements Serializable{ …}

3.编写cacheMapper.xml配置文件

  1. <mapper namespace="com.sl.mapper.CacheMapper">
  2. <cache/>

  3. <select id="selectProductById" parameterType="int" resultType="com.sl.po.Product" ><!-- useCache="false" 禁用二级缓存或者在Mapper接口上通过注解禁用-->
  4. select * from products where id = #{id}
  5. </select>
  6. <!-- update – 映射更新语句 -->
  7. <update id="updateProductById" parameterType="com.sl.po.Product" flushCache="true"> <!-- flushCache="true" 禁用二级缓存或者在Mapper接口上通过注解禁用-->
  8. update products set
  9. Name = #{Name},IsNew=#{IsNew}
  10. where id=#{id}
  11. </update>
  12. </mapper>

4.Mapper.java接口

  1. public interface CacheMapper {
  2. Product selectProductById(int id);
  3. //@Options(flushCache=FlushCachePolicy.TRUE) //清空 二级缓存
  4. int updateProductById(Product product);\
  5. }

5.测试方法:

  1. public class TestCacheMapperClient {
  2. SqlSessionFactory factory = null;
  3. @Before
  4. public void init() throws IOException {
  5. String resource = "SqlMapConfig.xml";
  6. InputStream inputStream = Resources.getResourceAsStream(resource);
  7. SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
  8. factory = builder.build(inputStream);
  9. }
    //二级缓存
  10. @Test
  11. public void testSelectProductById2() throws IOException {
  12. SqlSession session1 = factory.openSession();
  13. CacheMapper mapper1 = session1.getMapper(CacheMapper.class);
  14. Product product = mapper1.selectProductById(1);
  15. System.out.println(product.getName());
  16. /************同一session 共享一级缓存***************/
  17. //CacheMapper mapper2 = session1.getMapper(CacheMapper.class);
  18. //Product product2 = mapper2.selectProductById(1);
  19. //System.out.println(product2.getName());
  20. //执行commit 将清空一级缓存,无法情况二级缓存
  21. session1.commit();
  22. session1.close();
  23. //清空二级缓存
  24. //Mapper接口注解@Options(flushCache=FlushCachePolicy.TRUE) 或者Mapper.xml配置属性 flushCache="true"
  25. SqlSession session4 = factory.openSession();
  26. CacheMapper mapper4 = session4.getMapper(CacheMapper.class);
  27. Product up = new Product();
  28. up.setId(1);
  29. up.setIsNew(true);
  30. up.setName("缓存测试2");
  31. int count = mapper4.updateProductById(up);
  32. session4.commit();
  33. session4.close();
  34. /**********不同session实例 共享二级缓存************/
  35. SqlSession session3 = factory.openSession();
  36. CacheMapper mapper3 = session3.getMapper(CacheMapper.class);
  37. Product product3 = mapper3.selectProductById(1);
  38. System.out.println(product3.getName());
  39. // 关闭会话
  40. session3.close();
  41. }
  42. }

测试结果,上面updateProductById方法在配置sql中清空了二级缓存,所以后面mapper3.selectProductById(1)仍然执行数据库查询。

 

6. 禁用二级缓存

Mybatis还提供属性用于对指定的查询禁用二级缓存,在Mapper.xml配置文件中可是使用useCache=false禁止当前select使用二级缓存,即:

  1. <select id="selectProductById" parameterType="int" resultType="com.sl.po.Product" useCache="false" ><!-- useCache="false" 禁用二级缓存或者在Mapper接口上通过注解禁用-->
  2. select * from products where id = #{id}
  3. </select>

在Mapper.Java接口中可是通过注解来禁用二级缓存,即:

  1. @Options(useCache=false)
  2. Product selectProductById(int id);

7.缓存刷新

当mybatis执行数据更新sql语句后,DB数据与缓存数据可能已经不一致,如果不执行刷新缓存则可能出现脏读的情况,Mybatis同样提供xml配置和注解两种方式来实现缓存刷新

Xml配置形式:

  1. <update id="updateProductById" parameterType="com.sl.po.Product" flushCache="true"> <!-- flushCache="true" 禁用二级缓存或者在Mapper接口上通过注解禁用-->
  2. update products set
  3. Name = #{Name},IsNew=#{IsNew}
  4. where id=#{id}
  5. </update>

注解形式:

  1. @Options(flushCache=FlushCachePolicy.TRUE) //清空 二级缓存
  2. int updateProductById(Product product);

Mybatis默认启用二级缓存是服务器本地缓存,在程序部署到多台服务器时可能出现数据不一致的情况,这种情况下最好能有个集中式缓存来解决此问题。MyBatis的二级缓存允许自定义实现,Mybatis提供二级缓存接口,我们可以通过实现org.apache.ibatis.cache.Cache接口来整合第三方缓存,比如redis、memcache等。

Demo实现步骤:

1.  添加jar包依赖

  1. <!-- redis client -->
  2. <dependency>
  3. <groupId>redis.clients</groupId>
  4. <artifactId>jedis</artifactId>
  5. <version>${jedis.version}</version>
  6. </dependency>

2. 实现 Mybatis二级缓存org.apache.ibatis.cache.Cache接口

  1. package com.sl.redis;
  2. import java.util.concurrent.locks.ReadWriteLock;
  3. import java.util.concurrent.locks.ReentrantReadWriteLock;
  4. import org.apache.ibatis.cache.Cache;
  5. import redis.clients.jedis.Jedis;
  6. import redis.clients.jedis.JedisPool;
  7. import redis.clients.jedis.JedisPoolConfig;
  8. public class RedisCache implements Cache
  9. {
  10. private Jedis redisClient = createClient();
  11. private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  12. private String id;
  13. public RedisCache(final String id) {
  14. if (id == null) {
  15. throw new IllegalArgumentException("Cache instances require an ID");
  16. }
  17. this.id = id;
  18. }
  19. public String getId() {
  20. return this.id;
  21. }
  22. public int getSize() {
  23. return Integer.valueOf(redisClient.dbSize().toString());
  24. }
  25. public void putObject(Object key, Object value) {
  26. redisClient.set(SerializeHelper.serialize(key.toString()), SerializeHelper.serialize(value));
  27. }
  28. public Object getObject(Object key) {
  29. Object value = SerializeHelper.unserialize(redisClient.get(SerializeHelper.serialize(key.toString())));
  30. return value;
  31. }
  32. public Object removeObject(Object key) {
  33. return redisClient.expire(SerializeHelper.serialize(key.toString()), 0);
  34. }
  35. public void clear() {
  36. redisClient.flushDB();
  37. }
  38. public ReadWriteLock getReadWriteLock() {
  39. return readWriteLock;
  40. }
  41. protected static Jedis createClient() {
  42. try {
  43. JedisPool pool = new JedisPool(new JedisPoolConfig(),"localhost");
  44. return pool.getResource();
  45. } catch (Exception e) {
  46. e.printStackTrace();
  47. }
  48. throw new RuntimeException("初始化连接池错误");
  49. }
  50. }
  51. package com.sl.redis;
  52. import java.io.ByteArrayInputStream;
  53. import java.io.ByteArrayOutputStream;
  54. import java.io.ObjectInputStream;
  55. import java.io.ObjectOutputStream;
  56. public class SerializeHelper {
  57. public static byte[] serialize(Object object) {
  58. ObjectOutputStream oos = null;
  59. ByteArrayOutputStream baos = null;
  60. try {
  61. // 序列化
  62. baos = new ByteArrayOutputStream();
  63. oos = new ObjectOutputStream(baos);
  64. oos.writeObject(object);
  65. byte[] bytes = baos.toByteArray();
  66. return bytes;
  67. } catch (Exception e) {
  68. e.printStackTrace();
  69. }
  70. return null;
  71. }
  72. public static Object unserialize(byte[] bytes) {
  73. if (bytes == null)
  74. return null;
  75. ByteArrayInputStream bais = null;
  76. try {
  77. // 反序列化
  78. bais = new ByteArrayInputStream(bytes);
  79. ObjectInputStream ois = new ObjectInputStream(bais);
  80. return ois.readObject();
  81. } catch (Exception e) {
  82. e.printStackTrace();
  83. }
  84. return null;
  85. }
  86. }

View Code

3. 修改Mapper.xml配置文件,通过type属型指定自定义二级缓存实现  type=”com.sl.redis.RedisCache”

  1. <mapper namespace="com.sl.mapper.CacheMapper">
  2. <cache type="com.sl.redis.RedisCache"/>
  3. <select id="selectProductById" parameterType="int" resultType="com.sl.po.Product" ><!-- useCache="false" 禁用二级缓存或者在Mapper接口上通过注解禁用-->
  4. select * from products where id = #{id}
  5. </select>
  6. <!-- update – 映射更新语句 -->
  7. <update id="updateProductById" parameterType="com.sl.po.Product" flushCache="true"> <!-- flushCache="true" 禁用二级缓存或者在Mapper接口上通过注解禁用-->
  8. update products set
  9. Name = #{Name},IsNew=#{IsNew}
  10. where id=#{id}
  11. </update>
  12. </mapper>

测试方法同上。

以上通过重写Mybatis二级缓存接口Cache类中的方法,将mybatis中默认的二级缓存空间替换成Redis。mybatis的二级缓存默认存储1024个对象(通过size可配置),且自带的二级缓存是存储在服务器本地内存的,实际开发中往往放弃直接使用默认二级缓存。使用redis 可以将数据存储到专用缓存服务器上,同时redis的高性能也保证了缓存数据的高速读取。

版权声明:本文为ashleyboy原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/ashleyboy/p/9302507.html