说明

  • 随着时间的积累,应用的使用用户不断增加,数据规模也越来越大,往往数据库查询操作会成为影响用户使用体验的瓶颈,此时使用缓存往往是解决这一问题非常好的手段之一。

  • Spring 3开始提供了强大的基于注解的缓存支持,可以通过注解配置方式低侵入的给原有Spring应用增加缓存功能,提高数据访问性能。

  • 在Spring Boot中对于缓存的支持,提供了一系列的自动化配置,使我们可以非常方便的使用缓存。

快速入门

User实体的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Entity
@Data
@NoArgsConstructor
public class User {

@Id
@GeneratedValue
private Long id;

private String name;
private Integer age;

public User(String name, Integer age) {
this.name = name;
this.age = age;
}
}

User实体的数据访问实现

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
@Service
public class UserService {
@Autowired private UserMapper userMapper;

@GetMapping("/user/all")
public List<User> getAllUser() {
return userMapper.selectByExample(null);
}

// 查询,使用缓存 查询结果为空则不缓存
@Cacheable(cacheNames = "users", key = "#name", unless = "#result==null")
public User getUserByName(@PathVariable("name") String name) {
UserExample example = new UserExample();
Criteria criteria = example.createCriteria();
criteria.andNameEqualTo(name.trim());
List<User> users = userMapper.selectByExample(example);
if (users.size() > 1) {
return null;
} else {
return users.get(0);
}
}
// ...
}

单元测试类

  • 插入User表一条用户名为AAA,年龄为10的数据。并通过findByName函数完成两次查询,具体代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class Chapter51ApplicationTests {

    @Autowired
    private UserService userService;

    @Autowired
    CacheManager cacheManager;

    @Test
    public void test1() {
    User user1 = userService.getUserByName("张三");
    System.out.println("-----------------");
    User user2 = userService.getUserByName("张三");

    }
    }
  • 在没有加入缓存之前,我们可以先执行一下这个案例,可以看到如下的日志:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    Creating a new SqlSession
    SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@43a09ce2] was not registered for synchronization because synchronization is not active
    2021-06-20 22:46:41.449 INFO 11548 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
    2021-06-20 22:46:41.792 INFO 11548 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
    JDBC Connection [HikariProxyConnection@278166606 wrapping com.mysql.cj.jdbc.ConnectionImpl@3667faa8] will not be managed by Spring
    ==> Preparing: select id, name, age from user WHERE ( name = ? )
    ==> Parameters: 张三(String)
    <== Columns: id, name, age
    <== Row: 1, 张三, 23
    <== Total: 1
    Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@43a09ce2]
    -----------------
    Creating a new SqlSession
    SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6ebc9573] was not registered for synchronization because synchronization is not active
    JDBC Connection [HikariProxyConnection@513241240 wrapping com.mysql.cj.jdbc.ConnectionImpl@3667faa8] will not be managed by Spring
    ==> Preparing: select id, name, age from user WHERE ( name = ? )
    ==> Parameters: 张三(String)
    <== Columns: id, name, age
    <== Row: 1, 张三, 23
    <== Total: 1
    Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6ebc9573]
  • 两次查询都执行了两次SQL,都是对MySQL数据库的查询。

引入缓存

  1. 第一步:在pom.xml中引入cache依赖,添加如下内容:

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
  2. 第二步:在Spring Boot主类中增加@EnableCaching注解开启缓存功能,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @EnableCaching
    @SpringBootApplication
    public class Chapter51Application {

    public static void main(String[] args) {
    SpringApplication.run(Chapter51Application.class, args);
    }

    }
  3. 第三步:在数据访问接口中,增加缓存配置注解,如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 查询,使用缓存 查询结果为空则不缓存
    @Cacheable(cacheNames = "users", key = "#name", unless = "#result==null")
    public User getUserByName(@PathVariable("name") String name) {
    UserExample example = new UserExample();
    Criteria criteria = example.createCriteria();
    criteria.andNameEqualTo(name.trim());
    List<User> users = userMapper.selectByExample(example);
    if (users.size() > 1) {
    return null;
    } else {
    return users.get(0);
    }
    }
  4. 第四步:再来执行以下单元测试,可以在控制台中输出了下面的内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Creating a new SqlSession
    SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4fd05028] was not registered for synchronization because synchronization is not active
    2021-06-20 22:48:59.046 INFO 15704 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
    2021-06-20 22:48:59.439 INFO 15704 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
    JDBC Connection [HikariProxyConnection@1157484092 wrapping com.mysql.cj.jdbc.ConnectionImpl@712c5463] will not be managed by Spring
    ==> Preparing: select id, name, age from user WHERE ( name = ? )
    ==> Parameters: 张三(String)
    <== Columns: id, name, age
    <== Row: 1, 张三, 23
    <== Total: 1
    Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4fd05028]
    -----------------

  • 到这里,我们可以看到,在调用第二次getUserByName函数时,没有再执行select语句,也就直接减少了一次数据库的读取操作。

  • 为了可以更好的观察,缓存的存储,我们可以在单元测试中注入CacheManager

    1
    2
    @Autowired
    private CacheManager cacheManager;
  • 使用debug模式运行单元测试,观察CacheManager中的缓存集users以及其中的User对象的缓存加深理解。

ConcurrentMap Cache

  • Spring boot默认使用的是SimpleCacheConfiguration,即使用ConcurrentMapCacheManager来实现缓存

  • ConcurrentMapCache实质是一个ConcurrentHashMap集合对象java内置,所以无需引入其他依赖,也没有额外的配置

  • ConcurrentMapCache的自动装配声明在SimpleCacheConfiguration中,如果需要也可对它进行额外的装

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //注册1个id为cacheManager,类型为ConcurrentMapCacheManager的bean
    @Bean
    public ConcurrentMapCacheManager cacheManager() {
    //实例化ConcurrentMapCacheManager
    ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
    //读取配置文件,如果配置有spring.cache.cache-names=xx,xx,则进行配置cacheNames,默认是没有配置的
    List<String> cacheNames = this.cacheProperties.getCacheNames();
    if (!cacheNames.isEmpty()) {
    cacheManager.setCacheNames(cacheNames);
    }
    //调用CacheManagerCustomizers#customize 进行个性化设置,在该方法中是遍历其持有的List
    return this.customizerInvoker.customize(cacheManager);
    }

缓存提供者

在Spring Boot中通过@EnableCaching注解自动化配置合适的缓存管理器(CacheManager),Spring Boot根据下面的顺序去侦测缓存提供者:

  • Generic
  • JCache (JSR-107) (EhCache 3, Hazelcast, Infinispan, and others)
  • EhCache 2.x
  • Hazelcast
  • Infinispan
  • Couchbase
  • Redis
  • Caffeine
  • Simple

Caffeine Cache

Caffeine参数说明

  • initialCapacity=[integer]: 初始的缓存空间大小
  • maximumSize=[long]: 缓存的最大条数
  • maximumWeight=[long]: 缓存的最大权重
  • expireAfterAccess=[duration]: 最后一次写入或访问后经过固定时间过期
  • expireAfterWrite=[duration]: 最后一次写入后经过固定时间过期
  • refreshAfterWrite=[duration]: 创建缓存或者最近一次更新缓存后经过固定的时间间隔,刷新缓存 refreshAfterWrite requires a LoadingCache
  • weakKeys: 打开key的弱引用
  • weakValues:打开value的弱引用
  • softValues:打开value的软引用
  • recordStats:开发统计功能

注意

  • refreshAfterWrite必须实现LoadingCache,跟expire的区别是,指定时间过后,expire是remove该key,下次访问是同步去获取返回新值,而refresh则是指定时间后,不会remove该key,下次访问会触发刷新,新值没有回来时返回旧值
  • expireAfterWrite和expireAfterAccess同事存在时,以expireAfterWrite为准。
  • maximumSize和maximumWeight不可以同时使用
  • weakValues和softValues不可以同时使用

导入依赖

1
2
3
4
5
6
<!-- 使用  caffeine https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/caffeine -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.6.0</version>
</dependency>

