【基于SpringBoot的图书购买系统】Redis中的数据以分页的形式展示:从配置到前后端交互的完整实现

基于 Spring Boot 的图书购买系统:Redis 中的数据以分页形式展示完整实现

在图书购买系统中,我们常常需要将图书数据缓存到 Redis 中(如热门图书列表),并支持分页展示。这可以提高查询效率,避免频繁访问数据库。本教程基于 Spring Boot 3.x(最新 LTS 版本),使用 Redis 作为缓存层,实现从配置到前后端交互的完整流程。

前提假设

  • 项目使用 Spring Boot + Redis + Spring Data Redis。
  • 图书实体(Book):包含 id、title、author、price 等字段。
  • 数据存储在 Redis 的 List 或 Sorted Set 中(推荐 Sorted Set 以支持排序和高效分页)。
  • 分页策略:Redis 无原生分页,使用 ZRANGE(Sorted Set)或 LRANGE(List)模拟。基于工具搜索结果,Sorted Set 是高效选择(O(log N + M) 复杂度,M 为页面大小)。
  • 前端:简单使用 HTML + JavaScript(Fetch API)调用 REST 接口展示分页数据。生产环境可换 Vue/React。

项目结构概览

src/main/java
├── com.example.bookstore
│   ├── BookstoreApplication.java
│   ├── config/RedisConfig.java
│   ├── entity/Book.java
│   ├── service/BookService.java
│   ├── controller/BookController.java
resources/application.properties
frontend/index.html  // 前端页面(可选,放在 static 目录或单独前端项目)

步骤 1: 项目初始化与依赖配置

  1. 创建 Spring Boot 项目
  • 使用 Spring Initializr(https://start.spring.io/)创建项目。
  • 选择:Spring Boot 3.2.x、Java 17+、依赖:Spring Web、Spring Data Redis、Lombok(可选)。
  1. 添加依赖(pom.xml):
<dependencies>
    <!-- Spring Boot Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!-- Lombok 简化代码 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>
  1. 安装 Redis
  • Windows/macOS:下载 Redis 官方版或用 Docker:docker run -d -p 6379:6379 redis
  • 默认端口 6379,无密码。

步骤 2: Redis 配置

  1. application.properties 配置 Redis 连接:
# Redis 配置
spring.data.redis.host=localhost
spring.data.redis.port=6379
# 可选:密码、数据库
# spring.data.redis.password=yourpassword
# spring.data.redis.database=0

# 缓存配置(可选,启用缓存)
spring.cache.type=redis
spring.cache.redis.time-to-live=600000  # 缓存 TTL 10 分钟
  1. Redis 配置类(RedisConfig.java):自定义 RedisTemplate,支持序列化。
package com.example.bookstore.config;

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.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        // 使用 JSON 序列化(支持复杂对象)
        RedisSerializer<?> jsonSerializer = new GenericJackson2JsonRedisSerializer();
        template.setDefaultSerializer(jsonSerializer);
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());
        template.setValueSerializer(jsonSerializer);
        template.setHashValueSerializer(jsonSerializer);
        return template;
    }
}
  • 解释:默认 StringRedisTemplate 只支持字符串;这里用 RedisTemplate 支持对象存储。JSON 序列化便于存储 Book 对象。

步骤 3: 图书实体与数据存储到 Redis

  1. Book 实体(Book.java):
package com.example.bookstore.entity;

import lombok.Data;

@Data
public class Book {
    private Long id;
    private String title;
    private String author;
    private Double price;
}
  1. BookService:存储图书到 Redis 的 Sorted Set(键:”books”,score 为 id 或 price 以支持排序)。
package com.example.bookstore.service;

import com.example.bookstore.entity.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Service
public class BookService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    private static final String BOOKS_KEY = "books";  // Redis Sorted Set 键

    // 添加图书(score 为 id,假设 id 自增)
    public void addBook(Book book) {
        ZSetOperations<String, Object> zSetOps = redisTemplate.opsForZSet();
        zSetOps.add(BOOKS_KEY, book, book.getId());  // score = id
    }

    // 分页查询(使用 ZRANGE 模拟分页)
    public List<Book> getBooksByPage(int page, int size) {
        ZSetOperations<String, Object> zSetOps = redisTemplate.opsForZSet();
        // ZRANGE start = (page-1)*size, end = page*size -1
        Set<Object> booksSet = zSetOps.range(BOOKS_KEY, (page - 1) * size, page * size - 1);
        return booksSet.stream()
                .map(obj -> (Book) obj)
                .collect(Collectors.toList());
    }

    // 获取总记录数
    public long getTotalBooks() {
        return redisTemplate.opsForZSet().size(BOOKS_KEY);
    }
}
  • 分页实现:使用 Sorted Set 的 ZRANGE,按 score 分页。page 从 1 开始。
  • 为什么 Sorted Set:支持排序和范围查询,效率高于 List 的 LRANGE(List 为 O(N) 最坏情况)。
  • 数据初始化:可在应用启动时(或测试方法)添加样例数据:
