老鬼的博客 来都来啦,那就随便看看吧~
springboot2使用springcache
发布于: 2023-09-13 更新于: 2023-09-13 分类于:  阅读次数: 

一:springcache介绍

1
2
3
SpringCache是一个框架,实现了基于注解缓存功能,只需要简单地加一个注解,
就能实现缓存功能。 SpringCache提高了一层抽象,底层可以切换不同的cache
实现,具体就是通过cacheManager接口来统一不同的缓存技术。

二:pom.xml

1
2
3
4
5
<!-- spring cache -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>

三:缓存注解

  • @EnableCaching:开启缓存功能,放在启动类上
  • @Cacheable:定义缓存,用于触发缓存
  • @CachePut:定义更新缓存,触发缓存更新
  • @CacheEvict:定义清除缓存,触发缓存清除
  • @Caching:组合定义多种缓存功能
  • @CacheConfig:定义公共设置,位于class之上

四:yml配置

1
2
3
4
5
spring:
cache: # springcache
type: redis
redis:
time-to-live: 180000 # 单位毫秒

五:简单使用

5.1 service案例

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
package com.tohours.bdminiapp.modules.common.service.impl;

import java.util.List;
import java.util.stream.Collectors;

import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.alibaba.fastjson.JSON;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tohours.bdminiapp.modules.common.entity.DicCity;
import com.tohours.bdminiapp.modules.common.entity.DicOffice;
import com.tohours.bdminiapp.modules.common.entity.DicOrg;
import com.tohours.bdminiapp.modules.common.mapper.DicCityMapper;
import com.tohours.bdminiapp.modules.common.mapper.DicOfficeMapper;
import com.tohours.bdminiapp.modules.common.mapper.DicOrgMapper;
import com.tohours.bdminiapp.modules.common.service.DicCityService;
import com.tohours.bdminiapp.modules.common.service.DicOfficeService;
import com.tohours.bdminiapp.modules.common.service.DicOrgService;

import lombok.extern.slf4j.Slf4j;

/**
* <p>
* 机构字典表 服务实现类
* </p>
*
* @author RenJie
* @since 2023-09-11 17:28:16
*/
@Service
@DS("common")//设置common数据库
@Transactional
@Slf4j
@CacheConfig(cacheNames = "bdminiapp-DicOrgServiceImpl")
public class DicOrgServiceImpl extends ServiceImpl<DicOrgMapper, DicOrg> implements DicOrgService {

@Resource
private DicCityService dicCityService;
@Resource
private DicCityMapper dicCityMapper;
@Resource
private DicOfficeMapper dicOfficeMapper;
@Resource
private DicOfficeService dicOfficeService;
@Resource
private JdbcTemplate jdbcTemplate;

@Override
@Cacheable(key = "#root.methodName+'_'+#orgCode")//缓存300s
public List<DicOrg> query(String orgCode) {
LambdaQueryWrapper<DicOrg> lam = new LambdaQueryWrapper<DicOrg>();
if(StringUtils.isNotEmpty(orgCode)) {
lam.eq(DicOrg::getCode, orgCode);
}

List<DicOrg> orgList = super.list(lam);
orgList.stream().map(org -> {
String tmpOrgCode = org.getCode();
List<DicCity> cityList = dicCityMapper.queryCitysByOrgCode(tmpOrgCode);
log.info("cityList:" + JSON.toJSONString(cityList));
cityList.stream().map(city -> {
String cityCode = city.getCode();
List<DicOffice> officeList = dicOfficeMapper.queryOfficesByCityCode(cityCode);
officeList.stream().map(office -> {
office.setType("office");
return office;
}).collect(Collectors.toList());
city.setChildren(officeList);
city.setType("city");
return city;
}).collect(Collectors.toList());
org.setChildren(cityList);
org.setType("org");
return org;
}).collect(Collectors.toList());
return orgList;
// }
}

}

5.2 @Cacheable中key

1
2
3
4
5
6
7
8
9
key属性是用来指定Spring缓存方法的返回结果时对应的key的。该属性支持SpringEL表达式。
当我们没有指定该属性时,Spring将使用默认策略生成key。

其中key可以设置常量和参数值,其中参数值可以使用#参数名,如#userName,也可以使用
参数的索引下标,如:#p0,#p1等。

除了上述使用方法参数作为key之外,Spring还为我们提供了一个root对象可以用来生成key。
通过该root对象我们可以获取到以下信息。

1.png

5.3 备注

1
@Cacheable是基于Spring AOP代理的,类内部方法调用是不走代理的@Cacheable是不起作用的。

六:自定义cacheName下的过期时间

6.1 cache介绍