Caffeine配置

  1. 通过配置文件来设置Caffeine

    1
    2
    3
    4
    5
    6
    spring:
    cache:
    cache-names: outLimit,notOutLimit
    caffeine:
    spec: initialCapacity=50,maximumSize=500,expireAfterWrite=5s,refreshAfterWrite=7s #
    type: caffeine
  2. 通过bean装配

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Bean()
    @Primary
    public CacheManager cacheManagerWithCaffeine() {
    CaffeineCacheManager cacheManager = new CaffeineCacheManager();
    Caffeine caffeine = Caffeine.newBuilder()
    .initialCapacity(100) //cache的初始容量值
    .maximumSize(1000) //maximumSize用来控制cache的最大缓存数量,maximumSize和maximumWeight不可以同时使用,
    .maximumWeight(100) //控制最大权重
    .expireAfter(customExpireAfter) //自定义过期
    .refreshAfterWrite(5, TimeUnit.SECONDS); //使用refreshAfterWrite必须要设置cacheLoader
    cacheManager.setCaffeine(caffeine);
    cacheManager.setCacheLoader(cacheLoader); //缓存加载方案
    cacheManager.setCacheNames(getNames()); //缓存名称列表
    cacheManager.setAllowNullValues(false);
    return cacheManager;
    }
  3. 配置文件结合Bean装配

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Value("${caffeine.spec}")
    private String caffeineSpec;
    @Bean(name = "caffeineSpec")
    public CacheManager cacheManagerWithCaffeineFromSpec(){
    CaffeineSpec spec = CaffeineSpec.parse(caffeineSpec);
    Caffeine caffeine = Caffeine.from(spec); // 或使用 Caffeine caffeine = Caffeine.from(caffeineSpec);
    CaffeineCacheManager cacheManager = new CaffeineCacheManager();
    cacheManager.setCaffeine(caffeine);
    cacheManager.setCacheNames(getNames());
    return cacheManager;
    }

实现cacheLoader

  • CacheLoader是cache的一种加载策略,key不存在或者key过期之类的都可以通过CacheLoader来自定义获得/重新获得数据。

  • 使用refreshAfterWrite必须要设置cacheLoader

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Configuration
    public class CacheConfig {
    @Bean
    public CacheLoader<Object, Object> cacheLoader() {
    CacheLoader<Object, Object> cacheLoader = new CacheLoader<Object, Object>() {
    @Override
    public Object load(Object key) throws Exception {
    return null;
    }
    // 达到配置文件中的refreshAfterWrite所指定的时候回处罚这个事件方法
    @Override
    public Object reload(Object key, Object oldValue) throws Exception {
    return oldValue; //可以在这里处理重新加载策略,本例子,没有处理重新加载,只是返回旧值。
    }
    };
    return cacheLoader;
    }
    }
  • CacheLoader实质是一个监听,处上述load与reload还包含,expireAfterCreate,expireAfterUpdate,expireAfterRead等可以很灵活的配置CacheLoade

  • 具体详细配置可查看这里 https://www.jianshu.com/p/15d0a9ce37dd

  • 详细原理查看:https://www.cnblogs.com/liujinhua306/p/9808500.html

EhCache

  • EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认CacheProvider。

  • Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。

  • 它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个gzip缓存servlet过滤器,支持REST和SOAP api等特点。

  • 接下来我们通过下面的几步操作,就可以轻松的把上面的缓存应用改成使用ehcache缓存管理。

引入ehcache依赖

  • pom.xml中引入ehcache依赖, 在Spring Boot的parent管理下,不需要指定具体版本,会自动采用Spring Boot中指定的版本号。

    1
    2
    3
    4
    <dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    </dependency>

加入配置

  • 配置文件

    1
    2
    3
    spring.cache.type=EHCACHE # 配置ehcache缓存
    # 指定ehcache配置文件路径 ,可以不用写,因为默认就是这个路径,SpringBoot会自动扫描
    spring.cache.ehcache.config=classpath:/ehcache.xml
  • src/main/resources目录下创建:ehcache.xml, ehcache.xml只需要放到类路径下面,SpringBoot会自动扫描

    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
    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="ehcache.xsd">

    <!--
    磁盘存储:指定一个文件目录,当EHCache把数据写到硬盘上时,将把数据写到这个文件目录下
    path:指定在硬盘上存储对象的路径
    path可以配置的目录有:
    user.home(用户的家目录)
    user.dir(用户当前的工作目录)
    java.io.tmpdir(默认的临时目录)
    ehcache.disk.store.dir(ehcache的配置目录)
    绝对路径(如:d:\\ehcache)
    查看路径方法:String tmpDir = System.getProperty("java.io.tmpdir");
    -->
    <diskStore path="java.io.tmpdir" />

    <!--
    defaultCache:默认的缓存配置信息,如果不加特殊说明,则所有对象按照此配置项处理
    maxElementsInMemory:设置了缓存的上限,最多存储多少个记录对象
    eternal:代表对象是否永不过期 (指定true则下面两项配置需为0无限期)
    timeToIdleSeconds:最大的发呆时间 /秒
    timeToLiveSeconds:最大的存活时间 /秒
    overflowToDisk:是否允许对象被写入到磁盘
    说明:下列配置自缓存建立起600秒(10分钟)有效 。
    在有效的600秒(10分钟)内,如果连续120秒(2分钟)未访问缓存,则缓存失效。
    就算有访问,也只会存活600秒。
    -->
    <defaultCache maxElementsInMemory="10000" eternal="false"
    timeToIdleSeconds="600" timeToLiveSeconds="600" overflowToDisk="true" />
    <!-- 按缓存名称的不同管理策略 -->
    <cache name="users" maxElementsInMemory="10000" eternal="false"
    timeToIdleSeconds="120" timeToLiveSeconds="600" overflowToDisk="true" />

    </ehcache>

