本帖最后由 vsked 于 2026-2-12 14:26 编辑
缓存这东西大家都知道吧。打个比方,上游一直要传一些数据,如果这些数据一直传重复的就没有什么意义。我们只保存数据变化 的时候。
这时候我们会考虑用缓存来解决,缓存有本地缓存,也有网络缓存。如果本地缓存能解决问题,那我们一般优先上本地缓存。像caffeine就是一种本地缓存。
那当数据量很大,达到百万级,或是多个应用都需要访问共同的缓存数据怎么办呢?
这时候就会用到网络缓存,比如redis就是其中的一种。
spring boot版本升级很快。我们先引入spring boot 与redis相关的jar包,还有一些单元测试的jar包
这里使用了testng作为单元测试框架,使 用了log4j2作为日志记录框架。
使用了springdatajpa来管理redis连接的相关信息
[XML] 纯文本查看 复制代码 <?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.vsked</groupId>
<artifactId>springboot4redis</artifactId>
<version>1.0</version>
<name>${project.artifactId}</name>
<properties>
<!-- 声明项目配置依赖编码格式为 utf-8 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- 编译UTF-8 -->
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<!-- java jdk 版本声明 可变更 根据自己配置去匹配 -->
<java.version>21</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<maven.deploy.skip>true</maven.deploy.skip>
<maven.install.skip>true</maven.install.skip>
<mavenCompilerPlugin.version>3.14.0</mavenCompilerPlugin.version>
<mavenSurefirePlugin.version>3.5.3</mavenSurefirePlugin.version>
<testng.version>7.11.0</testng.version>
<springboot.version>4.0.2</springboot.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${springboot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- spring boot devtools 第一步添加依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
<scope>runtime</scope>
</dependency>
<!-- spring boot核心,包括自动配置支持日志和YAML -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 对web开发的支持包含了spring webmvc和tomcat等web开发的特性 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- TestNG start -->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>${testng.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- TestNG end -->
<!-- log4j start -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!-- log4j end -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${mavenCompilerPlugin.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<!-- true:跳过测试 -->
<skip>true</skip>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${mavenSurefirePlugin.version}</version>
<configuration>
<skip>true</skip>
<argLine>
-javaagent:"${settings.localRepository}/org/mockito/mockito-core/5.14.2/mockito-core-5.14.2.jar"
</argLine>
</configuration>
</plugin>
<!-- spring boot devtools 第二步添加maven插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${springboot.version}</version>
<configuration>
<mainClass>com.vsked.Application</mainClass>
<!--addResources如果没有该项配置,这个devtools不会起作用即应用不会restart这个要手动加进去 3.0版本用这个配置 -->
<addResources>true</addResources>
</configuration>
</plugin>
</plugins>
</build>
</project>
然后我们编写主配置文件
主配置文件作用是环境的切分这里我们激活开发环境,当然这里面也记录了redis的连接信息。比如ip,端口,密码
[Plain Text] 纯文本查看 复制代码 spring:
profiles:
active: dev
lifecycle:
timeout-per-shutdown-phase: 60s #设置优雅停机缓冲时间默认30s
devtools:
restart:
enabled: true #设置开启热部署
开发环境的实际代码如下application.yml
[Plain Text] 纯文本查看 复制代码 server:
port: 8181 #端口号
shutdown: graceful #开启优雅停机 默认为immediate立即停机
spring:
devtools:
restart:
enabled: true #设置开启热部署
data:
redis:
database: 0
host: 127.0.0.1
port: 6379
password: 123456789
timeout: 10000
logging:
config: classpath:log4j2.xml
开发环境引入了log4j2的配置文件,负责配置日志文件存储相关策略与打印策略log4j2.xml
[XML] 纯文本查看 复制代码 <?xml version="1.0" encoding="UTF-8"?>
<!--设置log4j2的自身log级别为warn-->
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置, 当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration status="warn" monitorInterval="60">
<Properties>
<!--<Property name="logpath">${sys:catalina.home}/logs/mybatismutids/</Property>-->
<Property name="logpath">/logs/springboot4redis/</Property>
<Property name="logname">springboot4redis</Property>
</Properties>
<!--先定义所有的appender-->
<appenders>
<!--这个输出控制台的配置-->
<console name="Console" target="SYSTEM_OUT">
<!--输出日志的格式-->
<PatternLayout charset="utf-8" pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} traceId:[%X{mdc_trace_id}] %-5level %class{36} %L %M - %msg%xEx%n" />
</console>
<!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用-->
<File name="log" fileName="${logpath}/${logname}_test" append="false">
<PatternLayout charset="utf-8" pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} traceId:[%X{mdc_trace_id}] %-5level %class{36} %L %M - %msg%xEx%n" />
</File>
<!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,
则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileInfo" fileName="${logpath}/info_${logname}" filePattern="${logpath}/${logname}error_%d{yyyy-MM-dd}_%i.log">
<Filters>
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="INFO"/>
<ThresholdFilter level="WARN"/>
</Filters>
<PatternLayout charset="utf-8" pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} traceId:[%X{mdc_trace_id}] %-5level %class{36} %L %M - %msg%xEx%n" />
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>
<RollingFile name="RollingFileWarn" fileName="${logpath}/warn_${logname}" filePattern="${logpath}/${logname}error_%d{yyyy-MM-dd}_%i.log">
<Filters>
<ThresholdFilter level="WARN"/>
<ThresholdFilter level="ERROR"/>
</Filters>
<PatternLayout charset="utf-8" pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} traceId:[%X{mdc_trace_id}] %-5level %class{36} %L %M - %msg%xEx%n" />
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了20 -->
<DefaultRolloverStrategy max="20"/>
</RollingFile>
<RollingFile name="RollingFileError" fileName="${logpath}/error_${logname}" filePattern="${logpath}/${logname}error_%d{yyyy-MM-dd}_%i.log">
<ThresholdFilter level="ERROR"/>
<PatternLayout charset="utf-8" pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} traceId:[%X{mdc_trace_id}] %-5level %class{36} %L %M - %msg%xEx%n" />
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>
<RollingFile name="RollingFileAll" fileName="${logpath}/All_${logname}" filePattern="${logpath}/${logname}all_%d{yyyy-MM-dd}_%i.log">
<ThresholdFilter level="ALL"/>
<PatternLayout charset="utf-8" pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} traceId:[%X{mdc_trace_id}] %-5level %class{36} %L %M - %msg%xEx%n" />
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>
</appenders>
<!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
<loggers>
<!--过滤掉spring和hibernate的一些无用的debug信息-->
<logger name="org.springframework" level="ERROR"></logger>
<logger name="org.hibernate" level="ERROR"></logger>
<logger name="com.zaxxer" level="ERROR"></logger>
<logger name="org.mybatis" level="ERROR"></logger>
<logger name="io.netty" level="ERROR"></logger>
<logger name="io.lettuce" level="ERROR"></logger>
<logger name="org.xnio" level="ERROR"></logger>
<logger name="org.apache" level="ERROR"></logger>
<logger name="org.jboss" level="ERROR"></logger>
<!-- 不打印本项目sql语句,需要时请调整为debug -->
<logger name="com.vsked.dao" level="ALL" additivity="true"></logger>
<root level="all">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
<appender-ref ref="RollingFileAll"/>
</root>
</loggers>
</configuration>
然后编写一个redis配置的文件负责配置redisTemplate也就是初始化参数,定义序列化与反序列化的类
[Java] 纯文本查看 复制代码 package com.vsked.config;
import tools.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJacksonJsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
class RedisConfiguration {
@Bean
RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
ObjectMapper jackson = new ObjectMapper();
//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
redisTemplate.setValueSerializer(new GenericJacksonJsonRedisSerializer(jackson));
redisTemplate.setHashValueSerializer(new GenericJacksonJsonRedisSerializer(jackson));
//使用StringRedisSerializer来序列化和反序列化redis的ke
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(connectionFactory);
return redisTemplate;
}
}
然后我们写一个启动应用的程序。这个主要为单元测试作准备的.springboot 应用都会有这么一个启动应用的。
[Java] 纯文本查看 复制代码 package com.vsked;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
然后我们就可以写一个单元测试试一下啦。
先写一个单元测试基础类。
这个类负责引用刚才写的springboot启动应用,来准备单元测试
[Java] 纯文本查看 复制代码 package com.vsked.test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.springframework.test.context.web.WebAppConfiguration;
import com.vsked.Application;
@SpringBootTest(classes = Application.class)
@WebAppConfiguration
public class BaseTestWithoutTransactional extends AbstractTestNGSpringContextTests{
}
然后就可以使用testng配合springboot4对redis进行单元测试了
为了方便,这里还打印了redis连接的相关信息。[Java] 纯文本查看 复制代码 package com.vsked.redis;
import com.vsked.test.BaseTestWithoutTransactional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.testng.annotations.Test;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class RedisTest extends BaseTestWithoutTransactional {
private static final Logger log = LoggerFactory.getLogger(RedisTest.class);
@Autowired
RedisTemplate<String,Object> redisTemplate;
@Test
public void printConnectionInfo() {
var factory = (LettuceConnectionFactory) redisTemplate.getConnectionFactory();
log.info("=================== Redis Connection Info ===================");
log.info("Host: {}:{}", factory.getHostName(), factory.getPort());
log.info("Database: {}", factory.getDatabase());
log.info("Password: {}", factory.getPassword());
log.info("Timeout: {}", factory.getTimeout());
log.info("SSL: {}", factory.isUseSsl());
log.info("Cluster: {}", factory.getClusterConfiguration());
// 打印连接池信息
// 获取 Lettuce 连接池配置
try {
var field = factory.getClass().getDeclaredField("clientConfiguration");
field.setAccessible(true);
var clientConfig = field.get(factory);
log.info("=================== Lettuce Pool Info ===================");
log.info("Client Configuration: {}", clientConfig);
} catch (Exception e) {
log.error("无法获取连接池配置信息: {}", e.getMessage());
}
log.info("===========================================================");
}
@Test
public void saveString(){
String redisKey="springBoot4Redis:module1:userNameF1";
String redisValue="myNameIsBruce";
redisTemplate.opsForValue().set(redisKey,redisValue);
log.info("{}",redisTemplate.opsForValue().get(redisKey));
}
@Test
public void saveStringWithTime(){
String redisKey="springBoot4Redis:module1:userNameF1";
String redisValue="myNameIsBruce";
redisTemplate.opsForValue().set(redisKey,redisValue,10,TimeUnit.MINUTES);
log.info("{}",redisTemplate.opsForValue().get(redisKey));
}
@Test
public void delString(){
String redisKey="springBoot4Redis:module1:userNameF1";
redisTemplate.delete(redisKey);
log.info("{}",redisTemplate.opsForValue().get(redisKey));
}
@Test
public void save2String(){
String redisKey="springBoot4Redis:module1:userName15";
String redisValue="myNameIsBruce5s";
redisTemplate.opsForValue().set(redisKey,redisValue,15, TimeUnit.SECONDS);
}
@Test
public void saveList(){
String redisKey="springBoot4Redis:module1:userList1";
List<Map<String,Object>> testDataList=new LinkedList<>();
for(int i=0;i<10;i++){
Map<String,Object> dataMap=new HashMap<>();
dataMap.put("user"+i,"value"+i);
testDataList.add(dataMap);
}
redisTemplate.opsForValue().set(redisKey,testDataList);
}
@Test
public void getList(){
String redisKey="springBoot4Redis:module1:userList1";
List<Map<String,Object>> testDataList= (List<Map<String, Object>>) redisTemplate.opsForValue().get(redisKey);
log.info("{}",testDataList);
}
}
如果你感觉我搭建的这个springboot4整合redis的框架还不错,可以分享给你的朋友,大家一起进步吧。
所有代码都在下面附件中:
springboot4redis.zip
(18.15 KB, 下载次数: 9)
|