1
2
springcache默认是不支持配置单个key或者单个cachename下的过期时间的,需要自己手动
配置RedisCacheManager。

6.2 配置介绍

1
2
3
1.只能配置到cachename下的过期时间
2.如果想要单独指定一个key的过期时间则需要将此key单独一个cachename
3.此配置必须按着一定的规则执行,如果匹配不到则会走默认的缓存时间

6.4 配置格式

1
2
@Cacheable(key = "'300'+#root.methodName+'_'+#orgCode",cacheNames ="bdminiapp-DicOrgServiceImpl-query")//缓存300s
其中key以#号分隔的第一个如果是数字则会匹配规则,此数字就是当前cacheName下的过期时间,单位是s

6.5 RedisConfig

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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
package com.tohours.bdminiapp.config.redis;

import java.io.File;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
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.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;

import cn.hutool.core.collection.CollUtil;
import lombok.extern.slf4j.Slf4j;

@EnableCaching
@Configuration
@Slf4j
public class RedisConfig {

// yml配置的缓存时间,单位是毫秒
@Value("${spring.cache.redis.time-to-live}")
private Integer timeToLive;

// 包的根路径,需要在此包下获取@Cacheable的注解并设置当前的cachename的过期时间
private static final String FULL_PACKAGE_NAME = "com.tohours.bdminiapp";

/**
* 配置redisTemplate针对不同key和value场景下不同序列化的方式
*
* @param factory Redis连接工厂
* @return
*/
@Primary
@Bean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer<Object> jackson2RedisSerializer = jackson2JsonRedisSerializer();
// 默认使用jdk序列化,
template.setDefaultSerializer(jackson2RedisSerializer);
return template;

}

// 配置Jackson2JsonRedisSerializer
// 避免出现获取缓存时出现的类型转换错误
@SuppressWarnings("deprecation")
private Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() {
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(
Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.configure(MapperFeature.USE_ANNOTATIONS, false);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);

// 此项必须配置,否则会报java.lang.ClassCastException: java.util.LinkedHashMap cannot be
// cast to XXX
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// 日期格式化
objectMapper.registerModule(new Jdk8Module()).registerModule(new JavaTimeModule())
.registerModule(new ParameterNamesModule());

return jackson2JsonRedisSerializer;
}

@Bean
RedisCache cache(RedisTemplate<String, Object> redisTemplate) {
return new com.tohours.bdminiapp.config.redis.RedisCacheManager(redisTemplate);
}

/**
* 自定义cacheManager,为了实现@cacheable针对cachaName整个层的过期时间,而不能单独指定一个key的过期时间,如果想单独指定
* 一个key的过期时间,则将这个key独立一层。 写法如下:
*
* @Cacheable(key =
* "'1000'+#root.methodName+'_'+#orgCode",cacheNames="queryOrg")
* 其中前面的1000是过期时间,单位是s,cacheNames就是自定义的名称,如果key开头不是数字#则使用默认的spring.cache.redis.time-to-live=180s
*
*/
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
return new RedisCacheManager(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
// 默认策略,未配置的 key 会使用这个
this.defaultCacheConfiguration(timeToLive / 1000),
// 指定 key 策略
this.getRedisCacheConfigurationMap());
}

/**
* @desc 默认redicCache的配置
* @param seconds
* @return
*/
private RedisCacheConfiguration defaultCacheConfiguration(Integer seconds) {
return RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer()))
.entryTtl(Duration.ofSeconds(seconds));
}

private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
// 通过反射工具获取到需要扫描的包下的所有的class
validAnnotation(loadClasses(FULL_PACKAGE_NAME), redisCacheConfigurationMap);
return redisCacheConfigurationMap;
}

