目录
[1.Java 对象序列化与反序列化](#1.Java 对象序列化与反序列化)
[1.1 核心概念](#1.1 核心概念)
[1.2 具体实现](#1.2 具体实现)
[1.3 运行与测试](#1.3 运行与测试)
[2.1 前提准备](#2.1 前提准备)
[2.2 具体实现](#2.2 具体实现)
[2.3 运行与测试](#2.3 运行与测试)
[2.4 独立补充:JSON序列化(实际开发首选方式)](#2.4 独立补充:JSON序列化(实际开发首选方式))
[2.4.1 JSON序列化与JDK自带序列化方式比较](#2.4.1 JSON序列化与JDK自带序列化方式比较)
[2.4.2 添加Maven依赖](#2.4.2 添加Maven依赖)
[2.4.3 具体实现](#2.4.3 具体实现)
[2.4.4 运行与测试](#2.4.4 运行与测试)
[3.基于 Redis 的数据库查询缓存实现](#3.基于 Redis 的数据库查询缓存实现)
[3.1 核心目标](#3.1 核心目标)
[3.2 数据库准备](#3.2 数据库准备)
[3.3 添加Maven依赖](#3.3 添加Maven依赖)
[3.4 具体实现](#3.4 具体实现)
[3.5 运行与测试](#3.5 运行与测试)
[4.基于 Redis 发布订阅(Pub/Sub)的多消费者消息队列](#4.基于 Redis 发布订阅(Pub/Sub)的多消费者消息队列)
[4.1 实现目标](#4.1 实现目标)
[4.2 具体实现](#4.2 具体实现)
[4.3 运行与测试](#4.3 运行与测试)
1.Java 对象序列化与反序列化
1.1 核心概念
在Java开发中,序列化与反序列化是实现对象持久化、跨场景传输的核心技术,也是Redis存储Java对象的关键前提。
-
序列化(Serialization) :将内存中的Java对象,转换为字节流(或其他可存储/传输的格式),以便写入文件、数据库,或通过网络传输到其他服务。
-
反序列化(Deserialization) :将存储的字节流(或其他格式),恢复为内存中的Java对象,以便程序后续调用、操作。
简单来说,序列化与反序列化是Java对象格式转换的两个互逆过程,核心目的是解决"对象如何存储、如何跨媒介传输"的问题。
1.2 具体实现
① 实体类:User(实现Serializable接口,支持序列化)
java
package com.hy.chapter6;
import java.io.Serializable;
/**
* 被序列化的实体类:User
* 必须实现Serializable接口,否则无法序列化
*/
public class User implements Serializable {
// 序列化的对象属性(无transient修饰,全部参与序列化)
private int id;
private String name;
private String address;
// 标准getter/setter方法,用于给对象赋值、取值
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
② 测试类:Test(执行序列化与反序列化操作)
java
package com.hy.chapter6;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* 序列化与反序列化测试类
* 核心功能:将List<User>对象序列化到本地文件,再从文件反序列化恢复对象
*/
public class Test {
public static void main(String[] args) {
// try-catch捕获序列化/反序列化可能出现的异常(IO异常、类找不到异常)
try {
// 一、序列化操作:将List<User>对象 → 字节流,写入本地文件
// 1. 创建ObjectOutputStream对象(用于将对象写入输出流)
ObjectOutputStream obj = new ObjectOutputStream(
new FileOutputStream(new File("e://user.datas")));
// 2. 创建List<User>集合,添加User对象(模拟需要存储/传输的数据)
List<User> lists = new ArrayList<User>();
// 创建第一个User对象并赋值
User u1 = new User();
u1.setId(100);
u1.setName("张1");
u1.setAddress("南京");
// 创建第二个User对象并赋值
User u2 = new User();
u2.setId(101);
u2.setName("张2");
u2.setAddress("上海");
// 将两个User对象添加到集合中
lists.add(u1);
lists.add(u2);
// 3. 执行序列化:将List<User>对象写入文件(底层转换为字节流)
obj.writeObject(lists);
// 刷新输出流,确保数据完全写入文件
obj.flush();
System.out.println("序列化成功!List<User>对象已写入e://user.datas文件");
// 二、反序列化操作:从本地文件读取字节流 → 恢复为List<User>对象
// 1. 创建ObjectInputStream对象(用于从输入流读取对象)
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("e://user.datas"));
// 2. 执行反序列化:读取字节流,转换为List<User>对象(需强制转换类型)
List<User> lists1 = (List<User>) in.readObject();
System.out.println("反序列化成功!已从文件恢复List<User>对象");
// 3. 遍历恢复后的集合,验证反序列化结果(输出每个User的name)
System.out.println("反序列化得到的User对象信息:");
for (User u : lists1) {
System.out.println("用户名:" + u.getName() + ",地址:" + u.getAddress());
}
// 关闭流资源(避免资源泄露)
obj.close();
in.close();
} catch (IOException e) {
// 捕获IO异常(如文件路径不存在、流关闭异常等)
e.printStackTrace();
} catch (ClassNotFoundException e) {
// 捕获类找不到异常(反序列化时,JVM找不到User类会抛出此异常)
e.printStackTrace();
}
}
}
1.3 运行与测试


关键代码示例:
java// 序列化:写入对象到文件 ObjectOutputStream obj = new ObjectOutputStream(new FileOutputStream("e://user.datas")); obj.writeObject(lists); // lists为List<User>对象 // 反序列化:从文件读取对象 ObjectInputStream in = new ObjectInputStream(new FileInputStream("e://user.datas")); List<User> lists1 = (List<User>) in.readObject();应用场景:
对象持久化存储(如本地缓存、文件备份);
网络传输(如分布式系统中对象的跨服务传递)。
2.Redis存储Java对象(序列化/反序列化实操)
核心功能:① 序列化List<User>对象→字节流,存入Redis;② 从Redis读取字节流→反序列化,恢复为List<User>对象;③ 验证结果,对比存入与读取的对象属性一致性。
2.1 前提准备
-
包路径:新增
com.hy.chapter6包下的RedisSerializeTest类(与User、Test类同包); -
依赖:确保已引入Jedis依赖(参考前文Redis相关内容,pom.xml配置不变);
-
Redis环境:本地Redis服务器正常启动(默认地址127.0.0.1:6379,无密码);
-
核心类:复用前文
User类(已实现Serializable接口,支持序列化)。
2.2 具体实现
java
package com.hy.chapter6;
import redis.clients.jedis.Jedis;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
/**
* Redis存储Java对象实操类
* 核心:手动实现序列化(对象→字节流)和反序列化(字节流→对象),完成Redis存储与读取
*/
public class RedisSerializeTest {
// 静态Jedis实例,复用Redis连接(参考Chapter1逻辑)
static Jedis jedis;
// 静态初始化块:启动时创建Redis连接
static {
try {
// 连接本地Redis服务器(默认地址127.0.0.1,端口6379)
jedis = new Jedis("127.0.0.1", 6379);
System.out.println("Redis连接成功,客户端信息:" + jedis);
} catch (Exception e) {
System.err.println("Redis连接失败:" + e.getMessage());
}
}
public static void main(String[] args) {
try {
// 1. 准备需要存入Redis的Java对象(List<User>集合,复用前文数据)
List<User> userList = new ArrayList<User>();
User u1 = new User();
u1.setId(100);
u1.setName("张1");
u1.setAddress("南京");
User u2 = new User();
u2.setId(101);
u2.setName("张2");
u2.setAddress("上海");
userList.add(u1);
userList.add(u2);
System.out.println("准备存入Redis的Java对象:" + userList);
// 2. 序列化:将List<User>对象 → 字节流(核心步骤)
// ByteArrayOutputStream:用于在内存中存储字节流(替代前文的本地文件)
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// ObjectOutputStream:将Java对象写入字节流
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(userList); // 执行序列化
byte[] userBytes = bos.toByteArray(); // 得到序列化后的字节流
System.out.println("Java对象序列化完成,字节流长度:" + userBytes.length);
// 3. 将序列化后的字节流,存入Redis(key为user:list,value为字节流)
jedis.set("user:list".getBytes(), userBytes);
System.out.println("序列化后的字节流,已成功存入Redis(key:user:list)");
// 4. 从Redis读取字节流
byte[] redisBytes = jedis.get("user:list".getBytes());
System.out.println("从Redis读取到的字节流长度:" + redisBytes.length);
// 5. 反序列化:将字节流 → 恢复为List<User>对象(核心步骤)
// ByteArrayInputStream:读取内存中的字节流
ByteArrayInputStream bis = new ByteArrayInputStream(redisBytes);
// ObjectInputStream:将字节流转换为Java对象
ObjectInputStream ois = new ObjectInputStream(bis);
List<User> redisUserList = (List<User>) ois.readObject(); // 执行反序列化,强制转换类型
System.out.println("字节流反序列化完成,恢复为Java对象:" + redisUserList);
// 6. 验证结果:遍历恢复后的对象,确认属性一致
System.out.println("\\n验证反序列化结果(对象属性):");
for (User u : redisUserList) {
System.out.println("用户ID:" + u.getId() + ",用户名:" + u.getName() + ",地址:" + u.getAddress());
}
// 关闭流资源,避免泄露
oos.close();
bos.close();
ois.close();
bis.close();
// 关闭Redis连接
jedis.close();
} catch (Exception e) {
// 捕获所有异常(IO异常、反序列化异常、Redis连接异常等)
e.printStackTrace();
}
}
}
2.3 运行与测试


2.4 独立补充:JSON序列化(实际开发首选方式)
2.4.1 JSON序列化与JDK自带序列化方式比较
前文演示的是JDK自带的序列化方式,这种方式虽然简单、无需额外引入依赖,但存在明显局限性:依赖Serializable接口、序列化后的字节流可读性差、不支持跨语言(如Java序列化的对象,无法被Python、Go等其他语言读取)。
因此,实际开发中,Redis存储Java对象更常用JSON序列化方式 ,主流工具包括FastJSON、Jackson、Gson等。JSON序列化的核心优势的是:序列化后的格式为JSON字符串(可读性极强)、无需实现Serializable接口、支持跨语言兼容、操作灵活,完全适配Redis存储字符串/字节流的特性,是企业开发中的首选方案。
2.4.2 添加Maven依赖

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.hy</groupId>
<artifactId>redisdemo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.2.3</version> <!-- 请检查最新版本 -->
</dependency>
<!-- 添加Logback作为SLF4J的日志实现 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
</dependency>
<!-- 添加FastJSON作为JSON处理库 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.32</version>
</dependency>
</dependencies>
</project>
2.4.3 具体实现
java
package com.hy.chapter6;
import com.alibaba.fastjson.JSON;
import redis.clients.jedis.Jedis;
import java.util.ArrayList;
import java.util.List;
/**
* Redis存储Java对象:FastJSON序列化实操类(实际开发首选)
* 核心:无需实现Serializable接口,通过FastJSON将对象转换为JSON串,完成Redis存储与读取
*/
public class RedisJsonSerializeTest {
// 静态Jedis实例,复用Redis连接(与前文RedisSerializeTest逻辑一致,参考Chapter1)
static Jedis jedis;
// 静态初始化块:启动时创建Redis连接,确保连接成功后再执行后续操作
static {
try {
// 连接本地Redis服务器(默认地址127.0.0.1,端口6379,无密码)
jedis = new Jedis("127.0.0.1", 6379);
// 可选:若Redis设置了密码,添加授权操作(解除注释,替换为自己的Redis密码)
// jedis.auth("yourRedisPassword");
System.out.println("Redis连接成功,客户端信息:" + jedis);
} catch (Exception e) {
System.err.println("Redis连接失败:" + e.getMessage());
// 连接失败时,终止程序,避免后续操作报错
System.exit(1);
}
}
public static void main(String[] args) {
try {
// 1. 准备需要存入Redis的Java对象(List<User>集合,复用前文数据,无需修改)
List<User> userList = new ArrayList<User>();
User u1 = new User();
u1.setId(100);
u1.setName("张1");
u1.setAddress("南京");
User u2 = new User();
u2.setId(101);
u2.setName("张2");
u2.setAddress("上海");
userList.add(u1);
userList.add(u2);
System.out.println("准备存入Redis的Java对象(List<User>):" + userList);
// 2. JSON序列化:将List<User>对象 → JSON字符串 → 字节流(核心步骤)
// 2.1 第一步:对象转换为JSON字符串(FastJSON核心API:JSON.toJSONString(obj))
String jsonStr = JSON.toJSONString(userList);
System.out.println("Java对象序列化后的JSON字符串:" + jsonStr);
// 2.2 第二步:JSON字符串转换为字节流(适配Redis存储,与前文逻辑一致)
byte[] jsonBytes = jsonStr.getBytes();
System.out.println("JSON字符串转换后的字节流长度:" + jsonBytes.length);
// 3. 将JSON序列化后的字节流,存入Redis
// 注意:key命名规范(与前文区分,避免冲突),此处用"user:list:json"
String redisKey = "user:list:json";
jedis.set(redisKey.getBytes(), jsonBytes);
System.out.println("JSON序列化后的字节流,已成功存入Redis(key:" + redisKey + ")");
// 4. 从Redis读取字节流(与前文读取逻辑一致,无差异)
byte[] redisJsonBytes = jedis.get(redisKey.getBytes());
System.out.println("从Redis读取到的字节流长度:" + redisJsonBytes.length);
// 5. JSON反序列化:将字节流 → JSON字符串 → List<User>对象(核心步骤)
// 5.1 第一步:字节流转换为JSON字符串
String redisJsonStr = new String(redisJsonBytes);
System.out.println("从Redis读取到的JSON字符串:" + redisJsonStr);
// 5.2 第二步:JSON字符串转换为Java对象(FastJSON核心API:JSON.parseArray(jsonStr, 目标类型.class))
// 注意:List集合反序列化,需指定集合元素的类型(User.class)
List<User> jsonUserList = JSON.parseArray(redisJsonStr, User.class);
System.out.println("JSON反序列化完成,恢复为Java对象(List<User>):" + jsonUserList);
// 6. 验证结果:遍历恢复后的对象,确认属性与存入时完全一致
System.out.println("\n验证JSON反序列化结果(对象属性详情):");
for (User u : jsonUserList) {
System.out.println("用户ID:" + u.getId() + ",用户名:" + u.getName() + ",地址:" + u.getAddress());
}
// 7. 额外验证:JSON序列化无需实现Serializable接口
// 可手动删除User类中的implements Serializable,重新运行,程序依然正常执行
System.out.println("\n验证:JSON序列化无需实现Serializable接口,程序运行正常!");
// 关闭Redis连接(释放资源,连接池场景无需手动关闭)
jedis.close();
} catch (Exception e) {
// 捕获所有异常(Redis连接异常、JSON序列化/反序列化异常等)
e.printStackTrace();
}
}
}
2.4.4 运行与测试


3.基于 Redis 的数据库查询缓存实现
3.1 核心目标
这套代码的核心目的是:实现数据库查询结果的 Redis 缓存拦截,具体逻辑为:
- 访问数据时优先查询 Redis 缓存;
- 如果缓存中无数据(缓存未命中),则查询 MySQL 数据库获取数据;
- 将数据库查询结果写入 Redis 并设置过期时间(60 秒),供后续请求复用;
- 如果缓存中有数据,则直接返回缓存数据,避免数据库查询开销。
3.2 数据库准备

sql
-- 创建表 --
CREATE TABLE t_classes (
cid INT PRIMARY KEY AUTO_INCREMENT,
cname VARCHAR(20) NOT NULL,
cphone VARCHAR(200)
);
-- 删除数据库表,用于先前如果创建好的表 --
DROP TABLE t_classes
-- 插入数据 --
INSERT INTO t_classes(cname,cphone) VALUES("张三","13800013800");
INSERT INTO t_classes(cname,cphone) VALUES("李四","13700013700");
INSERT INTO t_classes(cname,cphone) VALUES("王五","13600013600");
INSERT INTO t_classes(cname,cphone) VALUES("张六","13500013500");
INSERT INTO t_classes(cname,cphone) VALUES("王七","13400013400");
INSERT INTO t_classes(cname,cphone) VALUES("李八","13300013300");
-- 查询表 --
SELECT * FROM t_classes
3.3 添加Maven依赖

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.hy</groupId>
<artifactId>redisdemo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Redis客户端 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.2.3</version> <!-- 请检查最新版本 -->
</dependency>
<!-- 添加Logback作为SLF4J的日志实现 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
</dependency>
<!-- 添加FastJSON作为JSON处理库 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.32</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
</dependencies>
</project>
3.4 具体实现
① 定义核心接口(DBAop.java)
首先定义统一的操作接口,规范缓存拦截的行为,这是面向接口编程的基础。
java
package com.hy.chapter8;
import redis.clients.jedis.Jedis;
/**
* 数据库操作拦截器接口
* 定义缓存拦截的统一方法规范
*/
public interface DBAop {
public String interceptor(Jedis jedis);
}
② 创建实体类(Classes.java)
实体类对应数据库表t_classes的结构,用于封装数据库查询结果。
java
package com.hy.chapter8;
/**
* classes实体类
* 对应数据库t_classes表的字段
*/
public class Classes {
private int cid;
private String cname;
private String cphone;
public int getCid() {
return cid;
}
public void setCid(int cid) {
this.cid = cid;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
public String getCphone() {
return cphone;
}
public void setCphone(String cphone) {
this.cphone = cphone;
}
}
③ 实现缓存状态拦截器(InterceptorDatas.java)
实现DBAop接口,专门负责检查 Redis 缓存中是否存在目标数据,返回缓存状态标识。
java
package com.hy.chapter8;
import redis.clients.jedis.Jedis;
/**
* 缓存数据拦截器
* 负责检查Redis中是否存在指定的缓存数据
*/
public class InterceptorDatas implements DBAop {
@Override
public String interceptor(Jedis jedis) {
// 从Redis中获取缓存数据
String cacheData = jedis.get("classesdatas");
// 判断缓存是否为空(null或空字符串)
if (null == cacheData || "".equals(cacheData)) {
System.out.println("--------分布式缓存中没有数据");
return "emtry"; // 缓存为空标识
} else {
System.out.println("*******分布式缓存中有数据");
return "notnull"; // 缓存非空标识
}
}
}
④ 实现数据库查询工具类(DB.java)
负责建立 MySQL 数据库连接,查询t_classes表数据,并将结果转换为 JSON 字符串(便于 Redis 存储)。
java
package com.hy.chapter8;
import com.alibaba.fastjson.JSONArray;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
/**
* 数据库操作工具类
* 负责连接MySQL并查询t_classes表数据
*/
public class DB {
// 数据库连接对象
private Connection conn;
// 构造方法:初始化数据库连接
public DB() {
try {
// 加载MySQL驱动(MySQL 8.0+版本驱动)
Class.forName("com.mysql.cj.jdbc.Driver");
// 建立数据库连接(URL、用户名、密码需根据实际环境修改)
this.conn = DriverManager.getConnection(
"jdbc:mysql://127.0.0.1:3306/mysql2026",
"root",
"Hy61573166!!!"
);
} catch (ClassNotFoundException e) {
// 驱动加载失败异常处理
e.printStackTrace();
} catch (SQLException e) {
// 数据库连接失败异常处理
e.printStackTrace();
}
}
// 查询t_classes表所有数据并转换为JSON字符串
public String getDatas() {
// 查询SQL语句
String sql = "select * from t_classes";
// 存储查询结果的集合
List<Classes> lists = new ArrayList<Classes>();
// 最终返回的JSON字符串
String result = "";
try {
// 创建预编译Statement(防止SQL注入)
PreparedStatement pstmt = this.conn.prepareStatement(sql);
// 执行查询,获取结果集
ResultSet rs = pstmt.executeQuery();
// 遍历结果集,封装为Classes对象
while (rs.next()) {
Classes c = new Classes();
c.setCid(rs.getInt(1)); // 第1列:班级ID
c.setCname(rs.getString(2)); // 第2列:班级名称
c.setCphone(rs.getString(3)); // 第3列:班级电话
lists.add(c); // 添加到集合
}
// 将集合转换为JSON字符串(便于Redis存储)
result = JSONArray.toJSONString(lists);
} catch (SQLException e) {
// 查询异常处理
e.printStackTrace();
} finally {
// 关闭数据库连接(释放资源)
if (this.conn != null) {
try {
this.conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return result;
}
}
⑤ 实现核心业务逻辑(ClassesImpl.java)
整合缓存检查和数据库查询逻辑,实现 "缓存优先,未命中则查库并回写缓存" 的核心流程。
java
package com.hy.chapter8;
import redis.clients.jedis.Jedis;
/**
* 班级数据业务实现类
* 整合缓存拦截和数据库查询逻辑
*/
public class ClassesImpl implements DBAop {
// 缓存拦截器对象
private InterceptorDatas interceptorDatas;
// 构造方法:注入缓存拦截器
public ClassesImpl(InterceptorDatas interceptorDatas) {
this.interceptorDatas = interceptorDatas;
}
// 获取班级数据(优先缓存)
// jedis Redis客户端连接对象
@Override
public String interceptor(Jedis jedis) {
String result = "";
// 检查缓存状态:如果缓存为空
if (this.interceptorDatas.interceptor(jedis).equals("emtry")) {
// 1. 从数据库查询数据
DB db = new DB();
result = db.getDatas();
// 2. 将查询结果写入Redis,并设置60秒过期时间(缓存策略)
// 适用于周期性变化的数据(如每日新闻、临时数据)
jedis.setex("classesdatas", 60, result);
} else {
// 缓存非空:直接从Redis获取数据
result = jedis.get("classesdatas");
}
return result;
}
}
⑥ 编写测试类(Test.java)
创建 Redis 连接,调用核心业务逻辑,验证整个缓存流程。
java
package com.hy.chapter8;
import redis.clients.jedis.Jedis;
/**
* 测试类
* 验证Redis缓存+数据库查询的完整流程
*/
public class Test {
// Redis客户端连接对象(静态变量,全局复用)
static Jedis jedis;
// 静态代码块:初始化Redis连接(程序启动时执行)
static {
// 连接本地Redis服务(默认端口6379)
jedis = new Jedis("127.0.0.1", 6379);
System.out.println("连接成功:" + jedis);
}
public static void main(String[] args) {
// 1. 创建业务实现类实例(注入缓存拦截器)
ClassesImpl cimpl = new ClassesImpl(new InterceptorDatas());
// 2. 调用核心方法,获取数据
String result = cimpl.interceptor(jedis);
// 3. 打印结果
System.out.println("获取的结果为:" + result);
}
}
3.5 运行与测试
-
第一次运行:
- Redis 中无
classesdatas缓存,InterceptorDatas返回emtry; ClassesImpl调用DB类查询 MySQL 数据库;- 查询结果转换为 JSON 字符串,写入 Redis 并设置 60 秒过期;
- 控制台输出:
--------分布式缓存中没有数据+ 数据库查询的 JSON 结果。
- Redis 中无

-
60 秒内再次运行:
- Redis 中已有缓存数据,
InterceptorDatas返回notnull; ClassesImpl直接从 Redis 获取数据,无需查询数据库;- 控制台输出:
*******分布式缓存中有数据+ 缓存的 JSON 结果。
- Redis 中已有缓存数据,


- 60 秒后运行:Redis 缓存过期,流程回到 "第一次运行" 状态。
4.基于 Redis 发布订阅(Pub/Sub)的多消费者消息队列
4.1 实现目标
基于 Redis 的 Pub/Sub 机制和 Jedis 客户端,实现了单生产者 + 双消费者的轻量级消息队列模型,核心功能如下:
- 基于
JedisPool构建 Redis 连接池,实现 Redis 连接的复用,避免频繁创建 / 销毁 TCP 连接的性能损耗; - 实现控制台消息生产者:从控制台手动输入消息,发布到 Redis 指定频道
hychannel; - 实现两个消息消费者,同时订阅 Redis 的
hychannel频道,接收生产者发布的消息; - 消费者差异化处理消息:消费者 2 将符合格式的消息解析后落地到 MySQL 数据库的
t_classes表,消费者 1 仅做消息打印展示; - 演示 Redis Pub/Sub 的广播特性:同一频道的所有订阅者能同时接收到生产者发布的同一条消息。
4.2 具体实现
① 数据库操作工具类(DB.java)
作为数据持久化基础工具 ,负责建立 MySQL 数据库连接,并提供向t_classes表插入数据的方法,为消费者 2 的 "消息落地" 功能提供底层支撑。
java
package com.hy.chapter9;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* 数据库操作工具类
* 职责:建立MySQL连接,实现向t_classes表的数据插入操作
*/
public class DB {
// 数据库连接对象,用于执行SQL操作
private Connection conn;
// 构造方法:初始化数据库连接
public DB() {
try {
// 加载MySQL 8.0+版本的JDBC驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 建立数据库连接,指定数据库地址、库名、用户名和密码
this.conn = DriverManager.getConnection(
"jdbc:mysql://127.0.0.1:3306/mysql2026",
"root",
"Hy61573166!!!"
);
} catch (ClassNotFoundException e) {
// 驱动加载失败时打印异常
e.printStackTrace();
} catch (SQLException e) {
// 数据库连接失败时打印异常
e.printStackTrace();
}
}
// 向t_classes表插入数据
public void addDatas(Object obj) {
// 定义插入SQL,使用占位符防止SQL注入
String sql = "insert into t_classes(cid,cname,cphone) values(?,?,?)";
// 将入参转为Object数组,匹配表的三个字段
Object[] objs = (Object[]) obj;
try {
// 提前解析参数(方便后续打印成功消息)
int cid = Integer.parseInt(objs[0].toString().trim());
String cname = objs[1].toString().trim();
String cphone = objs[2].toString().trim();
// 创建预编译Statement对象,执行SQL操作
PreparedStatement pstmt = this.conn.prepareStatement(sql);
// 为SQL占位符赋值,按字段类型做对应转换
pstmt.setInt(1, cid);
pstmt.setString(2, cname);
pstmt.setString(3, cphone);
// 执行更新操作,获取受影响行数(判断是否插入成功)
int affectedRows = pstmt.executeUpdate();
// 插入成功时打印明确的提示消息
if (affectedRows > 0) {
System.out.println("✅ 数据库添加成功!");
System.out.println(" 插入数据:cid=" + cid + ",班级名称=" + cname + ",联系电话=" + cphone);
}
} catch (NumberFormatException e) {
// 补充cid格式错误的提示(避免空指针/非数字报错)
System.out.println("❌ 数据库添加失败:cid必须为整数!错误信息:" + e.getMessage());
e.printStackTrace();
} catch (SQLException e) {
// 数据插入失败时打印异常+提示
System.out.println("❌ 数据库添加失败:SQL执行异常!错误信息:" + e.getMessage());
e.printStackTrace();
} finally {
// 最终关闭数据库连接,释放资源,避免连接泄露
if (null != conn) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
② 订阅者消息处理器 1(Subscriber1.java)
继承 Redis 客户端的JedisPubSub类,实现第一个消费者的消息处理逻辑,是消费者 1 的核心处理类,仅负责接收并打印消息,无其他业务操作。
java
package com.hy.chapter9;
import redis.clients.jedis.JedisPubSub;
/**
* 订阅者消息处理器(消费者1)
* 职责:接收Redis频道消息,仅做控制台打印展示
*/
public class Subscriber1 extends JedisPubSub {
// 接收到订阅频道消息时触发的方法
// channel 订阅的Redis频道名称
// message 接收到的消息内容
@Override
public void onMessage(String channel, String message) {
// 打印接收到的消息,标注为消费者1接收,便于区分
System.out.println("订阅1的接受的消息为:" + message);
// 预留扩展:可在此添加日志记录、短信通知等其他业务逻辑
// String[] datas= message.split(",");
// DB db = new DB();
// db.addDatas(datas);
}
}
③ 订阅者消息处理器 2(Subscriber2.java)
同样继承JedisPubSub类,实现第二个消费者的消息处理逻辑,是消费者 2 的核心处理类,负责接收消息、清洗消息、解析消息并调用 DB 类将消息落地到数据库。
java
package com.hy.chapter9;
import redis.clients.jedis.JedisPubSub;
/**
* 订阅者消息处理器(消费者2)
* 职责:接收Redis频道消息,清洗并解析消息,将消息落地到MySQL数据库
*/
public class Subscriber2 extends JedisPubSub {
// 接收到订阅频道消息时触发的方法
@Override
public void onMessage(String channel, String message) {
// 打印接收到的消息,标注为消费者2接收,便于区分
System.out.println("订阅2接受的消息为:" + message);
// 消息清洗:去除消息首尾的空格,避免空字符影响解析
String cleanMessage = message.trim();
// 校验清洗后的消息是否为空,为空则跳过处理
if (cleanMessage.isEmpty()) {
System.out.println("错误:收到空消息,跳过处理!");
return;
}
// 按英文逗号分割消息,匹配数据库插入的参数格式
String[] datas = cleanMessage.split(",");
// 创建数据库工具类实例,调用插入方法完成消息落地
DB db = new DB();
db.addDatas(datas);
}
}
④ 消息生产者线程(MakeMessageThread.java)
继承Thread类,实现生产者线程的业务逻辑,作为独立线程运行,负责从控制台读取用户输入的消息,并通过 Redis 连接池获取连接,将消息发布到指定 Redis 频道。
java
package com.hy.chapter9;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* 消息生产者线程
* 职责:独立线程运行,从控制台读取输入消息,发布到Redis的hychannel频道
*/
public class MakeMessageThread extends Thread {
// 注入Redis连接池,实现连接复用
private JedisPool jedisPool;
// 构造方法:注入Redis连接池
public MakeMessageThread(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
// 线程核心执行逻辑:持续读取控制台输入并发布消息
@Override
public void run() {
System.out.println("基于JedisPool的生产者平台启动...");
// 创建控制台输入流,用于读取用户手动输入的消息
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
// 从Redis连接池获取可用的Redis连接
Jedis jedis = this.jedisPool.getResource();
// 无限循环,持续接收用户输入,直到程序停止
while (true) {
System.out.println("请在控制台输入消息:");
try {
// 读取控制台输入的一行消息(阻塞方法,等待用户输入)
String message = br.readLine();
// 核心操作:将消息发布到Redis的hychannel频道
jedis.publish("hychannel", message);
} catch (IOException e) {
// 输入流异常(如控制台关闭)时打印异常
e.printStackTrace();
}
}
}
}
⑤ 消费者线程 1(ReceiverMessageThread1.java)
继承Thread类,实现第一个消费者的线程业务逻辑 ,作为独立线程运行,负责从 Redis 连接池获取连接,订阅指定 Redis 频道,并绑定对应的消息处理器Subscriber1。
java
package com.hy.chapter9;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
/**
* 第一个消息消费者线程
* 职责:独立线程运行,订阅Redis频道,绑定Subscriber1处理器处理消息
*/
public class ReceiverMessageThread1 extends Thread {
// 注入Redis连接池,实现连接复用
private JedisPool jedisPool;
// 初始化消息处理器,绑定当前消费者的消息处理逻辑
private Subscriber1 subscriber1 = new Subscriber1();
// 构造方法:注入Redis连接池
public ReceiverMessageThread1(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
// 线程核心执行逻辑:订阅Redis频道并阻塞监听消息
@Override
public void run() {
System.out.println("消息的消费者平台1已经启动");
// 从Redis连接池获取可用的Redis连接
Jedis jedis = this.jedisPool.getResource();
// 核心操作:订阅hychannel频道,绑定Subscriber1处理器
// 注意:subscribe为阻塞方法,需在独立线程中运行
jedis.subscribe(subscriber1, "hychannel");
}
}
⑥ 消费者线程 2(ReceiverMessageThread2.java)
继承Thread类,实现第二个消费者的线程业务逻辑 ,作为独立线程运行,负责从 Redis 连接池获取连接,订阅指定 Redis 频道,并绑定对应的消息处理器Subscriber2。
java
package com.hy.chapter9;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
/**
* 第二个消息消费者线程
* 职责:独立线程运行,订阅Redis频道,绑定Subscriber2处理器处理消息
*/
public class ReceiverMessageThread2 extends Thread {
// 注入Redis连接池,实现连接复用
private JedisPool jedisPool;
// 初始化消息处理器,绑定当前消费者的消息处理逻辑
private Subscriber2 subscriber2 = new Subscriber2();
// 构造方法:注入Redis连接池
public ReceiverMessageThread2(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
// 线程核心执行逻辑:订阅Redis频道并阻塞监听消息
@Override
public void run() {
System.out.println("消息的消费者平台2已经启动");
// 从Redis连接池获取可用的Redis连接
Jedis jedis = this.jedisPool.getResource();
// 核心操作:订阅hychannel频道,绑定Subscriber2处理器
// 注意:subscribe为阻塞方法,需在独立线程中运行
jedis.subscribe(subscriber2, "hychannel");
}
}
⑦ 主程序入口(Chapter9.java)
整套系统的唯一入口,负责初始化 Redis 连接池配置、构建 Redis 连接池,依次启动生产者线程和两个消费者线程,整合所有组件并启动整个消息队列系统。
java
package com.hy.chapter9;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* 主程序入口类
* 职责:初始化Redis连接池,启动生产者和所有消费者线程,启动整个消息队列系统
*/
public class Chapter9 {
public static void main(String[] args) {
// 1. 创建Redis连接池配置对象,使用默认配置(生产环境可自定义参数)
JedisPoolConfig poolConfig = new JedisPoolConfig();
// 2. 构建Redis连接池,指定配置、Redis服务地址和端口
JedisPool jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379);
// 打印连接成功信息,确认Redis连接池初始化完成
System.out.println("连接redis服务器成功");
// 3. 初始化并启动消息生产者线程,注入Redis连接池
MakeMessageThread makeMessageThread = new MakeMessageThread(jedisPool);
makeMessageThread.start();
// 4. 初始化并启动第二个消费者线程,注入Redis连接池
ReceiverMessageThread2 receiverMessageThread = new ReceiverMessageThread2(jedisPool);
receiverMessageThread.start();
// 5. 初始化并启动第一个消费者线程,注入Redis连接池
ReceiverMessageThread1 receiverMessageThread1 = new ReceiverMessageThread1(jedisPool);
receiverMessageThread1.start();
}
}
4.3 运行与测试
① 控制台输出:

② 数据库查询:
