Mybatis中的二级缓存 Myabtis 的二级缓存是如何实现的1 2 3 4 # 在Mybatis中开启二级缓存 在Mapper.xml文件中加标签<cache /> 开启二级缓存需要将实体类序列化,否则会出现错误 Cause: java.io.NotSerializableException: com.jiang.travels.entity.User
MARKDOWN
1 2 3 # Mybatis的二级缓存的实现 mybatis中有一个接口Cache。 mybatis二级缓存的实现类是PerpetualCache.java,实现的是接口Cache
MARKDOWN
image-20210409091444602
image-20210409091649035
1 2 我们可以通过设置断点来证明PerpetualCache.java是二级缓存的实现类 在PerpetualCache.java中的getObject()方法设置一个断点
MARKDOWN
image-20210409091938771
image-20210409092142465
1 2 3 我们发现在程序停在了getObject()方法中。 此时我们可以知道key的值,和cacahe的size。 因此我们知道了Mybatis的二级缓存的实现类确实是PerpetualCache.java
MARKDOWN
解析PerpetualCache.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 public class PerpetualCache implements Cache { private final String id; private final Map<Object, Object> cache = new HashMap <>(); public PerpetualCache (String id) { this .id = id; } @Override public String getId () { return id; } @Override public int getSize () { return cache.size(); } @Override public void putObject (Object key, Object value) { cache.put(key, value); } @Override public Object getObject (Object key) { return cache.get(key); } @Override public Object removeObject (Object key) { return cache.remove(key); } @Override public void clear () { cache.clear(); } @Override public boolean equals (Object o) { if (getId() == null ) { throw new CacheException ("Cache instances require an ID." ); } if (this == o) { return true ; } if (!(o instanceof Cache)) { return false ; } Cache otherCache = (Cache) o; return getId().equals(otherCache.getId()); } @Override public int hashCode () { if (getId() == null ) { throw new CacheException ("Cache instances require an ID." ); } return getId().hashCode(); } }
JAVA
1 我们了解完PerpetualCache.java的实现类,知道了Mybatis的二级缓存的原理。那么我们通过redis来实现Mybatis的二级缓存就很简单了。
MARKDOWN
redis实现Mybatis二级缓存1 2 3 4 5 6 我们已经知道了Mybatis二级缓存的原理,实现的是PerpetualCache.java。 我们可以自己来创建一个类,实现Cache接口。重写put,get方法。 但是我们怎么让Mybatis知道? 就像我们如何让Spring容器知道我们注册了一个类。 我们说过,只有在Mapper.xml文件中添加<Cache /> 标签才能使用二级缓存。而在<Cache /> 标签中有几个属性
MARKDOWN
image-20210409094246879
1 2 其中type属性就是决定实现类。 默认 <cache type="org.apache.ibatis.cache.impl.PerpetualCache"/>
MARK
RedisCache1 2 3 4 5 到现在,我们基本上可以知道RedisCache的原理和实现1. 创建一个RedisCache类,并implements Cache接口2. 实现RedisCache中的方法3. 在Mapper.xml文件中 type="XXXXX.RedisCache"
MARKDOWN
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class RedisCache implements Cache { @Override public String getId () { return null ; } @Override public void putObject (Object key, Object value) { } @Override public Object getObject (Object key) { return null ; } @Override public Object removeObject (Object key) { return null ; } @Override public void clear () { } @Override public int getSize () { return 0 ; } }
JAVA
id的作用1 2 3 4 5 6 7 RedisCache的基本框架就是这样。 我们与PerpetualCache.java进行对比,我们发现少了许多的东西。1. private final String id;2. private final Map<Object, Object> cache = new HashMap<>(); 对于Map<> cache很容易理解 我们来分析id的含义
MARKDOWN
1 2 3 # private final String id; 我们并不知道id的作用是什么,我们首先来使用这个Cache,来判断这个id是否是必须的。 注意更改实现类 <cache type ="com.jiang.travels.cache.RedisCache" />
MARKDOWN
image-20210409100123846
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # 出现了错误: 错误中由这样一句话: Base cache implementations must have a constructor that takes a String id as a parameter. 必须有一个构造器带id。 此时我们依旧不知道这个id是什么用。 不过,既然出现了错误,我们就改:为RedisCache实现类添加常量id,并添加构造函数 private final String id; public RedisCache(String id) { System.out.println("id = " + id); this.id = id; } @Override public String getId() { return id; } 我们打印这个id到底是什么。 此时并没有报错,并且数据已经从数据库中查到
MARKDOWN
image-20210409100752912
image-20210409100833215
1 我们终于知道了这个id 的含义:这就是Mapper.xml文件中的namespace
MARKDOWN
image-20210409101026749
实现方法get put1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Override public void putObject (Object key, Object value) { System.out.println("key = " + key); System.out.println("value = " + value); }@Override public Object getObject (Object key) { System.out.println("key = " + key); return null ; }
JAVA
1 同时,我们为了让结果更加的简单明了,我们更改Test方法。
MARKDOWN
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Test public void findUser () { User user = new User (); user.setPassword("123456" ); user.setUsername("admin" ); User byUser1 = userService.findByUser(user); System.out.println(byUser1); System.out.println("====================================" ); user.setPassword("123456" ); user.setUsername("admin" ); User byUser2 = userService.findByUser(user); System.out.println(byUser2); }
JAVA
image-20210409102404794
1 2 3 4 5 很明显,方法执行了。 我们根据方法分析, 我们执行了一个方法,userService.findByUser(user); 这个方法去读取缓存,即执行getObject()方法。但是缓存为命中。 所以执行putObject()方法,放入缓存,因此在日志中才打印出这么多的数据。
MARKDOWN
1 2 3 4 5 6 7 # redis实现方法 我们知道如何通过java来操作redis。 --- RedisTemplate 如何不了解的可以查看一些文章 https://www.cnblogs.com/smartsmile/p/11633844.html 但是我们需要创建一个工厂,来获取这个redisTRedisTemplate
MARKDOWN
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Component public class ApplicationContextUtils implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext (ApplicationContext applicationContext) throws BeansException { this .applicationContext = applicationContext; } public static Object getBean (String beanName) { return applicationContext.getBean(beanName); } }
JAVA
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Override public void putObject (Object key, Object value) { System.out.println("key = " + key); System.out.println("value = " + value); RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate" ); redisTemplate.setKeySerializer(new StringRedisSerializer ()); redisTemplate.setHashKeySerializer(new StringRedisSerializer ()); redisTemplate.opsForHash().put(id.toString(),key.toString(),value); }
JAVA
1 2 # 写完put方法之后,我们对它进行测试 首先我们可以看到在redis中我们没有任何数据
MARKDOWN
image-20210409110346364
1 2 3 # 执行单元测试 再次观察redis,我们发现redis中有了数据,并且key为id值, 但是我们发现,在日志中,缓存依旧没有命中,这是因为我们没有重写getObject()方法
MARKDOWN
image-20210409110448039
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Override public Object getObject (Object key) { System.out.println("key = " + key); RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate" ); redisTemplate.setKeySerializer(new StringRedisSerializer ()); redisTemplate.setHashKeySerializer(new StringRedisSerializer ()); Object obj = redisTemplate.opsForHash().get(id.toString(), key.toString()); return obj; }
JAVA
1 2 我们将redis中的数据删除,重新运行单元测试 明显看出,缓存命中了
MARKDOWN
image-20210409111052865
image-20210409111119362
缓存带来的问题1 2 3 4 我们都知道缓存会带来一些问题,比如我们修改后,再次查询,我们依旧会查询到redis中的缓存。 针对这个问题: 在redis中的解决方案是从redis中删除掉。
MARKDOWN
1 2 # clear()方法和removeObject()方法 删除时使用哪一个方法,我们暂时没有对方法进行具体操作。
MARKDOWN
1 2 3 4 5 6 7 8 9 10 @Override public Object removeObject (Object key) { System.out.println("removeObject ================ key = " + key); return null ; }@Override public void clear () { System.out.println("clear ================ " ); }
JAVA
1 2 3 4 5 6 7 8 9 10 11 12 <update id="updata" parameterType="com.jiang.travels.entity.User"> update t_user set email = #{email} where id = #{id} </update > @Testpublic void Updata(){ User user = new User (); user .setId("9"); user .setEmail("11111@qq.com"); userService.updata(user ); }
PGSQL
image-20210409113712974
1 我们可以发现最后执行的时clear方法
MARKDOWN
1 2 3 4 5 6 7 8 9 10 11 12 @Override public void clear () { System.out.println("clear ================ " ); RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate" ); redisTemplate.setKeySerializer(new StringRedisSerializer ()); redisTemplate.setHashKeySerializer(new StringRedisSerializer ()); redisTemplate.delete(id.toString()); }
JAVA
1 运行之后,我们再去redis中查看是否还有缓存。
MARKDOWN
image-20210409114150467
1 证明redis中的缓存已经被删除了
MARKDOWN
代码 RedisCache.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 package com.jiang.travels.cache;import com.jiang.travels.utils.ApplicationContextUtils;import org.apache.ibatis.cache.Cache;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.serializer.StringRedisSerializer;public class RedisCache implements Cache { private final String id; public RedisCache (String id) { System.out.println("id = " + id); this .id = id; } @Override public String getId () { return id; } public RedisTemplate getRedisTemplate () { RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate" ); redisTemplate.setKeySerializer(new StringRedisSerializer ()); redisTemplate.setHashKeySerializer(new StringRedisSerializer ()); return redisTemplate; } @Override public void putObject (Object key, Object value) { System.out.println("key = " + key); System.out.println("value = " + value); getRedisTemplate().opsForHash().put(id.toString(),key.toString(),value); } @Override public Object getObject (Object key) { System.out.println("key = " + key); return getRedisTemplate().opsForHash().get(id.toString(), key.toString()); } @Override public Object removeObject (Object key) { System.out.println("removeObject ================ key = " + key); return null ; } @Override public void clear () { System.out.println("clear ================ " ); getRedisTemplate().delete(id.toString()); } @Override public int getSize () { return getRedisTemplate().opsForHash().size(id.toString()).intValue(); } }
JAVA
ApplicationContextUtils.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.jiang.travels.utils;import org.springframework.beans.BeansException;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.stereotype.Component;@Component public class ApplicationContextUtils implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext (ApplicationContext applicationContext) throws BeansException { this .applicationContext = applicationContext; } public static Object getBean (String beanName) { return applicationContext.getBean(beanName); } }
JAVA
参考资料:https://www.bilibili.com/video/BV1jD4y1Q7tU?p=17