Java入门级教程26——序列化和反序列化,Redis存储Java对象、查询数据库与实现多消费者消息队列

目录

[1.Java 对象序列化与反序列化](#1.Java 对象序列化与反序列化)

[1.1 核心概念](#1.1 核心概念)

[1.2 具体实现](#1.2 具体实现)

[1.3 运行与测试](#1.3 运行与测试)

2.Redis存储Java对象(序列化/反序列化实操)

[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 缓存拦截,具体逻辑为:

  1. 访问数据时优先查询 Redis 缓存;
  2. 如果缓存中无数据(缓存未命中),则查询 MySQL 数据库获取数据;
  3. 将数据库查询结果写入 Redis 并设置过期时间(60 秒),供后续请求复用;
  4. 如果缓存中有数据,则直接返回缓存数据,避免数据库查询开销。

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 结果。
  • 60 秒内再次运行

    • Redis 中已有缓存数据,InterceptorDatas返回notnull
    • ClassesImpl直接从 Redis 获取数据,无需查询数据库;
    • 控制台输出:*******分布式缓存中有数据 + 缓存的 JSON 结果。
  • 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 运行与测试

① 控制台输出:

② 数据库查询:

相关推荐
多多*2 小时前
Mysql数据库相关 事务 MVCC与锁的爱恨情仇 锁的层次架构 InnoDB锁分析
java·数据库·windows·sql·oracle·面试·哈希算法
cyforkk2 小时前
15、Java 基础硬核复习:File类与IO流的核心逻辑与面试考点
java·开发语言·面试
李少兄2 小时前
解决 org.springframework.context.annotation.ConflictingBeanDefinitionException 报错
java·spring boot·mybatis
大飞哥~BigFei2 小时前
整数ID与短字符串互转思路及开源实现分享
java·开源
benjiangliu2 小时前
LINUX系统-09-程序地址空间
android·java·linux
历程里程碑2 小时前
子串-----和为 K 的子数组
java·数据结构·c++·python·算法·leetcode·tornado
独自破碎E3 小时前
字符串相乘
android·java·jvm
东东5163 小时前
OA自动化居家办公管理系统 ssm+vue
java·前端·vue.js·后端·毕业设计·毕设
没有bug.的程序员3 小时前
Spring Cloud Alibaba:Nacos 配置中心与服务发现的工业级深度实战
java·spring boot·nacos·服务发现·springcloud·配置中心·alibaba