背景:
最近公司项目中需要引入缓存机制来减轻数据库负载,所以对一些缓存方案进行了研究,其中包括看了几篇讲mybatis的二级缓存的,写的都很不错,推荐美团的一篇:聊聊MyBatis缓存机制 对mybatis的缓存机制讲的很清楚了。博主在本文提供一种使用redis的hash结构来实现mybatis的二级缓存方案,初次尝试,如有疑问欢迎指正。
环境:
- jdk 1.8
- ide:Intellij 2017.1
- spring-boot :2.0.4.RELEASE
- redis:4.0.2
- mysql:5.7.20
搭建项目&实现
- 通过idea新建spring-boot项目,傻瓜式的选择一些需要的starter即可,不再赘述:参考图片
- 数据源、redis、mybatis相关配置 (spring-boot的自动配置这里也很简单)直接加入相关的配置即可如下:
spring: redis: host: 127.0.0.1 password: port: 6379 jedis: pool: min-idle: 0 max-active: 8 max-idle: 8 max-wait: 5000 datasource: url: jdbc:mysql://127.0.0.1:3306/song?characterEncoding=UTF-8&allowMultiQueries=true username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver mybatis: configuration: cache-enabled: true #开启全局二级缓存 mybatis通过这个会使用CachingExecutor mapper-locations: classpath:mapper/*.xml
- 正常mybatis使用配置(beans、mapper配置、mapper接口):
- 定义一个User bean
@Data @AllArgsConstructor @NoArgsConstructor public class User { private Integer id; private String name; private Integer gender; }
- mapper接口
@Mapper public interface UserMapper { User selectOne(Integer id); void insertOne(User user); }
- mapper配置
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.demo.mapper.UserMapper"> <!--使用自定义的缓存 <cache type="com.example.demo.cache.RedisCache"/> --> <resultMap id="userResultMap" type="com.example.demo.entities.User"> <id property="id" column="id" jdbcType="INTEGER" javaType="java.lang.Integer"/> <result property="name" column="name" jdbcType="VARCHAR" javaType="java.lang.String"/> <result property="gender" column="gender" jdbcType="TINYINT" javaType="java.lang.Integer"/> </resultMap> <select id="selectOne" resultMap="userResultMap" parameterType="java.lang.Integer"> SELECT id, name, gender FROM `USER` WHERE id = #{id} </select> <insert id="insertOne" parameterType="com.example.demo.entities.User" useGeneratedKeys="true" keyProperty="id"> INSERT INTO `USER` (name, gender) VALUES (#{name}, #{gender}) </insert> </mapper>
最好先测试下不使用缓存时代码能用
- 定义一个User bean
- RedisTemplate的配置:
@Configuration public class RedisConfiguration { @Autowired private RedisConnectionFactory redisConnectionFactory; @Bean public RedisTemplate<String,Object> redisTemplate() { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); initDomainRedisTemplate(redisTemplate, redisConnectionFactory); return redisTemplate; } /** * 设置数据存入 redis 的序列化方式 * @param template * @param factory */ private void initDomainRedisTemplate(RedisTemplate<String, Object> template, RedisConnectionFactory factory) { // 定义 key 的序列化方式为 string // 需要注意这里Key使用了 StringRedisSerializer,那么Key只能是String类型的,不能为Long,Integer,否则会报错抛异常。 StringRedisSerializer redisSerializer = new StringRedisSerializer(); template.setKeySerializer(redisSerializer); // 定义 value 的序列化方式为 json @SuppressWarnings({"rawtypes", "unchecked"}) Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); template.setValueSerializer(jackson2JsonRedisSerializer); //hash结构的key和value序列化方式 template.setHashKeySerializer(jackson2JsonRedisSerializer); template.setHashValueSerializer(jackson2JsonRedisSerializer); template.setEnableTransactionSupport(true); template.setConnectionFactory(factory); } }
- 自定义实现mybatis的cache接口 直接上代码吧
public class RedisCache implements Cache { private String id; private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); public RedisCache(String id) { this.id = id; } private RedisTemplate<Object, Object> getRedisTemplate(){ return ApplicationContextHolder.getBean("redisTemplate"); } @Override public String getId() { return id; } @Override public void putObject(Object key, Object value) { getRedisTemplate().boundHashOps(getId()).put(key, value); } @Override public Object getObject(Object key) { return getRedisTemplate().boundHashOps(getId()).get(key); } @Override public Object removeObject(Object key) { return getRedisTemplate().boundHashOps(getId()).delete(key); } @Override public void clear() { getRedisTemplate().delete(getId()); } @Override public int getSize() { Long size = getRedisTemplate().boundHashOps(getId()).size(); return size == null ? 0 : size.intValue(); } @Override public ReadWriteLock getReadWriteLock() { return readWriteLock; } }
这里注意RedisTemplate不能通过spring的resource注入,因为cache对象是和id绑定的,可以debug看下这个id其实是mapper中的namespace、这也是为什么自定义cache中必须有一个有参构造器;这个实现中也是根据这个id来为每个namespace对应一个redis的hash结构,在clear时只删掉本namespace的缓存即可
- mapper中使用自定义cache,只需要在mapper中加上<cache>标签,具体其中的属性可以自己Google
<cache type="com.example.demo.cache.RedisCache"/>
所有代码见:https://github.com/swg-liuge/spring-boot-mybatis-cache2-redis