Contents

起步

首先,我们需要初始化我们的Spring-Boot工程,必要添加的pom

 1<?xml version="1.0" encoding="UTF-8"?>
 2<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 3         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
 4    <modelVersion>4.0.0</modelVersion>
 5    <parent>
 6        <groupId>org.springframework.boot</groupId>
 7        <artifactId>spring-boot-starter-parent</artifactId>
 8        <version>2.2.1.RELEASE</version>
 9        <relativePath/> <!-- lookup parent from repository -->
10    </parent>
11    <groupId>online.keepon.mybatis</groupId>
12    <artifactId>mybatis-cache</artifactId>
13    <version>0.0.1-SNAPSHOT</version>
14    <name>mybatis-cache</name>
15    <description>Demo project for Spring Boot</description>
16
17    <properties>
18        <java.version>11</java.version>
19    </properties>
20
21    <dependencies>
22        <dependency>
23            <groupId>org.springframework.boot</groupId>
24            <artifactId>spring-boot-starter-web</artifactId>
25        </dependency>
26        <dependency>
27            <groupId>org.mybatis.spring.boot</groupId>
28            <artifactId>mybatis-spring-boot-starter</artifactId>
29            <version>2.1.1</version>
30        </dependency>
31        <dependency>
32            <groupId>org.springframework.boot</groupId>
33            <artifactId>spring-boot-starter-data-redis</artifactId>
34        </dependency>
35        <dependency>
36            <groupId>mysql</groupId>
37            <artifactId>mysql-connector-java</artifactId>
38            <scope>runtime</scope>
39        </dependency>
40        <dependency>
41            <groupId>org.projectlombok</groupId>
42            <artifactId>lombok</artifactId>
43            <optional>true</optional>
44        </dependency>
45        <dependency>
46            <groupId>com.alibaba</groupId>
47            <artifactId>fastjson</artifactId>
48            <version>1.2.61</version>
49        </dependency>
50        <dependency>
51            <groupId>org.springframework.boot</groupId>
52            <artifactId>spring-boot-starter-test</artifactId>
53            <scope>test</scope>
54            <exclusions>
55                <exclusion>
56                    <groupId>org.junit.vintage</groupId>
57                    <artifactId>junit-vintage-engine</artifactId>
58                </exclusion>
59            </exclusions>
60        </dependency>
61    </dependencies>
62
63    <build>
64        <plugins>
65            <plugin>
66                <groupId>org.springframework.boot</groupId>
67                <artifactId>spring-boot-maven-plugin</artifactId>
68            </plugin>
69        </plugins>
70    </build>
71
72</project>
73

配置文件

 1server.port=8990
 2spring.datasource.username=root
 3spring.datasource.password=root
 4spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
 5spring.datasource.url=jdbc:mysql://localhost:3306/assistant?characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
 6#Redis配置## Redis数据库索引(默认为0)
 7spring.redis.database=0
 8# Redis服务器地址
 9spring.redis.host=127.0.0.1
10# Redis服务器连接端口
11spring.redis.port=6379
12# Redis服务器连接密码
13spring.redis.password=
14# 连接池最大连接数(使用负值表示没有限制)
15spring.redis.jedis.pool.max-active=8
16# 连接池最大阻塞等待时间(使用负值表示没有限制)
17spring.redis.jedis.pool.max-wait=30000ms
18# 连接池中的最大空闲连接
19spring.redis.jedis.pool.max-idle=8
20# 连接池中的最小空闲连接
21spring.redis.jedis.pool.min-idle=1
22# 连接超时时间(毫秒)
23spring.redis.timeout=6000ms
24#mybatis
25mybatis.type-aliases-package=online.keepon.mybatis.model
26mybatis.mapper-locations=classpath:mapper/*
27mybatis.configuration.cache-enabled=true
28mybatis.configuration.map-underscore-to-camel-case=true
29spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
30spring.jackson.time-zone=GMT+8
31logging.level.online.keepon.mybatis.mapper=trace

一个简单的User

需要实现Serializable 接口,避免redis报错

1@Data
2public class User implements Serializable {
3    private Integer id;
4    private String username;
5}
6

UserMapper

 1@Mapper
 2@CacheNamespace(implementation = RedisCache.class)
 3public interface UserMapper {
 4
 5    @Select("select * from user where id=#{id} ")
 6    User selectById(@Param("id") Integer id);
 7
 8	/**
 9     * flushCache = FlushCachePolicy.TRUE 更新的时候删除缓存
10     * @param user
11     * @return
12     */
13    @Options(flushCache = FlushCachePolicy.TRUE)
14    @Update("update user set username=#{username} where id=#{id}  ")
15    int update(User user);
16}
17

