Redis7之Spring Boot集成Redis
Posted 晓风残月Lx
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis7之Spring Boot集成Redis相关的知识,希望对你有一定的参考价值。
十一 Spring Boot集成Redis
1.配置文件
redis.conf配置文件,改完后确保生效,记得重启,记得重启
-
默认daemonize no 改为 daemonize yes
-
默认protected-mode yes 改为 protected-mode no
-
默认bind 127.0.0.1 改为 直接注释掉(默认bind 127.0.0.1只能本机访问)或改成本机IP地址,否则影响远程IP连接
-
添加redis密码 改为 requirepass 你自己设置的密码
-
2.防火墙
启动: systemctl start firewalld
关闭: systemctl stop firewalld
查看状态: systemctl status firewalld
开机禁用 : systemctl disable firewalld
开机启用 : systemctl enable firewalld
添加 :firewall-cmd --zone=public --add-port=80/tcp --permanent (--permanent永久生效,没有此参数重启后失效)
重新载入: firewall-cmd --reload
查看: firewall-cmd --zone= public --query-port=80/tcp
删除: firewall-cmd --zone= public --remove-port=80/tcp --permanent
3.Jedis (一般不用了,了解即可)
1.介绍
Jedis Client 是Redis 官网推荐的一个面向 Java 客户端,库文件实现了对各类API进行封装调用
2.步骤
-
建Moudle redis_7_study
-
改POM
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.atguigu.redis7</groupId> <artifactId>redis7_study</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.10</version> <relativePath/> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <junit.version>4.12</junit.version> <log4j.version>1.2.17</log4j.version> <lombok.version>1.16.18</lombok.version> </properties> <dependencies> <!--SpringBoot通用依赖模块--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--jedis--> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>4.3.1</version> </dependency> <!--通用基础配置--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>$junit.version</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>$log4j.version</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>$lombok.version</version> <optional>true</optional> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
-
写YML
server.port=7777 spring.application.name=redis7_study
-
主启动
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Redis7Study01Application public static void main(String[] args) SpringApplication.run(Redis7Study01Application.class, args);
-
业务类
import redis.clients.jedis.Jedis; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * @Author:晓风残月Lx * @Date: 2023/3/13 21:32 */ public class JedisDemo public static void main(String[] args) // 1 connection 连接,通过指定ip和端口号 Jedis jedis = new Jedis("192.168.238.111", 6379); // 2 指定访问服务器密码 jedis.auth("123456"); // 3 获得了Jedis客户端,可以像jdbc一样访问redis System.out.println(jedis.ping()); // keys Set<String> keys = jedis.keys("*"); System.out.println(keys); // string jedis.set("k3","hello-jedis"); System.out.println(jedis.get("k3")); System.out.println(jedis.ttl("k3")); // list jedis.lpush("list","11","22","33"); List<String> list = jedis.lrange("list", 0, -1); for (String s : list) System.out.println(s); System.out.println(jedis.rpop("list")); System.out.println(jedis.lpop("list")); // hash jedis.hset("hset1","k1","v1"); Map<String,String> hash = new HashMap<>(); hash.put("k1","1"); hash.put("k2","2"); hash.put("k3","3"); jedis.hmset("hset2",hash); System.out.println(jedis.hmget("hset2","k1","k3","k2")); System.out.println(jedis.hget("hset1", "k1")); System.out.println(jedis.hexists("hset2","k2")); System.out.println(jedis.hkeys("hset2")); // set jedis.sadd("set1","1","2","3"); jedis.sadd("set2","4"); System.out.println(jedis.smembers("set1")); System.out.println(jedis.scard("set1")); System.out.println(jedis.spop("set1")); jedis.smove("set1","set2","1"); System.out.println(jedis.smembers("set1")); System.out.println(jedis.smembers("set2")); System.out.println(jedis.sinter("set1", "set2")); // 交集 System.out.println(jedis.sunion("set1","set2")); // 并集 // zset jedis.zadd("zset1",100,"v1"); jedis.zadd("zset1",80,"v2"); jedis.zadd("zset1",60,"v3"); List<String> zset1 = jedis.zrange("zset1", 0, -1); for (String s : zset1) System.out.println(s); List<String> zset11 = jedis.zrevrange("zset1", 0, -1); for (String s : zset11) System.out.println(s);
4.Lettuce
1.介绍以及和Jedis的区别
Lettuce是一个Redis的Java驱动包,Lettuce翻译为生菜,没错,就是吃的那种生菜,所以它的Logo就是生菜
2.步骤
-
改POM(导包)
<!--lettuce--> <dependency> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> <version>6.2.1.RELEASE</version> </dependency>
-
业务类
import io.lettuce.core.RedisClient; import io.lettuce.core.RedisURI; import io.lettuce.core.api.StatefulRedisConnection; import io.lettuce.core.api.sync.RedisCommands; import java.util.List; /** * @Author:晓风残月Lx * @Date: 2023/3/13 22:13 */ public class LettuceDemo public static void main(String[] args) // 1 使用构建器链式编程来builder我们的RedisURI RedisURI uri = RedisURI.builder() .withHost("192.168.238.111") .withPort(6379) .withAuthentication("default", "123456") .build(); // 2 连接客户端 RedisClient redisClient = RedisClient.create(uri); StatefulRedisConnection<String, String> conn = redisClient.connect(); // 3 创建操作的command, 通过conn 创建 RedisCommands<String, String> commands = conn.sync(); // string commands.set("k1","v1"); System.out.println("==========================="+commands.get("k1")); System.out.println("==========================="+commands.mget("k1","k2")); List<String> keys = commands.keys("*"); for (String key : keys) System.out.println("========================="+key); // list commands.lpush("list01","1","2","3"); List<String> list01 = commands.lrange("list01", 0, -1); for (String s : list01) System.out.println("================"+s); System.out.println("===================="+ commands.rpop("list01", 2)); // hash commands.hset("hash","k1","v1"); commands.hset("hash","k2","v2"); commands.hset("hash","k3","v3"); System.out.println("======================="+commands.hgetall("hash")); Boolean hexists = commands.hexists("hash", "v2"); System.out.println("------"+hexists); // set commands.sadd("s1","1","2"); System.out.println("=================================" + commands.smembers("s1")); System.out.println(commands.sismember("s1", "1")); System.out.println(commands.scard("s1")); // zset commands.zadd("a1",100,"v1"); commands.zadd("a1",80,"v2"); System.out.println(commands.zrange("a1", 0, -1))5分钟入手Spring Boot;
- Spring Boot数据库交互之Spring Data JPA;
- Spring Boot数据库交互之Mybatis;
- Spring Boot视图技术;
- Spring Boot之整合Swagger;
- Spring Boot之junit单元测试踩坑;
- 如何在Spring Boot中使用TestNG;
- Spring Boot之整合logback日志;
- Spring Boot之整合Spring Batch:批处理与任务调度;
- Spring Boot之整合Spring Security: 访问认证;
- Spring Boot之整合Spring Security: 授权管理;
- Spring Boot之多数据库源:极简方案;
- Spring Boot之使用MongoDB数据库源;
- Spring Boot之多线程、异步:@Async;
- Spring Boot之前后端分离(一):Vue前端;
- Spring Boot之前后端分离(二):后端、前后端集成;
- Spring Boot之前后端分离(三):登录、登出、页面认证;
- Spring Boot之面向切面编程:Spring AOP;
- Spring Boot之集成Redis(一):Redis初入门;
- Spring Boot之集成Redis(二):集成Redis;
在上一篇文章Spring Boot之集成Redis(二):集成Redis中,我们一起学习了如何使用StringRedisTemplate来与Redis进行交互,但也在文末提到这种方式整体代码还是比较多的,略显臃肿,并剧透了另外一种操作Redis的方式:
基于Spring Cache方式操作Redis!
今天的内容厉害啦,不仅能学会在Spring Boot中更好的使用Redis,还能学习Spring Cache!
我们一起拨开云雾睹青天吧!
代码基于上期使用的Git Hub仓库演进,欢迎取阅:
整体步骤
- 安装commons-pool2依赖;
- 修改application.properties配置;
- 学习Spring Cache的缓存注解;
- 使用Spring Cache的缓存注解操作Redis;
- Spring Cache + Redis 缓存演示;
- 配置类方式配置和管理Redis缓存;
- 动态缓存有效期的实现;
1. 安装commons-pool2依赖;
由于我们会在application.properties配置文件中配置lettuce类型的redis连接池,因此需要引入新的依赖:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
记得安装一下依赖:
mvn install -Dmaven.test.skip=true -Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true
2. 修改application.properties配置;
application.properties文件整体样子:
server.port=8080
# 设置Spring Cache的缓存类型为redis
spring.cache.type=redis
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务端的host和port
spring.redis.host=127.0.0.1
spring.redis.port=6379
# Redis服务端的密码
spring.redis.password=Redis!123
# Redis最大连接数
spring.redis.pool.max-active=8
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.lettuce.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=0
# Redis连接超时时间,单位 ms(毫秒)
spring.redis.timeout=5000
特别是spring.cache.type=redis这个配置,指定了redis作为Spring Cache的缓存(还有多种可选的缓存方式,如Simple、none、Generic、JCache、EhCache、Hazelcast等,请读者有需要自行脑补哈)。
3. 学习Spring Cache的缓存注解;
Spring Cache提供了5个缓存注解,通常直接使用在Service类上,各有其作用:
1. @Cacheable;
@Cacheable 作用在方法上,触发缓存读取操作。
被作用的方法如果缓存中没有,比如第一次调用,则会执行方法体,执行后就会进行缓存,而如果已有缓存,那么则不再执行方法体;
2. @CachePut;
@CachePut 作用在方法上,触发缓存更新操作;
被作用的方法每次调用都会执行方法体;
3. @CacheEvict;
@CachePut 作用在方法上,触发缓存失效操作;
也即清除缓存,被作用的方法每次调用都会执行方法体,并且使用方法体返回更新缓存;
4. @Caching;
@Caching作用在方法上,注解中混合使用@Cacheable、@CachePut、@CacheEvict操作,完成复杂、多种缓存操作;
比如同一个方法,关联多个缓存,并且其缓存名字、缓存key都不同时,或同一个方法有增删改查的缓存操作等,被作用的方法每次调用都会执行方法体。
5. @CacheConfig;
在类上设置当前缓存的一些公共设置,也即类级别的全局缓存配置。
4. 使用Spring Cache的缓存注解操作Redis;
整体步骤:
1). 修改项目入口类;
2). 编写演示用实体类;
3). 编写演示用Service类;
4). 编写演示用Controller类;
1). 修改项目入口类;
在Spring Boot项目入口类App.java上添加注解:@EnableCaching,App.java类整体如下:
package com.github.dylanz666;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
/**
* @author : dylanz
* @since : 10/28/2020
*/
@SpringBootApplication
@EnableCaching
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
2). 编写演示用实体类;
在domain包内建立演示用实体类User.java,简单撸点代码演示一下:
package com.github.dylanz666.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;
import java.io.Serializable;
/**
* @author : dylanz
* @since : 10/31/2020
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
@Component
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String userName;
private String roleName;
@Override
public String toString() {
return "{" +
""userName":"" + userName + ""," +
""roleName":" + roleName + "" +
"}";
}
}
3). 编写演示用Service类;
重点来了,在service包内新建UserService类,编写带有Spring Cache注解的方法们,代码如下:
package com.github.dylanz666.service;
import com.github.dylanz666.domain.User;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
/**
* @author : dylanz
* @since : 10/31/2020
*/
@Service
public class UserService {
@Cacheable(value = "content", key = "‘id_‘+#userName")
public User getUserByName(String userName) {
System.out.println("io operation : getUserByName()");
//模拟从数据库中获取数据
User userInDb = new User();
userInDb.setUserName("dylanz");
userInDb.setRoleName("adminUser");
return userInDb;
}
@CachePut(value = "content", key = "‘id_‘+#user.userName")
public User updateUser(User user) {
System.out.println("io operation : updateUser()");
//模拟从数据库中获取数据
User userInDb = new User();
userInDb.setUserName(user.getUserName());
userInDb.setRoleName(user.getRoleName());
return userInDb;
}
@CacheEvict(value = "content", key = "‘id_‘+#user.userName")
public void deleteUser(User user) {
System.out.println("io operation : deleteUser()");
}
@Caching(cacheable = {
@Cacheable(cacheNames = "test", key = "‘id_‘+#userName")
}, put = {
@CachePut(value = "testGroup", key = "‘id_‘+#userName"),
@CachePut(value = "user", key = "#userName")
})
public User getUserByNameAndDoMultiCaching(String userName) {
System.out.println("io operation : getUserByNameAndDoMultiCaching()");
//模拟从数据库中获取数据
User userInDb = new User();
userInDb.setUserName("dylanz");
userInDb.setRoleName("adminUser");
return userInDb;
}
}
简单介绍一下:
- 在UserService的方法内模拟了一下数据库中的数据,用于模拟从数据库中获取数据,代码为:
//模拟从数据库中获取数据
User userInDb = new User();
userInDb.setUserName("dylanz");
userInDb.setRoleName("adminUser");
-
我写了4个方法getUserByName(String userName)、
updateUser(User user)、deleteUser(User user)、getUserByNameAndDoMultiCaching(String userName)分别用于演示查询缓存、更新缓存、删除缓存,以及同一个方法使用混合缓存注解; -
缓存注解中使用了2个基本属性(还有其他属性),value(也可以用cacheNames)和key,key如果是从方法体获取的,则需要在key前加#号,比如key="#userName"或者key = "‘id_‘+#userName",当然也可以写死如key="test123";
-
缓存注解中的key属性值可以从对象中获取,如:key = "‘id_‘+#user.userName";
-
缓存注解的value(或cacheNames)属性、key属性共同构成了Redis服务端中完整的key,如value = "content", key = "test123",则在Redis服务端,其完整key为:content::test123;
-
@Caching中可混合使用缓存注解@Cacheable、@CachePut、@CacheEvict,对应的属性是:cacheable、put、evict,复杂场景有帮助!
3). 编写演示用Controller类;
在controller包内新建UserController.java类,编写4个API,分别使用Service中的4个带有Spring Cache注解的方法们,代码如下:
package com.github.dylanz666.controller;
import com.github.dylanz666.domain.User;
import com.github.dylanz666.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* @author : dylanz
* @since : 10/31/2020
*/
@RestController
@RequestMapping("/api/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("")
public @ResponseBody User getUserByName(@RequestParam String userName) {
return userService.getUserByName(userName);
}
@PutMapping("")
public @ResponseBody
User updateUser(@RequestBody User user) {
return userService.updateUser(user);
}
@DeleteMapping("")
public @ResponseBody String deleteUser(@RequestBody User user) {
userService.deleteUser(user);
return "success";
}
@GetMapping("/multiCaching")
public @ResponseBody User getUserByNameAndDoMultiCaching(@RequestParam String userName) {
return userService.getUserByNameAndDoMultiCaching(userName);
}
}
至此,项目整体结构:
5. Spring Cache + Redis 缓存演示;
1). 启动Redis服务端;
redis-server.exe redis.windows.conf
2). 启动Redis客户端,进入monitor模式;
启动:
redis-cli.exe -h 127.0.0.1 - p 6379
auth Redis!123
在Redis客户端,进入monitor模式,命令:
monitor
3). 启动项目;
4). postman访问Controller中的API;
- 首先在设置缓存前,直接调用获取user的API(GET方法):http://127.0.0.1:8080/api/user?userName=dylanz
我们会看到,第一次调用API时,执行了方法体,并完成了第一次Redis缓存!
- 调用更新user的API(PUT方法):http://127.0.0.1:8080/api/user
- 更新后,再次调用获取user的API:
我们会看到,缓存确实被更新了!
- 调用删除user的API(DELETE方法):http://127.0.0.1:8080/api/user
- 缓存删除后,再次调用获取user的API:
我们会看到,缓存被删除后,再次获取缓存则会自动再次执行方法体,并完成新一轮的缓存!
- 调用拥有混合缓存注解的API(GET方法):http://127.0.0.1:8080/api/user/multiCaching?userName=dylanz
同时为了更直观的让大家看到缓存有起到效果,我在Service层的方法内,都往控制台打印了一些文字,如:io operation : getUserByName()...
我分别各调用了3次具有更新缓存的API、删除缓存的API、获取缓存的API、混合使用缓存注解的API,控制台打印:
我们会发现:更新缓存、删除缓存、混合使用缓存注解的缓存,每次都有执行方法体,而获取缓存只执行了一次方法体,这跟我们之前介绍缓存注解的时候是一致的,再次验证了之前对缓存注解的认识!
6. 配置类方式配置和管理Redis缓存;
在config包内创建Redis配置类RedisConfig.java,并编写如下代码:
package com.github.dylanz666.config;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
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.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;
import java.time.Duration;
/**
* @author : dylanz
* @since : 10/31/2020
*/
稍微解读一下:
1). 默认情况下Redis的序列化会使用jdk序列化器JdkSerializationRedisSerializer,而该序列化方式,在存储内容时除了属性的内容外还存了其它内容在里面,总长度长,且不容易阅读,因此我们自定义了序列化策略:redisTemplate(),使用该自定义序列化方式后,存储在Redis的内容就比较容易阅读了!
2). Spring Cache注解方式,默认是没有过期概念的,即设置了缓存,则一直有效,这显然不能完全满足使用要求,而RedisConfig中的CacheManager给我们提供了一种方式来设置缓存过期策略!
3). cacheManager()用于配置缓存管理器,我们在方法内配置了一个entryTtl为1分钟,TTL即Time To Live,代表缓存可以生存多久,我没有在cacheManager()方法内指明哪个缓存,因此该TTL是针对所有缓存的,是全局性的,也是我们设置的默认TTL;
4). 我们可以给不同的key设置不同的TTL,但hard code的缓存key就有点多啦,此处不介绍啦!
5). 该配置类使用了注解@EnableCaching,则Spring Boot入口类App.java中的@EnableCaching注解就可以删除啦!
7. 动态缓存有效期的实现;
动态缓存有效期的实现有多种方式,如:
1). 在缓存注解上使用不同的自定义的CacheManager;
2). 自定义Redis缓存有效期注解;
3). 利用在Spring Cache缓存注解中value属性中设置的值,设置缓存有效期;
1). 在缓存注解上使用不同的自定义的CacheManager;
在第6步中,我们使用了一个全局的CacheManager,其实这个可以更灵活,可以用于给不同的缓存设置不同的TTL,也即缓存过期。
我们可以在RedisConfig.java中编写多个不同名字的CacheManager,每个CacheManager使用不同的TTL。
然后缓存注解中使用不同的CacheManager,就能达到不同缓存有不同TTL的目的啦,并且没有在CacheManager中hard code缓存key,演示代码:
-
RedisConfig.java演示代码:
package com.github.dylanz666.config;
import org.springframework.cache.CacheManager;
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.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;
import java.time.Duration;
/**
* @author : dylanz
* @since : 10/31/2020
*/