// 在服务中添加测试方法或 CommandLineRunner
public void initData() {
    addBook(new Book(1L, "Java编程思想", "Bruce Eckel", 99.0));
    addBook(new Book(2L, "Spring Boot实战", "Craig Walls", 89.0));
    // ... 添加更多
}

步骤 4: 后端控制器(REST API)

BookController.java:提供分页 API,支持 ?page=1&size=10 参数。

package com.example.bookstore.controller;

import com.example.bookstore.entity.Book;
import com.example.bookstore.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
public class BookController {

    @Autowired
    private BookService bookService;

    @GetMapping("/books")
    public ResponseEntity<Map<String, Object>> getBooks(
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size) {

        List<Book> books = bookService.getBooksByPage(page, size);
        long total = bookService.getTotalBooks();

        Map<String, Object> response = new HashMap<>();
        response.put("books", books);
        response.put("total", total);
        response.put("currentPage", page);
        response.put("totalPages", (total + size - 1) / size);

        return ResponseEntity.ok(response);
    }
}
  • 解释:返回 JSON,包括图书列表、总记录、页码。使用 Spring 的 @RequestParam 处理分页参数。

步骤 5: 前端交互(简单 HTML + JS)

假设前端页面为 index.html(放在 src/main/resources/static/ 下,或单独前端项目)。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>图书购买系统 - 分页展示</title>
    <style>
        table { border-collapse: collapse; width: 100%; }
        th, td { border: 1px solid #ddd; padding: 8px; }
        .pagination { margin-top: 10px; }
        button { margin: 0 5px; }
    </style>
</head>
<body>
    <h1>Redis 中的图书列表(分页)</h1>
    <table id="bookTable">
        <thead>
            <tr><th>ID</th><th>标题</th><th>作者</th><th>价格</th></tr>
        </thead>
        <tbody></tbody>
    </table>
    <div class="pagination">
        <button id="prevBtn" disabled>上一页</button>
        <span id="pageInfo"></span>
        <button id="nextBtn">下一页</button>
    </div>

    <script>
        let currentPage = 1;
        const pageSize = 10;

        async function fetchBooks(page) {
            const response = await fetch(`/books?page=${page}&size=${pageSize}`);
            const data = await response.json();

            const tbody = document.querySelector('#bookTable tbody');
            tbody.innerHTML = '';  // 清空表格
            data.books.forEach(book => {
                const tr = document.createElement('tr');
                tr.innerHTML = `<td>${book.id}</td><td>${book.title}</td><td>${book.author}</td><td>${book.price}</td>`;
                tbody.appendChild(tr);
            });

            document.getElementById('pageInfo').textContent = `第 ${data.currentPage} 页 / 共 ${data.totalPages} 页 (总 ${data.total} 本书)`;

            document.getElementById('prevBtn').disabled = data.currentPage === 1;
            document.getElementById('nextBtn').disabled = data.currentPage >= data.totalPages;
        }

        // 按钮事件
        document.getElementById('prevBtn').addEventListener('click', () => {
            if (currentPage > 1) {
                currentPage--;
                fetchBooks(currentPage);
            }
        });

        document.getElementById('nextBtn').addEventListener('click', () => {
            currentPage++;
            fetchBooks(currentPage);
        });

        // 初始加载
        fetchBooks(currentPage);
    </script>
</body>
</html>
  • 解释:使用 Fetch API 调用后端 /books 接口,动态渲染表格和分页按钮。点击“上一页/下一页”触发请求。

步骤 6: 测试与运行

  1. 启动应用:运行 BookstoreApplication.java,确保 Redis 运行。
  2. 初始化数据:在服务中调用 initData() 或手动通过 Redis CLI 添加。
  3. 访问前端:浏览器打开 http://localhost:8080/index.html,查看分页效果。
  4. 验证 Redis:用 Redis CLI:ZRANGE books 0 -1 WITHSCORES 查看数据。

注意事项与优化

  • 性能:分页大小(size)不宜太大(<100),避免 O(N) 开销。
  • 错误处理:添加 try-catch 处理 Redis 连接异常。
  • 生产优化:用 Lettuce 连接池(默认配置)、添加缓存注解 @Cacheable(如果结合数据库)。
  • 扩展:结合数据库(JPA),Redis 只缓存热门图书;前端用 Element UI 或 Ant Design 提升 UI。
  • 最佳实践(基于搜索结果):Sorted Set 适合带排序的分页;若无排序需求,可用 List + LRANGE。

这个实现覆盖了从配置到交互的全链路。如果需要完整源码或特定调整(如用 Vue),随时告诉我!🚀

文章已创建 3707

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部