新建一个RedisCache 实现mybatis 的缓存

 1import lombok.extern.slf4j.Slf4j;
 2import online.keepon.mybatis.utils.ApplicationContextHolder;
 3import org.apache.ibatis.cache.Cache;
 4import org.springframework.data.redis.core.RedisCallback;
 5import org.springframework.data.redis.core.RedisTemplate;
 6
 7import java.util.Objects;
 8import java.util.concurrent.TimeUnit;
 9import java.util.concurrent.locks.ReadWriteLock;
10import java.util.concurrent.locks.ReentrantReadWriteLock;
11
12/**
13 * @author felix
14 * @ 日期 2019-12-06 15:04
15 */
16@Slf4j
17public class RedisCache implements Cache {
18    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
19    private String id;
20    private RedisTemplate<String, Object> redisTemplate = ApplicationContextHolder.getBean("redisTemplate");
21
22    // redis过期时间
23    private static final long EXPIRE_TIME_IN_MINUTES = 30;
24
25    public RedisCache(String id) {
26        if (id == null) {
27            throw new IllegalArgumentException("Cache instances require an ID");
28        }
29        this.id = id;
30    }
31
32    @Override
33    public String getId() {
34        return id;
35    }
36
37    /**
38     * Put query result to redis
39     *
40     * @param key
41     * @param value
42     */
43    @Override
44    @SuppressWarnings("unchecked")
45    public void putObject(Object key, Object value) {
46        System.out.println(key);
47        redisTemplate.opsForValue().set(key.toString(), value, EXPIRE_TIME_IN_MINUTES, TimeUnit.SECONDS);
48        log.debug("Put query result to redis");
49    }
50
51    /**
52     * Get cached query result from redis
53     *
54     * @param key
55     * @return
56     */
57    @Override
58    public Object getObject(Object key) {
59        log.debug("Get cached query result from redis");
60        return redisTemplate.opsForValue().get(key.toString());
61    }
62
63    /**
64     * Remove cached query result from redis
65     *
66     * @param key
67     * @return
68     */
69    @Override
70    public Object removeObject(Object key) {
71        redisTemplate.delete(key.toString());
72        log.debug("Remove cached query result from redis");
73        return null;
74    }
75
76    /**
77     * Clears this cache instance
78     */
79    @Override
80    @SuppressWarnings("unchecked")
81    public void clear() {
82        redisTemplate.execute((RedisCallback) connection -> {
83            connection.flushDb();
84            return null;
85        });
86        log.debug("Clear all the cached query result from redis");
87    }
88
89    @Override
90    public int getSize() {
91        return Objects.requireNonNull(Objects.requireNonNull(redisTemplate.getConnectionFactory()).getConnection().dbSize()).intValue();
92    }
93
94    @Override
95    public ReadWriteLock getReadWriteLock() {
96        return readWriteLock;
97    }
98}
99

redis 序列化配置

 1import com.fasterxml.jackson.annotation.JsonAutoDetect;
 2import com.fasterxml.jackson.annotation.PropertyAccessor;
 3import com.fasterxml.jackson.databind.ObjectMapper;
 4import online.keepon.mybatis.cache.FastJson2JsonRedisSerializer;
 5import org.springframework.context.annotation.Bean;
 6import org.springframework.context.annotation.Configuration;
 7import org.springframework.data.redis.connection.RedisConnectionFactory;
 8import org.springframework.data.redis.core.RedisTemplate;
 9import org.springframework.data.redis.serializer.StringRedisSerializer;