@SuppressWarnings("rawtypes")
public static List<Class> loadClasses(String packageName) {
List<Class> classes = new ArrayList<>();
String path = packageName.replace(".", "/");
File dir = new File(Thread.currentThread().getContextClassLoader().getResource(path).getFile());
if (dir.exists()) {
for (File file : dir.listFiles()) {
String fileName = file.getName();
if (file.isDirectory()) {
List<Class> subClasses = loadClasses(packageName + "." + fileName);
classes.addAll(subClasses);
} else if (fileName.endsWith(".class")) {
String className = fileName.substring(0, fileName.length() - 6);
try {
classes.add(Class.forName(packageName + "." + className));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
return classes;
}

@SuppressWarnings("rawtypes")
private void validAnnotation(List<Class> clsList, Map<String, RedisCacheConfiguration> redisCacheConfigurationMap) {
if (CollUtil.isEmpty(clsList)) {
return;
}
clsList.forEach(cls -> {
// 1.0获取所有的方法
Method[] methods = cls.getDeclaredMethods();
if (Objects.isNull(methods)) {
return;// 如果类中无方法则返回
}

for (Method method : methods) {
// 2.0 获取方法上的@Cacheable注解
Cacheable cacheable = method.getAnnotation(Cacheable.class);
if (Objects.isNull(cacheable)) {
continue;// 如果方法无此注解则默认不做处理
}
// 3.0 按照定义好的规则截取出过期时间, 并进行设置
String[] split = cacheable.key().replace("'", "").replace("+", "").split("#");
if (split.length >= 2) {
Boolean isInt = isInt(split[0]);// 定义的规则是开头是时间,如果是int类型则走后续的定义失效时间的规则
if (isInt) {
try {
String[] methodCacheNames = cacheable.cacheNames();
if (methodCacheNames != null && methodCacheNames.length > 0) {
Arrays.stream(methodCacheNames).forEach(v -> redisCacheConfigurationMap.put(v,
this.defaultCacheConfiguration(Integer.valueOf(split[0]))));
}

} catch (Exception e) {
log.error("自定义SpringCache过期时间异常", e);
}

}

}
}
});
}

private Boolean isInt(String str) {
try {
Integer.valueOf(str);
} catch (Exception e) {
return false;
}
return true;
}
}

6.6 案例

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
package com.tohours.bdminiapp.modules.common.service.impl;

import java.util.List;
import java.util.stream.Collectors;

import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.alibaba.fastjson.JSON;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tohours.bdminiapp.modules.common.entity.DicCity;
import com.tohours.bdminiapp.modules.common.entity.DicOffice;
import com.tohours.bdminiapp.modules.common.entity.DicOrg;
import com.tohours.bdminiapp.modules.common.mapper.DicCityMapper;
import com.tohours.bdminiapp.modules.common.mapper.DicOfficeMapper;
import com.tohours.bdminiapp.modules.common.mapper.DicOrgMapper;
import com.tohours.bdminiapp.modules.common.service.DicCityService;
import com.tohours.bdminiapp.modules.common.service.DicOfficeService;
import com.tohours.bdminiapp.modules.common.service.DicOrgService;

import lombok.extern.slf4j.Slf4j;

/**
* <p>
* 机构字典表 服务实现类
* </p>
*
* @author RenJie
* @since 2023-09-11 17:28:16
*/
@Service
@DS("common")//设置common数据库
@Transactional
@Slf4j
@CacheConfig(cacheNames = "bdminiapp-DicOrgServiceImpl")
public class DicOrgServiceImpl extends ServiceImpl<DicOrgMapper, DicOrg> implements DicOrgService {

@Resource
private DicCityService dicCityService;
@Resource
private DicCityMapper dicCityMapper;
@Resource
private DicOfficeMapper dicOfficeMapper;
@Resource
private DicOfficeService dicOfficeService;
@Resource
private JdbcTemplate jdbcTemplate;

@Override
@Cacheable(key = "'300'+#root.methodName+'_'+#orgCode",cacheNames ="bdminiapp-DicOrgServiceImpl-query")//缓存300s
public List<DicOrg> query(String orgCode) {
LambdaQueryWrapper<DicOrg> lam = new LambdaQueryWrapper<DicOrg>();
if(StringUtils.isNotEmpty(orgCode)) {
lam.eq(DicOrg::getCode, orgCode);
}

List<DicOrg> orgList = super.list(lam);
orgList.stream().map(org -> {
String tmpOrgCode = org.getCode();
List<DicCity> cityList = dicCityMapper.queryCitysByOrgCode(tmpOrgCode);
log.info("cityList:" + JSON.toJSONString(cityList));
cityList.stream().map(city -> {
String cityCode = city.getCode();
List<DicOffice> officeList = dicOfficeMapper.queryOfficesByCityCode(cityCode);
officeList.stream().map(office -> {
office.setType("office");
return office;
}).collect(Collectors.toList());
city.setChildren(officeList);
city.setType("city");
return city;
}).collect(Collectors.toList());
org.setChildren(cityList);
org.setType("org");
return org;
}).collect(Collectors.toList());
return orgList;
// }
}

@Override
@Cacheable(key = "'300'+#root.methodName+'_'+#orgCode",cacheNames ="bdminiapp-DicOrgServiceImpl-queryAll")//缓存300s
public List<DicOrg> queryAll() {
return query(null);
}

@Override
@Cacheable(key = "'30'+#root.methodName",cacheNames="bdminiapp-DicOrgServiceImpl-test1")
public String test1() {
return "test1";
}

@Override
@Cacheable(key = "#root.methodName",cacheNames="bdminiapp-DicOrgServiceImpl-test2")
public String test2() {
return "test2";
}
}
*************感谢您的阅读*************