Spring Boot与Kafka整合实战:微服务消息队列最佳实践
2026/7/21 6:51:39
在图书购买系统中,我们常常需要将图书数据缓存到 Redis 中(如热门图书列表),并支持分页展示。这可以提高查询效率,避免频繁访问数据库。本教程基于 Spring Boot 3.x(最新 LTS 版本),使用 Redis 作为缓存层,实现从配置到前后端交互的完整流程。
前提假设:
项目结构概览:
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 目录或单独前端项目)创建 Spring Boot 项目:
添加依赖(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>docker run -d -p 6379:6379 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 分钟packagecom.example.bookstore.config;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.data.redis.connection.RedisConnectionFactory;importorg.springframework.data.redis.core.RedisTemplate;importorg.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;importorg.springframework.data.redis.serializer.RedisSerializer;@ConfigurationpublicclassRedisConfig{@BeanpublicRedisTemplate<String,Object>redisTemplate(RedisConnectionFactoryconnectionFactory){RedisTemplate<String,Object>template=newRedisTemplate<>();template.setConnectionFactory(connectionFactory);// 使用 JSON 序列化(支持复杂对象)RedisSerializer<?>jsonSerializer=newGenericJackson2JsonRedisSerializer();template.setDefaultSerializer(jsonSerializer);template.setKeySerializer(RedisSerializer.string());template.setHashKeySerializer(RedisSerializer.string());template.setValueSerializer(jsonSerializer);template.setHashValueSerializer(jsonSerializer);returntemplate;}}packagecom.example.bookstore.entity;importlombok.Data;@DatapublicclassBook{privateLongid;privateStringtitle;privateStringauthor;privateDoubleprice;}packagecom.example.bookstore.service;importcom.example.bookstore.entity.Book;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.data.redis.core.RedisTemplate;importorg.springframework.data.redis.core.ZSetOperations;importorg.springframework.stereotype.Service;importjava.util.List;importjava.util.Set;importjava.util.stream.Collectors;@ServicepublicclassBookService{@AutowiredprivateRedisTemplate<String,Object>redisTemplate;privatestaticfinalStringBOOKS_KEY="books";// Redis Sorted Set 键// 添加图书(score 为 id,假设 id 自增)publicvoidaddBook(Bookbook){ZSetOperations<String,Object>zSetOps=redisTemplate.opsForZSet();zSetOps.add(BOOKS_KEY,book,book.getId());// score = id}// 分页查询(使用 ZRANGE 模拟分页)publicList<Book>getBooksByPage(intpage,intsize){ZSetOperations<String,Object>zSetOps=redisTemplate.opsForZSet();// ZRANGE start = (page-1)*size, end = page*size -1Set<Object>booksSet=zSetOps.range(BOOKS_KEY,(page-1)*size,page*size-1);returnbooksSet.stream().map(obj->(Book)obj).collect(Collectors.toList());}// 获取总记录数publiclonggetTotalBooks(){returnredisTemplate.opsForZSet().size(BOOKS_KEY);}}// 在服务中添加测试方法或 CommandLineRunnerpublicvoidinitData(){addBook(newBook(1L,"Java编程思想","Bruce Eckel",99.0));addBook(newBook(2L,"Spring Boot实战","Craig Walls",89.0));// ... 添加更多}BookController.java:提供分页 API,支持 ?page=1&size=10 参数。
packagecom.example.bookstore.controller;importcom.example.bookstore.entity.Book;importcom.example.bookstore.service.BookService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.http.ResponseEntity;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RequestParam;importorg.springframework.web.bind.annotation.RestController;importjava.util.HashMap;importjava.util.List;importjava.util.Map;@RestControllerpublicclassBookController{@AutowiredprivateBookServicebookService;@GetMapping("/books")publicResponseEntity<Map<String,Object>>getBooks(@RequestParam(defaultValue="1")intpage,@RequestParam(defaultValue="10")intsize){List<Book>books=bookService.getBooksByPage(page,size);longtotal=bookService.getTotalBooks();Map<String,Object>response=newHashMap<>();response.put("books",books);response.put("total",total);response.put("currentPage",page);response.put("totalPages",(total+size-1)/size);returnResponseEntity.ok(response);}}假设前端页面为 index.html(放在 src/main/resources/static/ 下,或单独前端项目)。
<!DOCTYPEhtml><htmllang="zh-CN"><head><metacharset="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><tableid="bookTable"><thead><tr><th>ID</th><th>标题</th><th>作者</th><th>价格</th></tr></thead><tbody></tbody></table><divclass="pagination"><buttonid="prevBtn"disabled>上一页</button><spanid="pageInfo"></span><buttonid="nextBtn">下一页</button></div><script>letcurrentPage=1;constpageSize=10;asyncfunctionfetchBooks(page){constresponse=awaitfetch(`/books?page=${page}&size=${pageSize}`);constdata=awaitresponse.json();consttbody=document.querySelector('#bookTable tbody');tbody.innerHTML='';// 清空表格data.books.forEach(book=>{consttr=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>ZRANGE books 0 -1 WITHSCORES查看数据。这个实现覆盖了从配置到交互的全链路。如果需要完整源码或特定调整(如用 Vue),随时告诉我!🚀