装配

  • SpringBoot会为我们自动配置 EhCacheCacheManager 这个Bean,如果想自定义设置一些个性化参数时,通过Java Config形式配置。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Configuration
    @EnableCaching
    public class CacheConfig {

    @Bean
    public CacheManager cacheManager() {
    return new EhCacheCacheManager(ehCacheCacheManager().getObject());
    }

    @Bean
    public EhCacheManagerFactoryBean ehCacheCacheManager() {
    EhCacheManagerFactoryBean cmfb = new EhCacheManagerFactoryBean();
    cmfb.setConfigLocation(new ClassPathResource("ehcache.xml"));
    cmfb.setShared(true);
    return cmfb;
    }

    }

测试

  • 执行测试代码,debug模式下可以看到采用EhCacheCacheManager,第二次查询不走数据库

Redis

Redis 优势

  • 分布式
  • 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
  • 丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
  • 原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
  • 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性

导入依赖

  • 就只需要这一个依赖!不需要spring-boot-starter-cache

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
  • 导入这一个依赖时,SpringBoot的CacheManager就会使用RedisCache。

  • Redis使用模式使用pool2连接池,在需要时引用下面的依赖

    1
    2
    3
    4
    5
    6
    <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->
    <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.6.2</version>
    </dependency>

配置Redis

1
2
3
4
5
6
7
8
9
spring.redis.database=1 # Redis数据库索引(默认为0)
spring.redis.host=127.0.0.1 # Redis服务器地址
spring.redis.port=6379 # Redis服务器连接端口
spring.redis.password= # Redis服务器连接密码(默认为空)
spring.redis.pool.max-active=1000 # 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-wait=-1 # 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-idle=10 # 连接池中的最大空闲连接
spring.redis.pool.min-idle=2 # 连接池中的最小空闲连接
spring.redis.timeout=0 # 连接超时时间(毫秒)

装配

  • 如果需要自定义缓存配置可以通过,继承CachingConfigurerSupport类,手动装配,如果一切使用默认配置可不必

序列化类型

1
2
3
4
5
6
7
8
9
10
@Bean
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory connectionFactory) {
// 配置redisTemplate
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());//key序列化
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());//value序列化
redisTemplate.afterPropertiesSet();
return redisTemplate;
}

过期时间

  • 配置文件方式

    1
    2
    3
    4
    5
    6
    7
    spring:
    cache:
    type: REDIS
    ehcache:
    config: classpath:/ehcache.xml
    redis:
    time-to-live: 20000 #缓存超时时间ms
  • 代码配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /**
    * 通过RedisCacheManager配置过期时间
    *
    * @param redisConnectionFactory
    * @return
    */
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
    RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
    .entryTtl(Duration.ofHours(1)); // 设置缓存有效期一小时
    return RedisCacheManager
    .builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
    .cacheDefaults(redisCacheConfiguration).build();
    }

完整的装配类

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
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
package tk.fulsun.demo.config;

import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;

import java.lang.reflect.Method;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;

/**
* @Description TODO
* @Date 2021/6/20
* @Created by 凉月-文
*/
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
public RedisConfig() {
super();
}

/**
* 指定使用哪一种缓存
* 通过RedisCacheManager配置过期时间
*
* @param factory
* @return
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
// RedisCacheManager rcm = RedisCacheManager.create(redisConnectionFactory);
// return rcm;

// 设置缓存有效期一小时
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1));
return RedisCacheManager
.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
.cacheDefaults(redisCacheConfiguration).build();
}

/**
* 指定默认的key生成方式
*
* @return
*/
@Override
public KeyGenerator keyGenerator() {
KeyGenerator keyGenerator = new KeyGenerator() {
@Override
public Object generate(Object o, Method method, Object... objects) {
StringBuilder sb = new StringBuilder();
sb.append(o.getClass().getName());
sb.append(method.getName());
for (Object obj : objects) {
sb.append(obj.toString());
}
return sb.toString();
}
};
return keyGenerator;
}

