【12】SpringBoot-Redis相关
文章目录
一、spring-data-redis 简介
Spring 提供了一个 RedisConnectionFactory 接口,通过它可以生成一个RedisConnection接口对象, 而 RedisConnection 接口对象是对 Redis底层接口的封装。例如,下面使用的 Jedis 驱动,Spring 就会提供 RedisConnection 接口的实现类 JedisConnection 去封装原有的 Jedis(redis.clients.jedis.Jedis)对象。
查看类图
从图可看出,Spring中是通过 RedisConnection接口操作 Redis的,而RedisConnection 则对原生的 Jedis进行封装。要获取 RedisConnection接口对象,是通过 RedisConnectionFactory 接口去生成的,
所以第一步要配置这个redis连接工厂,配置这个工厂主要是配置 Redis的连接池,对于连接池可以限定其最大连接数、超时时间等属性。如下
@Configuration // 都可以在springboot配置文件中配置
public class RedisConfig {
private RedisConnectionFactory connectionFactory;
// 自定义配置redis连接工厂
@Bean(name = "redisConnectionFactory")
public RedisConnectionFactory initRedisConnectionFactory() {
if (Objects.nonNull(this.connectionFactory)) {
return this.connectionFactory;
}
JedisPoolConfig poolConfig = new JedisPoolConfig();
// 最大空闲数
poolConfig.setMaxIdle(30);
// 最大连接数
poolConfig.setMaxTotal(50);
// 最大等待毫秒数
poolConfig.setMaxWaitMillis(2000);
// 创建Jedis连接工厂
JedisConnectionFactory connectionFactory = new JedisConnectionFactory(poolConfig);
// 获取单机的redis配置
RedisStandaloneConfiguration rsCfg = connectionFactory.getStandaloneConfiguration();
connectionFactory.setHostName("192.168.11.131");
connectionFactory.setPort(6379);
connectionFactory.setPassword("123456");
this.connectionFactory = connectionFactory;
return connectionFactory;
}
// ...
}
1.1 RedisTemplate
RedisTemplate 是使用最多的类,它会自动从 RedisConnectionFactory 工厂中获取连接,然后执行对应的 Redis命令,最后关闭 Redis连接。
对象是无法存储到redis中的,不过 Java 提供了序列化机制,只要类实现了 Serializable接口,就能对类对象进行序列化就能得到二进制字符串,然后Redis 就可以将这些类对象以字符串进行存储。并且可以从redis中取出进行反序列化为对象。Spring中,提供了序列化器的机制,并且实现了几个序列化器,如图
查看序列化器接口
public interface RedisSerializer<T> {
// 序列化对象
@Nullable
byte[] serialize(@Nullable T var1) throws SerializationException;
// 反序列化
@Nullable
T deserialize(@Nullable byte[] var1) throws SerializationException;
// ...
}
-
StringRedisSeralizer 是针对字符串
-
JdkSerializationRedisSerializer 是 RedisTemplate 的默认序列化器。如果使用这个序列化器存储键值对,那么取键的时候并不是原来的键。(比如存储了一个 key1 ,通过 keys *key1。可看到存的是 “/xac/xbc/…/key1"这样一个键。这样我们取 redis 的数据就比较困难
RedisTemplate 可配置的属性:
属性 | 描述 | 备注 |
---|---|---|
如何解决?把 redis的键用 普通字符串保存,如下配置
@Bean(name = "redisTemplate")
public RedisTemplate<Object, Object> initRedisTemplate(){
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
// RedisTemplate 会自动初始化 StringRedisSerializer ,所以这里直接获取
RedisSerializer<String> stringSerializer = redisTemplate.getStringSerializer();
// 设置字符串序列化器,这样 Spring就会把 Redis的key当做字符串处理了
redisTemplate.setKeySerializer(stringSerializer);
redisTemplate.setHashKeySerializer(stringSerializer);
redisTemplate.setHashValueSerializer(stringSerializer);
redisTemplate.setConnectionFactory(initRedisConnectionFactory());
return redisTemplate;
}
1.2 Spring 对 Redis数据类型操作
Redis 可支持 7种类型的数据结构,这7种是 字符串、散列、列表(链表)、集合、有序集合、基数和地理位置。Spring 为每一种都封装了对应的操作
// 列表操作
ListOperations listOps = redisTemplate.opsForList();
// 字符串
ValueOperations valueOps = redisTemplate.opsForValue();
// 普通set集合
SetOperations setOps = redisTemplate.opsForSet();
// 有序set集合
ZSetOperations zSetOps = redisTemplate.opsForZSet();
// 散列操作接口
HashOperations hashOps = redisTemplate.opsForHash();
// 地理位置操作接口
GeoOperations geoOps = redisTemplate.opsForGeo();
// 基数操作接口
HyperLogLogOperations hyperLogLogOps = redisTemplate.opsForHyperLogLog();
有时需连续多次操作一个散列类型或列表,可使用 “绑定对象” ,即 BoundXXXOperations 接口,如下
// Geo
BoundGeoOperations geoKey = redisTemplate.boundGeoOps("geoKey");
// hash
BoundHashOperations hashKey = redisTemplate.boundHashOps("hashKey");
// list
BoundListOperations list = redisTemplate.boundListOps("list");
// set
BoundSetOperations setKey = redisTemplate.boundSetOps("setKey");
// string
BoundValueOperations stringKey = redisTemplate.boundValueOps("stringKey");
// zset
BoundZSetOperations zsetKey = redisTemplate.boundZSetOps("zsetKey");
1.3 SessionCallback 和 RedisCallback 接口
这2个接口的作用:让 RedisTemplate 进行回调,通过它们可以在一条连接执行多个 Redis 命令。SesssionCallback 封装比较好,推荐使用。RedisCallback 接口比较底层,需要处理的内容多,可读性差。
都是通过 RedisTemplate的 execute()方法进行调用的
使用示例:
/**
* 使用1个redis连接执行多条语句 SessionCallback :高级接口,推荐使用
*/
public static void useSessionCallback(RedisTemplate redisTemplate) {
redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations ro) throws DataAccessException {
ro.opsForValue().set("key2", "val1");
ro.opsForHash().put("myHash", "field", "hval33");
return null;
}
});
}
/**
* RedisCallback:需要处理底层的转换规则,如果不考虑改写底层,尽量不要使用
*/
public static void useRedisCallback(RedisTemplate redisTemplate) {
redisTemplate.execute((RedisCallback) rc -> {
rc.set("key3".getBytes(), "val4".getBytes());
rc.hSet("hash".getBytes(), "field3".getBytes(), "hval8".getBytes());
return null;
});
}
二、SpringBoot 中配置 Redis
springboot 中redis的配置
# 数据库配置
spring:
datasource:
username: root
password: admin
url: jdbc:mysql:///eesy?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
# 配置redis连接池 ------> RedisConnectionFactory 配置的内容
jedis:
pool:
min-idle: 5 # 最小空闲数
max-active: 10 # 最大活动数
max-idle: 10 # 最大空闲数
max-wait: 2000 # 获取连接最大等待时间
# redis服务器属性
port: 6379
host: 127.0.0.1
# password: 123456
# redis 连接池超时时间,单位毫秒
timeout: 1000
通过上面的配置,SpringBoot 就可以自动配置 Redis相关的操作对象。比如 RedisConnectionFactory,RedisTemplate,StringRedisTemplate 等
然后配置序列化器,在 SpringBoot 启动类下配置
@SpringBootApplication
@EnableCaching // 使用注解缓存驱动机制
public class SpringbootDataMybatis01Application {
@Autowired
private RedisTemplate redisTemplate;
// 定义 Spring-自定义后初始化方法
@PostConstruct
public void init() {
this.initRedisTemplate();
}
public void initRedisTemplate() {
// RedisTemplate 默认定义了一个 StringRedisSerializer
RedisSerializer<String> stringSerializer = redisTemplate.getStringSerializer();
// 设置字符串序列化器,这样 Spring就会把 Redis的key当做字符串处理了
redisTemplate.setKeySerializer(stringSerializer);
redisTemplate.setHashKeySerializer(stringSerializer);
redisTemplate.setHashValueSerializer(stringSerializer);
}
public static void main(String[] args) {
SpringApplication.run(SpringbootDataMybatis01Application.class, args);
}
}
2.1 操作 redis字符串和散列数据类型
@RequestMapping("/string-hash")
public String testStringAndHash() {
redisTemplate.opsForValue().set("key1", "val1");
// 默认,使用jdk的序列化器,所以redis保存时不是整数,不能运算
redisTemplate.opsForValue().set("int_key", "1");
// 使用 字符串型的序列化器
stringRedisTemplate.opsForValue().set("int", "1");
// 运算+1
stringRedisTemplate.opsForValue().increment("int", 1);
// 获取底层jedis连接
Jedis jedis = (Jedis) stringRedisTemplate.getConnectionFactory().getConnection().getNativeConnection();
// 减1操作,这个操作RedisTemplate不支持
jedis.decr("int");
// ####################### hash操作
HashMap<String, String> hash = new HashMap<>();
hash.put("field1", "val1");
hash.put("field2", "val2");
// 存入一个散列数据类型
stringRedisTemplate.opsForHash().putAll("hash", hash);
// 新增字段
stringRedisTemplate.opsForHash().put("hash", "field3", "val3");
// 绑定散列操作的key,连续对一个key操作
BoundHashOperations<String, Object, Object> hashOps = stringRedisTemplate.boundHashOps("hash");
// 删除2个字段
hashOps.delete("field1", "field2");
// 新增字段
hashOps.put("field4", "val4");
return "success";
}
2.2 操作列表(链表
@RequestMapping("/redis-list")
public String testList() {
// v4 v3 v2 v1 从左边插入
stringRedisTemplate.opsForList().leftPushAll("list1", "v1", "v2", "v3", "v4");
// v1 v2 v3 v4
stringRedisTemplate.opsForList().rightPushAll("list2", "v1", "v2", "v3", "v4");
// 绑定list2链表操作
BoundListOperations<String, String> listOps = stringRedisTemplate.boundListOps("list2");
// 从右边弹出一个成员
String v4 = listOps.rightPop();
// 获取定位元素
String v2 = listOps.index(1);
// 从左边插入链表
Long v0 = listOps.leftPush("v0");
// 链表长度
Long len = listOps.size();
// 求链表下标区间成员,范围[0,len-1]
List<String> elements = listOps.range(0, len - 2);
elements.forEach(System.out::print);
return "success";
}
2.3 操作集合
@RequestMapping("/redis-set")
public String testSet() {
stringRedisTemplate.opsForSet().add("set1", "v1", "v2", "v3", "v3", "v4");
stringRedisTemplate.opsForSet().add("set2", "v0", "v2", "v3", "v4", "v5");
// 绑定set1集合操作
BoundSetOperations<String, String> setOps = stringRedisTemplate.boundSetOps("set1");
// 增加2个元素
setOps.add("v6", "v7");
// 删除2个元素
setOps.remove("v1", "v2");
// 返回所有元素
Set<String> set1 = setOps.members();
// 求成员数
Long size = setOps.size();
// 求交集
Set<String> inner = setOps.intersect("set2");
// 求交集,用新集合inner保存
setOps.intersectAndStore("set2", "inner");
// 求差集,用diff保存
Set<String> diff = setOps.diff("set2");
setOps.diffAndStore("set2", "diff");
// 求并集,用新集合 union保存-->redis中
Set<String> union = setOps.union("set2");
setOps.unionAndStore("set2", "union");
return "success";
}
2.4 操作有序集合
Spring中使用 TypedTuple 保存zSet元素
/**
* Spring中使用 TypedTuple保存zSet元素
*/
@RequestMapping("/redis-zset")
public String testZset() {
HashSet<ZSetOperations.TypedTuple<String>> typedTupleSet = new HashSet<>();
for (int i = 1; i <= 9; i++) {
double score = i * 0.1;
ZSetOperations.TypedTuple<String> typedTuple = new DefaultTypedTuple<>("value" + i, score);
typedTupleSet.add(typedTuple);
}
// 插入元素
stringRedisTemplate.opsForZSet().add("zset1", typedTupleSet);
// 绑定操作
BoundZSetOperations<String, String> zsetOps = stringRedisTemplate.boundZSetOps("zset1");
// 添加元素
zsetOps.add("value10", 0.12);
Set<String> setRange = zsetOps.range(1, 6);
// 按分数排序获取有序元素
Set<String> setScore = zsetOps.rangeByScore(0.2, 0.6);
// 定义值范围
RedisZSetCommands.Range range = new RedisZSetCommands.Range();
range.gt("value3"); // >= value3
//range.gte("value8"); // < value8
//range.lt("value8"); // < value8
//range.lte("value8"); // <= value8
// 按值排序,字符串排序 结果:value4~9
Set<String> setLex = zsetOps.rangeByLex(range);
// 删除元素
zsetOps.remove("value9", "vlaue2");
// 求分数
Double score = zsetOps.score("value8");
// 在下标区间,按分数排序,返回value和score
Set<ZSetOperations.TypedTuple<String>> rangeSet = zsetOps.rangeWithScores(1, 6);
// 在分数区间,按分数排序,返回val score 结果:null
Set<ZSetOperations.TypedTuple<String>> scoreSet = zsetOps.rangeByScoreWithScores(1, 6);
// 按从大到小排序
Set<String> reverseSet = zsetOps.reverseRange(2, 8);
return "success";
}
三、Redis 特殊用法
3.1 使用 Redis事务
redis 是支持一定事务能力的 NoSQL,在Redis中使用事务,通常的命令组合是 watch…multi…exec,也就是在一个Redis 连接中执行多个命令,这时可考虑使用 SessionCallback接口来做
// Redis事务测试
@RequestMapping("/redis-multi")
public String testMulti() {
redisTemplate.opsForValue().set("key1", "value1");
List list = (List) redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
// 设置监控key1
operations.watch("key1");
// 开启事务,在exec命令执行前,现在只是进入队列
operations.multi();
// 将命令放入队列
operations.opsForValue().set("key2", "value2");
operations.opsForValue().set("key3", "value3");
// 获取key2,此时value=null,因为命令还在队列里未执行
Object val2 = operations.opsForValue().get("key2");
Object val3 = operations.opsForValue().get("key3");
return operations.exec();
}
});
System.out.println(list);
return "redis-multi";
}
为什么要使用 监控 “key1”,因为根据 redis 的事务规则,我们先监控 key1,然后使用 multi 开启事务,让后面的redis命令进入执行队列,如果在 exec()方法前修改了 key1 ,就会取消事务。没有就可以执行事务
3.2 Redis 流水线
默认情况下,redis命令是一条条命令发送给redis 服务器的,这样显然性能不高。对于redis,可以使用流水线(pipeline)技术,提高redis的性能
// 使用 redis 流水线
@RequestMapping("/redis-pipeline")
public String testPipeline() {
long start = System.currentTimeMillis();
List list = redisTemplate.executePipelined(new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
for (int i = 1; i < 100000; i++) {
operations.opsForValue().set("pipeline_" + i, "value_" + i);
String value = (String) operations.opsForValue().get("pipeline_" + i);
if (i == 100000) {
System.out.println("命令只是进入队列,所以值为空【" + value + "】");
}
}
return null;
}
});
long end = System.currentTimeMillis();
System.out.println(">>>>>>>>>>>>>>>>>>>>> 耗时:" + (end - start) + " 毫秒");
return "redis-pipeline";
}
3.3 使用 Redis发布订阅
发布订阅是消息的一种常见模式。对于 Redis,可提供一个渠道,消息发送到这个渠道,其他系统监听这个渠道。如图
我们先定义一个消息监听器(MessageListener)
// Redis消息监听器
@Component
public class RedisMessageListener implements MessageListener {
/**
* 得到消息后的处理方法,
* @param message redis发送过来的消息
* @param pattern 渠道名称
*/
@Override
public void onMessage(Message message, byte[] pattern) {
// 消息体
String body = new String(message.getBody());
// 渠道名称
String topic = new String(pattern);
System.out.println("消息体: "+body);
System.out.println("渠道名称: "+topic);
}
}
message参数代表 Redis发送过来的消息,pattern 是消息渠道。
然后去springboot 启动类去配置,使之能够监控 redis的消息,如下
@SpringBootApplication
@EnableCaching // 使用注解缓存驱动机制
public class SpringbootDataMybatis01Application {
@Autowired
private RedisTemplate redisTemplate;
// Redis连接工厂 --->springboot自动创建
@Autowired
private RedisConnectionFactory connectionFactory;
// Redis消息监听器
@Autowired
private MessageListener redisMsgListener;
// 任务池
private ThreadPoolTaskScheduler taskScheduler;
// 定义自定义后初始化方法
@PostConstruct
public void init() {
this.initRedisTemplate();
}
public void initRedisTemplate() {
// RedisTemplate 默认定义了一个 StringRedisSerializer
RedisSerializer<String> stringSerializer = redisTemplate.getStringSerializer();
// 设置字符串序列化器,这样 Spring就会把 Redis的key当做字符串处理了
redisTemplate.setKeySerializer(stringSerializer);
redisTemplate.setHashKeySerializer(stringSerializer);
redisTemplate.setHashValueSerializer(stringSerializer);
}
/**
* 创建任务池,运行线程,进行阻塞,等待处理redis消息
*/
@Bean
public ThreadPoolTaskScheduler initTaskScheduler(){
if(this.taskScheduler != null){
return this.taskScheduler;
}
taskScheduler = new ThreadPoolTaskScheduler();
// 设置任务池大小
taskScheduler.setPoolSize(20);
return taskScheduler;
}
/**
* 定义Redis的监听容器
*/
@Bean
public RedisMessageListenerContainer initRedisContainer(){
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
// Redis连接池工厂
container.setConnectionFactory(connectionFactory);
// 设置运行任务池
container.setTaskExecutor(this.initTaskScheduler());
// 定义监听渠道,名称为topic1
ChannelTopic topic = new ChannelTopic("topic1");
// 使用 监听器监听Redis的消息
container.addMessageListener(redisMsgListener,topic);
return container;
}
public static void main(String[] args) {
SpringApplication.run(SpringbootDataMybatis01Application.class, args);
}
}
上面配置了一个 任务池,线程在里面运行,并进行阻塞,等待redis消息的传入
然后定义了一个 Redis 消息监听容器-RedisMessageListenerContainer,在容器里设置里了 redis连接工厂和指定消息的线程池
-启动SpringBoot项目后,在redis的客户端输入命令
publish topic1 msg
-在Spring中,也可以使用RedisTemplate来发送消息,例如:
// channel 代表渠道,message代表消息
redisTemplate.convertAndSend(channel,message)
3.4 使用Lua脚本
redis 2.6提供了Lua 脚本的支持,而且Lua脚本在 Redis中还具备原子性,所以在需要保证数据一致性的高并发环境中,可以使用 Lua语言来保证数据一致性,而且Lua脚本具有更强的运算功能。而且在高并发保证数据一致性,Lua脚本方案比使用 Redis自身提供的事务要更好一些。
在redis 中有2中运行Lua的方法,一种是直接发送 Lua到 Redis服务器去执行,另一种是先把Lua 发送给Redis,Redis会对 Lua脚本进行缓存,然后返回一个SHA1的32位编码回来,之后只需要发送 SHA1和相关参数给Redis便可以直接执行了。(减少网络传输带来的影响
为了支持Redis 的Lua脚本,Spring 提供了 RedisScript 接口,同时也有一个实现类 DefaultRedisScript 。查看源码
public interface RedisScript<T> {
// 获取脚本的 sha1
String getSha1();
// 获取脚本的返回值
@Nullable
Class<T> getResultType();
// 获取脚本的字符串
String getScriptAsString();
}
示例1:该脚本简单返回一个字符串
注意:需要定义返回类型,否则没有返回结果
@RequestMapping("/redis-lua")
public String testLua() {
// 创建脚本对象
DefaultRedisScript<String> rs = new DefaultRedisScript<>();
// 设置脚本
rs.setScriptText("return 'Hello Redis'");
// 定义返回类型,注意:如果没有这个定义,spring不会返回结果
rs.setResultType(String.class);
RedisSerializer<String> stringSerializer = redisTemplate.getStringSerializer();
// 执行lua脚本
String str = (String)redisTemplate.execute(rs, stringSerializer, stringSerializer, null);
return "redis-lua";
}
使用 RedisTemplate 的exexute()方法执行脚本。有2种方法执行脚本
// 脚本 keys代表redis的键
public <T> T execute(RedisScript<T> script, List<K> keys, Object... args) {
return this.scriptExecutor.execute(script, keys, args);
}
// 脚本,键的序列化器,参数序列化器,args是脚本参数
public <T> T execute(RedisScript<T> script, RedisSerializer<?> argsSerializer, RedisSerializer<T> resultSerializer, List<K> keys, Object... args) {
return this.scriptExecutor.execute(script, argsSerializer, resultSerializer, keys, args);
}
示例2:执行复杂 Lua脚本
lua脚本: 使用了两个键去保存两个参数然后对这两个参数进行比较,如果相等则返回1否则返回0。注意脚本中KEYS[1]和KEYS[2]的写法,它们代表客户端传递的第一个键和第二个键,而ARGV[1]和ARGV[2]则表示客户端传递的第一个和第二个参数。
redis.call('set',KEYS[1],ARGV[1])
redis.call('set',KEYS[2],ARGV[2])
local str1 = redis.call('get',KEYS[1])
local str2 = redis.call('get',KEYS[2])
if str1 == str2 then
return 1
end
return 0
@RequestMapping("redis-lua2")
public String testLua2(String key1, String key2, String value1, String value2) {
// 定义Lua脚本
String lua = "redis.call('set',KEYS[1],ARGV[1])\n" +
"redis.call('set',KEYS[2],ARGV[2])\n" +
"local str1 = redis.call('get',KEYS[1])\n" +
"local str2 = redis.call('get',KEYS[2])\n" +
"if str1 == str2 then\n" +
"return 1\n" +
"end\n" +
"return 0";
// 结果返回为Long
DefaultRedisScript<Long> rs = new DefaultRedisScript<>();
rs.setScriptText(lua);
// 设置结果类型
rs.setResultType(Long.class);
// 采用字符串序列化器
RedisSerializer<String> stringSerializer = redisTemplate.getStringSerializer();
// 定义key参数
List<String> keyList = new ArrayList<>();
keyList.add(key1);
keyList.add(key2);
// 传递2个参数,分别是key,val 的序列化器
Long result = (Long) redisTemplate.execute(rs, stringSerializer, stringSerializer, keyList, value1, value2);
return "redis-lua2";
}
四、Spring 缓存注解操作redis
4.1 缓存管理器和缓存的启用
Spring 在使用缓存注解前,需要配置缓存管理器,缓存管理器将提供了一些重要信息,如缓存类型、超时时间等。Spring支持多种缓存处理器,提供了CacheManager接口
可配置内容:
SPRING CACHE (Cache Properties)
spring.cache.cache-names-=#如果由底层的缓存管理器支持创建,以逗号分隔的列表来缓存名称
spring.cache.caffeine.spec# caffeine缓存配置细节
spring.cache.couchbase.CaChCOChaS€Xaon=0Couchbase=0mscouchbase缓存超时时间,默认是永不超时 spring.cache.ehcache.config=#配置 ehcache缓存初始化文件路径
spring.cache.infinispan.config=infinispan缓存配置文件
spring.cache.jcache.config=#jcache缓存配置文件
spring.cache.jcache.provider=#jcache缓存提供者配置
spring.cache.redis.cache-valu-null-=true#是否允许 Redis缓存空值
spring.cache.redis.key-prefix-=# Redis的键前缀
spring.cache.redis.time-to-1ive=0ms#缓存超时时间戳,配置为则不设置超时时间 spring.cache.edis.use-key-prefix-=true是否启用 Redis的键前缀
spring.cache.type=#缓存类型在默认的情况下, Spring会自动根据上下文探测
1.配置 Redis缓存管理器
spring.cache.type=REDIS
spring.cache.cache-names=redisCache
这样就配置完了缓存管理器
- spring.cache.type:配置的是缓存类型,为 Redis, SpringBoot 会自动生成 RedisCacheManager对象,
- spring.cache.cache-names: 则是配置缓存名称,多个名称可以使用逗号分隔,以便于缓存注解的引用。
- 为了使用缓存管理器,需要在 Spring Boot 启动类 中加入驱动缓存的注解 @EnableCaching
@SpringBootApplication
@EnableCaching // 使用注解缓存驱动机制
public class SpringbootDataMybatis01Application {
// ...
public static void main(String[] args) {
SpringApplication.run(SpringbootDataMybatis01Application.class, args);
}
}
4.2 开发缓存注解
配置
# 数据库配置
spring:
datasource:
username: root
password: admin
url: jdbc:mysql:///eesy?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
# 配置redis连接池
jedis:
pool:
min-idle: 5
max-active: 10
max-idle: 10
max-wait: 2000
# redis服务器属性
port: 6379
host: 127.0.0.1
# password: 123456
# redis 连接池超时时间,单位毫秒
timeout: 1000
# 缓存配置
cache:
type: redis
cache-names: redisCache
redis:
use-key-prefix: false # 禁用前缀
cache-null-values: true # 允许缓存空值
# key-prefix: 自定义前缀
time-to-live: 600000 # 定义超时时间,单位毫秒 10分钟=600000ms
# 整合mybatis
mybatis:
# 定义别名扫描的包
type-aliases-package: top.roise.domain
mapper-locations: classpath:mybatis/mapper/*.xml
# mybatis 外部配置文件
# config-location:
# 级联延迟加载属性配置
# configuration:
# aggressive-lazy-loading: false
properties
logging.level.root=debug
logging.level.org.springframework=debug
logging.level.org.mybatis=debug
# 配置servlet容器相关
server.port=8080
server.servlet.context-path=/boot
# 配置tomcat相关
server.tomcat.uri-encoding=utf-8
server.tomcat.connection-timeout=5000
# 解决:@DeleteMapping请求时出错,报Request method 'POST' not supported错误
spring.mvc.hiddenmethod.filter.enabled=true
mybatis用户操作接口
// 表明这是一个mybatis的mapper类
@Mapper
@Repository
public interface UserMapper {
List<User> findUsers(@Param("username") String username, @Param("address") String address);
User getUser(Long id);
int insertUser(User user);
int updateUser(User user);
int deleteUser(Long id);
}
用户服务接口
public interface UserService {
User insertUser2(User user);
User getUser(Long id);
User updateUserName(Long id, String username);
List<User> findUsers(String username,String address);
int deleteUser(Long id);
}
用户服务实现类,使用 Spring缓存注解
Service
public class UserServieImpl implements UserService , ApplicationContextAware {
@Autowired
private UserMapper userMapper;
private ApplicationContext applicationContext;
// 实现生命周期方法,设置IOC容器
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Transactional
@CachePut(value = "redisCache",key = "'redis_user_'+#result.id")
@Override
public User insertUser2(User user) {
userMapper.insertUser(user);
return user;
}
@Cacheable(value = "redisCache",key = "'redis_user_'+#id")
@Override
public User getUser(Long id) {
return userMapper.getUser(id);
}
@Transactional
@CachePut(value = "redisCache",condition = "#result != 'null'",key = "'redis_user_'+#id")
@Override
public User updateUserName(Long id, String username) {
User user = this.getUser(id);
if(Objects.isNull(user)){
return null;
}
user.setUsername(username);
userMapper.updateUser(user);
return user;
}
// 命中率低,不采用缓存机制
@Override
public List<User> findUsers(String username, String address) {
return userMapper.findUsers(username,address);
}
// 方法执行后,移除缓存
@Override
@CacheEvict(value = "redisCache",key="'redis_user_'+#id",beforeInvocation = false)
public int deleteUser(Long id) {
return userMapper.deleteUser(id);
}
}
使用的注解:
注解 | 作用 |
---|---|
@CachePut | 表示将 方法结果存回到缓存中 |
@Cacheable | 表示先从缓存中定义的键查询,查到就返回,否则查询数据库,然后把返回结果存到缓存中 |
@CacheEvict | 通过定义的键移除缓存,它有一个Boolean类型的配置项 beforeInvocation,表示在方法之前或者之后移除缓存。因为默认值为 false,表示方法之后移除缓存 |
参数解释:
- #id :代表参数,通过参数名称匹配,要求方法参数有一个名为 id的参数
- #result :代表返回的结果对象,像上面就代表 User对象,#result.id 就是取出它的属性 id
@CachePut 使用了属性 condition配置项,可使用spel表达式,返回true,表示使用缓存操作。否则不缓存
注意:Redis缓存机制,会使用 #{cacheName}:#{key} 的形式作为键保存数据,像上面的键就是:“redisCache:redis_user_1”
4.3 缓存注解自调用失效问题
updateUserName方法调用 getUser,属于类自调用,并不存在代理对象的调用,就没有使用 aop。如何解决,
- 2个Service
- xxxAware,从ioc容器获取代理对象
4.4 自定义缓存管理器
配置注解缓存 redis,消除前缀,设置超时时间
# 禁用前缀
spring.cache.redis.use-key-prefix=false
# 允许保存空值
# spring.cache.redis.cache-null-values=true
# 自定义前缀
# spring.cache.redis.key-prefix=
# 定义超时时间,单位毫秒 10分钟
spring.cache.redis.time-to-1ive=600000
然后上面的键就变成了,“redis_user_1"。
或者使用 自定义缓存管理器
@Autowired
private RedisConnectionFactory connectionFactory;
// 自定义 Redis 缓存管理器
@Bean(name = "redisCacheManager")
public RedisCacheManager initRedisCacheManager(){
// Redis加锁的写入器
RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(connectionFactory);
// 启动Redis缓存的默认设置
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
// 设置JDK序列化器
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new JdkSerializationRedisSerializer()));
// 禁用前缀
config = config.disableKeyPrefix();
// 设置 10min 超时
config = config.entryTtl(Duration.ofMinutes(10));
// 创建 redis缓存管理器
RedisCacheManager redisCacheManager = new RedisCacheManager(writer, config);
return redisCacheManager;
}