10
11/**
12 * @author felix
13 * @ 日期 2019-12-06 15:15
14 */
15@Configuration
16public class RedisConfig {
17   @Bean
18    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
19        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
20        redisTemplate.setConnectionFactory(redisConnectionFactory);
21        FastJson2JsonRedisSerializer fastJson2JsonRedisSerializer = new FastJson2JsonRedisSerializer(Object.class);
22        ObjectMapper objectMapper = new ObjectMapper();
23        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
24        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
25        fastJson2JsonRedisSerializer.setObjectMapper(objectMapper);
26
27        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
28        // key采用String的序列化方式
29        redisTemplate.setKeySerializer(stringRedisSerializer);
30        // string的value采用fastJson序列化方式
31        redisTemplate.setValueSerializer(fastJson2JsonRedisSerializer);
32        // hash的key也采用String的序列化方式
33        redisTemplate.setHashKeySerializer(stringRedisSerializer);
34        // hash的value采用fastJson序列化方式
35        redisTemplate.setHashValueSerializer(fastJson2JsonRedisSerializer);
36        redisTemplate.afterPropertiesSet();
37        return redisTemplate;
38    }
39}
 1
 2import com.alibaba.fastjson.JSON;
 3import com.alibaba.fastjson.parser.ParserConfig;
 4import com.alibaba.fastjson.serializer.SerializerFeature;
 5import com.fasterxml.jackson.databind.JavaType;
 6import com.fasterxml.jackson.databind.ObjectMapper;
 7import com.fasterxml.jackson.databind.type.TypeFactory;
 8import org.springframework.data.redis.serializer.RedisSerializer;
 9import org.springframework.data.redis.serializer.SerializationException;
10import org.springframework.util.Assert;
11
12import java.nio.charset.Charset;
13
14/**
15 * @author felix
16 * @ 日期 2019-12-06 15:57
17 */
18public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> {
19
20    private ObjectMapper objectMapper = new ObjectMapper();
21    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
22
23    private Class<T> clazz;
24
25    static {
26        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
27        ParserConfig.getGlobalInstance().addAccept("com.openailab.oascloud");
28
29    }
30
31    public FastJson2JsonRedisSerializer(Class<T> clazz) {
32        super();
33        this.clazz = clazz;
34    }
35
36    @Override
37    public byte[] serialize(T t) throws SerializationException {
38        if (t == null) {
39            return new byte[0];
40        }
41        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
42    }
43
44    @Override
45    public T deserialize(byte[] bytes) throws SerializationException {
46        if (bytes == null || bytes.length <= 0) {
47            return null;
48        }
49        String str = new String(bytes, DEFAULT_CHARSET);
50
51        return JSON.parseObject(str, clazz);
52    }
53
54    public void setObjectMapper(ObjectMapper objectMapper) {
55        Assert.notNull(objectMapper, "'objectMapper' must not be null");
56        this.objectMapper = objectMapper;
57    }
58
59    protected JavaType getJavaType(Class<?> clazz) {
60        return TypeFactory.defaultInstance().constructType(clazz);
61    }
62}
63

ApplicationContextHolder

需要注意的是,这里不能通过autowire的方式引用redisTemplate,
因为RedisCache并不是Spring容器里的bean。所以我们需要手动地去调用容器的getBean方法来拿到这个bean;

 1import org.springframework.beans.BeansException;
 2import org.springframework.context.ApplicationContext;
 3import org.springframework.context.ApplicationContextAware;
 4import org.springframework.stereotype.Component;
 5
 6@Component
 7public class ApplicationContextHolder implements ApplicationContextAware {
 8    private static ApplicationContext applicationContext;
 9
10    @Override
11    public void setApplicationContext(ApplicationContext ctx) throws BeansException {
12        applicationContext = ctx;
13    }
14
15
16    public static ApplicationContext getApplicationContext() {
17        return applicationContext;
18    }
19
20
21    public static <T> T getBean(Class<T> clazz) {
22        return applicationContext.getBean(clazz);
23    }
24
25
26    @SuppressWarnings("unchecked")
27    public static <T> T getBean(String name) {
28        return (T) applicationContext.getBean(name);
29    }
30}
31

测试使用

 1@SpringBootTest
 2class MybatisCacheApplicationTests {
 3    @Autowired
 4    UserMapper userMapper;
 5
 6    @Test
 7    void select() {
 8        User user = userMapper.selectById(1);
 9        System.out.println(user);
10    }
11
12    @Test
13    public void update() {
14        User user = new User();
15        user.setId(1);
16        user.setUsername("user");
17        userMapper.update(user);
18
19    }
20
21}
22

参考

参考文章