@Override
public CacheResolver cacheResolver() {
return super.cacheResolver();
}

@Override
public CacheErrorHandler errorHandler() {
return super.errorHandler();
}

/**
* redis 序列化策略 ,通常情况下key值采用String序列化策略
* StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。StringRedisSerializer
* RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。JdkSerializationRedisSerializer
*
* @param factory
* @return
*/
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);

// // 使用Jackson2JsonRedisSerialize 替换默认序列化
// 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);
//
//
// //设置value的序列化方式
// redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
// //设置key的序列化方式
// redisTemplate.setKeySerializer(new StringRedisSerializer());
// redisTemplate.setHashKeySerializer(new StringRedisSerializer());
// redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

//使用fastJson作为默认的序列化方式
GenericFastJsonRedisSerializer genericFastJsonRedisSerializer = new GenericFastJsonRedisSerializer();
redisTemplate.setDefaultSerializer(genericFastJsonRedisSerializer);
redisTemplate.setValueSerializer(genericFastJsonRedisSerializer);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(genericFastJsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.afterPropertiesSet();

return redisTemplate;

}

/**
* 转换返回的object为json
*
* @return
*/
@Bean
public HttpMessageConverters fastJsonHttpMessageConverters() {
// 1、需要先定义一个converter 转换器
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
// 2、添加fastJson 的配置信息,比如:是否要格式化返回的json数据
FastJsonConfig fastJsonConfig = new FastJsonConfig();
SerializerFeature[] serializerFeatures = new SerializerFeature[]{
// 输出key是包含双引号
// SerializerFeature.QuoteFieldNames,
// 是否输出为null的字段,若为null 则显示该字段
// SerializerFeature.WriteMapNullValue,
// 数值字段如果为null,则输出为0
SerializerFeature.WriteNullNumberAsZero,
// List字段如果为null,输出为[],而非null
SerializerFeature.WriteNullListAsEmpty,
// 字符类型字段如果为null,输出为"",而非null
SerializerFeature.WriteNullStringAsEmpty,
// Boolean字段如果为null,输出为false,而非null
SerializerFeature.WriteNullBooleanAsFalse,
// Date的日期转换器
SerializerFeature.WriteDateUseDateFormat,
// 循环引用
SerializerFeature.DisableCircularReferenceDetect,
};
// fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
fastJsonConfig.setSerializerFeatures(serializerFeatures);
// 中文乱码解决方案
// 默认SupportedMediaTypes值为 */* 这样对于很多浏览器是识别不了具体的格式和编码类型的,所以出现乱码
List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(MediaType.APPLICATION_JSON_UTF8);//设定json格式且编码为UTF-8
fastConverter.setSupportedMediaTypes(mediaTypes);
// 3、在convert 中添加配置信息
fastConverter.setFastJsonConfig(fastJsonConfig);
// 4、将convert 添加到converters当中
HttpMessageConverter<?> converter = fastConverter;
return new HttpMessageConverters(converter);
}
}

Redis编程模板

  • 除了使用注解,Spring boot集成 Redis 客户端jedis。封装Redis 连接池,以及操作模板,可以方便的显示的在方法的代码中处理缓存对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @Autowired
    private StringRedisTemplate stringRedisTemplate;//操作key-value都是字符串

    @Autowired
    private RedisTemplate redisTemplate;//操作key-value都是对象

    @Autowired
    private RedisCacheManager redisCacheManager;
    /**
    * Redis常见的五大数据类型:
    * stringRedisTemplate.opsForValue();[String(字符串)]
    * stringRedisTemplate.opsForList();[List(列表)]
    * stringRedisTemplate.opsForSet();[Set(集合)]
    * stringRedisTemplate.opsForHash();[Hash(散列)]
    * stringRedisTemplate.opsForZSet();[ZSet(有序集合)]
    */
    public void test(){
    stringRedisTemplate.opsForValue().append("msg","hello");
    Cache emp = redisCacheManager.getCache("emp");
    emp.put("111", "222");
    }