当前位置: 首页 > news >正文

Redis02-Ehcache缓存

Redis02-Ehcache缓存

【Ehcache缓存】+【使用@Cacheable】+【替换为Redis】

文章目录

  • Redis02-Ehcache缓存
  • 1-Ehcache缓存操作
    • 1. 添加依赖
    • 2. Ehcache配置
    • 3. 实体类
    • 4. 数据访问层(ConcurrentHashMap模拟数据库)
    • 5. 服务层(带缓存逻辑)
    • 6. 控制器层
    • 7. 启动类
    • 8. 测试用例
    • 9. API测试请求示例
    • 10. 关键特性说明


1-Ehcache缓存操作

个人实现代码仓库:https://gitee.com/enzoism/springboot_redis_ehcache

1. 添加依赖

<!-- pom.xml -->
<dependencies><!-- Spring Boot Starter Cache --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency><!-- Ehcache --><dependency><groupId>org.ehcache</groupId><artifactId>ehcache</artifactId></dependency><!-- Spring Boot Starter Web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency>
</dependencies>

2. Ehcache配置

# application.yml
spring:cache:type: ehcacheehcache:config: classpath:ehcache.xmlserver:port: 8080
<!-- ehcache.xml -->
<config xmlns="http://www.ehcache.org/v3"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.0.xsd"><cache alias="userCache"><key-type>java.lang.String</key-type><value-type>com.example.demo.entity.User</value-type><expiry><ttl unit="minutes">10</ttl></expiry><resources><heap unit="entries">1000</heap></resources></cache><cache alias="userListCache"><key-type>java.lang.String</key-type><value-type>java.util.List</value-type><expiry><ttl unit="minutes">5</ttl></expiry><resources><heap unit="entries">100</heap></resources></cache>
</config>

3. 实体类

package com.example.demo.entity;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {private static final long serialVersionUID = 1L;private Long userId;private Long roleId;private String username;private String email;private String phone;private Integer status;private String createTime;// 构建复合key的方法public String getCompositeKey() {return userId + "_" + roleId;}
}

4. 数据访问层(ConcurrentHashMap模拟数据库)

package com.example.demo.dao;import com.example.demo.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;@Slf4j
@Repository
public class UserDao {// 使用ConcurrentHashMap模拟数据库,以复合key作为键private final Map<String, User> database = new ConcurrentHashMap<>();// 初始化一些测试数据public UserDao() {// 初始化测试数据User user1 = new User(1L, 1L, "admin", "admin@example.com", "13800138000", 1, "2024-01-01");User user2 = new User(2L, 2L, "user", "user@example.com", "13900139000", 1, "2024-01-02");User user3 = new User(3L, 1L, "manager", "manager@example.com", "13700137000", 1, "2024-01-03");database.put(user1.getCompositeKey(), user1);database.put(user2.getCompositeKey(), user2);database.put(user3.getCompositeKey(), user3);log.info("初始化用户数据完成,共{}条记录", database.size());}/*** 新增用户*/public User insert(User user) {String key = user.getCompositeKey();if (database.containsKey(key)) {throw new RuntimeException("用户已存在,userId: " + user.getUserId() + ", roleId: " + user.getRoleId());}database.put(key, user);log.info("新增用户成功:{}", user);return user;}/*** 根据复合key查询用户*/public User selectByCompositeKey(Long userId, Long roleId) {String key = userId + "_" + roleId;User user = database.get(key);log.info("查询用户:userId={}, roleId={}, 结果:{}", userId, roleId, user != null ? "存在" : "不存在");return user;}/*** 查询所有用户*/public List<User> selectAll() {List<User> users = new ArrayList<>(database.values());log.info("查询所有用户,共{}条记录", users.size());return users;}/*** 根据userId查询用户列表(一个userId可能有多个roleId)*/public List<User> selectByUserId(Long userId) {List<User> users = database.values().stream().filter(user -> user.getUserId().equals(userId)).collect(Collectors.toList());log.info("根据userId查询用户:userId={}, 结果数量:{}", userId, users.size());return users;}/*** 根据roleId查询用户列表*/public List<User> selectByRoleId(Long roleId) {List<User> users = database.values().stream().filter(user -> user.getRoleId().equals(roleId)).collect(Collectors.toList());log.info("根据roleId查询用户:roleId={}, 结果数量:{}", roleId, users.size());return users;}/*** 更新用户*/public User update(User user) {String key = user.getCompositeKey();if (!database.containsKey(key)) {throw new RuntimeException("用户不存在,userId: " + user.getUserId() + ", roleId: " + user.getRoleId());}database.put(key, user);log.info("更新用户成功:{}", user);return user;}/*** 删除用户*/public boolean delete(Long userId, Long roleId) {String key = userId + "_" + roleId;User removed = database.remove(key);boolean success = removed != null;log.info("删除用户:userId={}, roleId={}, 结果:{}", userId, roleId, success ? "成功" : "失败");return success;}/*** 判断用户是否存在*/public boolean exists(Long userId, Long roleId) {String key = userId + "_" + roleId;boolean exists = database.containsKey(key);log.info("判断用户是否存在:userId={}, roleId={}, 结果:{}", userId, roleId, exists ? "存在" : "不存在");return exists;}/*** 获取数据库大小(用于测试)*/public int getDatabaseSize() {return database.size();}
}

5. 服务层(带缓存逻辑)

package com.example.demo.service;import com.example.demo.dao.UserDao;
import com.example.demo.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.*;
import org.springframework.stereotype.Service;import java.util.List;@Slf4j
@Service
@CacheConfig(cacheNames = "userCache") // 默认缓存名称
public class UserService {@Autowiredprivate UserDao userDao;/*** 新增用户* 新增后清除相关缓存*/@CacheEvict(key = "'allUsers'", allEntries = true)public User addUser(User user) {log.info("开始新增用户:{}", user);return userDao.insert(user);}/*** 根据复合key查询用户* 使用复合key作为缓存key*/@Cacheable(key = "#userId + '_' + #roleId", unless = "#result == null")public User getUserByCompositeKey(Long userId, Long roleId) {log.info("缓存未命中,查询数据库获取用户:userId={}, roleId={}", userId, roleId);return userDao.selectByCompositeKey(userId, roleId);}/*** 查询所有用户*/@Cacheable(key = "'allUsers'", unless = "#result == null || #result.isEmpty()")public List<User> getAllUsers() {log.info("缓存未命中,查询数据库获取所有用户");return userDao.selectAll();}/*** 根据userId查询用户列表*/@Cacheable(key = "'userId_' + #userId", unless = "#result == null || #result.isEmpty()")public List<User> getUsersByUserId(Long userId) {log.info("缓存未命中,查询数据库获取用户列表:userId={}", userId);return userDao.selectByUserId(userId);}/*** 根据roleId查询用户列表*/@Cacheable(key = "'roleId_' + #roleId", unless = "#result == null || #result.isEmpty()")public List<User> getUsersByRoleId(Long roleId) {log.info("缓存未命中,查询数据库获取用户列表:roleId={}", roleId);return userDao.selectByRoleId(roleId);}/*** 更新用户* 更新后清除该用户的缓存和相关列表缓存*/@Caching(evict = {@CacheEvict(key = "#user.userId + '_' + #user.roleId"),@CacheEvict(key = "'userId_' + #user.userId"),@CacheEvict(key = "'roleId_' + #user.roleId"),@CacheEvict(key = "'allUsers'", allEntries = true)})public User updateUser(User user) {log.info("开始更新用户:{}", user);return userDao.update(user);}/*** 删除用户* 删除后清除相关缓存*/@Caching(evict = {@CacheEvict(key = "#userId + '_' + #roleId"),@CacheEvict(key = "'userId_' + #userId"),@CacheEvict(key = "'roleId_' + #roleId"),@CacheEvict(key = "'allUsers'", allEntries = true)})public boolean deleteUser(Long userId, Long roleId) {log.info("开始删除用户:userId={}, roleId={}", userId, roleId);return userDao.delete(userId, roleId);}/*** 判断用户是否存在*/@Cacheable(key = "'exists_' + #userId + '_' + #roleId")public boolean existsUser(Long userId, Long roleId) {log.info("缓存未命中,查询数据库判断用户是否存在:userId={}, roleId={}", userId, roleId);return userDao.exists(userId, roleId);}/*** 手动清除指定用户的缓存*/@CacheEvict(key = "#userId + '_' + #roleId")public void evictUserCache(Long userId, Long roleId) {log.info("手动清除用户缓存:userId={}, roleId={}", userId, roleId);}/*** 清除所有用户缓存*/@CacheEvict(allEntries = true)public void evictAllUsersCache() {log.info("清除所有用户缓存");}
}

6. 控制器层

package com.example.demo.controller;import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;import java.util.List;@Slf4j
@RestController
@RequestMapping("/api/users")
public class UserController {@Autowiredprivate UserService userService;/*** 新增用户*/@PostMappingpublic ResponseEntity<User> addUser(@RequestBody User user) {try {User savedUser = userService.addUser(user);return ResponseEntity.ok(savedUser);} catch (Exception e) {log.error("新增用户失败:{}", e.getMessage());return ResponseEntity.badRequest().body(null);}}/*** 根据复合key查询用户*/@GetMapping("/{userId}/{roleId}")public ResponseEntity<User> getUserByCompositeKey(@PathVariable Long userId, @PathVariable Long roleId) {User user = userService.getUserByCompositeKey(userId, roleId);return user != null ? ResponseEntity.ok(user) : ResponseEntity.notFound().build();}/*** 查询所有用户*/@GetMappingpublic ResponseEntity<List<User>> getAllUsers() {List<User> users = userService.getAllUsers();return ResponseEntity.ok(users);}/*** 根据userId查询用户列表*/@GetMapping("/userId/{userId}")public ResponseEntity<List<User>> getUsersByUserId(@PathVariable Long userId) {List<User> users = userService.getUsersByUserId(userId);return ResponseEntity.ok(users);}/*** 根据roleId查询用户列表*/@GetMapping("/roleId/{roleId}")public ResponseEntity<List<User>> getUsersByRoleId(@PathVariable Long roleId) {List<User> users = userService.getUsersByRoleId(roleId);return ResponseEntity.ok(users);}/*** 更新用户*/@PutMappingpublic ResponseEntity<User> updateUser(@RequestBody User user) {try {User updatedUser = userService.updateUser(user);return ResponseEntity.ok(updatedUser);} catch (Exception e) {log.error("更新用户失败:{}", e.getMessage());return ResponseEntity.notFound().build();}}/*** 删除用户*/@DeleteMapping("/{userId}/{roleId}")public ResponseEntity<Void> deleteUser(@PathVariable Long userId, @PathVariable Long roleId) {boolean success = userService.deleteUser(userId, roleId);return success ? ResponseEntity.ok().build() : ResponseEntity.notFound().build();}/*** 判断用户是否存在*/@GetMapping("/exists/{userId}/{roleId}")public ResponseEntity<Boolean> existsUser(@PathVariable Long userId, @PathVariable Long roleId) {boolean exists = userService.existsUser(userId, roleId);return ResponseEntity.ok(exists);}/*** 手动清除指定用户缓存*/@DeleteMapping("/cache/{userId}/{roleId}")public ResponseEntity<Void> evictUserCache(@PathVariable Long userId, @PathVariable Long roleId) {userService.evictUserCache(userId, roleId);return ResponseEntity.ok().build();}/*** 清除所有用户缓存*/@DeleteMapping("/cache/all")public ResponseEntity<Void> evictAllUsersCache() {userService.evictAllUsersCache();return ResponseEntity.ok().build();}
}

7. 启动类

package com.example.demo;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;@SpringBootApplication
@EnableCaching // 启用缓存功能
public class SpringBootEhcacheApplication {public static void main(String[] args) {SpringApplication.run(SpringBootEhcacheApplication.class, args);System.out.println("SpringBoot Ehcache 应用启动成功!");System.out.println("访问地址:http://localhost:8080/api/users");}
}

8. 测试用例

package com.example.demo;import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.util.List;import static org.junit.jupiter.api.Assertions.*;@Slf4j
@SpringBootTest
public class UserServiceTest {@Autowiredprivate UserService userService;@Testpublic void testCRUDOperations() {// 测试新增User newUser = new User(100L, 10L, "testuser", "test@example.com", "15000150000", 1, "2024-01-10");User savedUser = userService.addUser(newUser);assertNotNull(savedUser);log.info("新增用户成功:{}", savedUser);// 测试查询(首次会查询数据库,第二次会从缓存获取)log.info("=== 第一次查询(应该查询数据库) ===");User foundUser1 = userService.getUserByCompositeKey(100L, 10L);assertNotNull(foundUser1);log.info("第一次查询结果:{}", foundUser1);log.info("=== 第二次查询(应该从缓存获取) ===");User foundUser2 = userService.getUserByCompositeKey(100L, 10L);assertNotNull(foundUser2);log.info("第二次查询结果:{}", foundUser2);// 测试更新foundUser1.setUsername("updateduser");foundUser1.setEmail("updated@example.com");User updatedUser = userService.updateUser(foundUser1);assertEquals("updateduser", updatedUser.getUsername());log.info("更新用户成功:{}", updatedUser);// 验证更新后的缓存User updatedFromCache = userService.getUserByCompositeKey(100L, 10L);assertEquals("updateduser", updatedFromCache.getUsername());log.info("从缓存获取更新后的用户:{}", updatedFromCache);// 测试删除boolean deleted = userService.deleteUser(100L, 10L);assertTrue(deleted);log.info("删除用户成功");// 验证删除后缓存也被清除User deletedUser = userService.getUserByCompositeKey(100L, 10L);assertNull(deletedUser);log.info("验证用户已被删除:{}", deletedUser);}@Testpublic void testBatchOperations() {// 测试查询所有用户log.info("=== 测试查询所有用户 ===");List<User> allUsers = userService.getAllUsers();log.info("所有用户数量:{}", allUsers.size());allUsers.forEach(user -> log.info("用户:{}", user));// 测试根据userId查询log.info("=== 测试根据userId查询 ===");List<User> usersByUserId = userService.getUsersByUserId(1L);log.info("userId=1的用户数量:{}", usersByUserId.size());usersByUserId.forEach(user -> log.info("用户:{}", user));// 测试根据roleId查询log.info("=== 测试根据roleId查询 ===");List<User> usersByRoleId = userService.getUsersByRoleId(1L);log.info("roleId=1的用户数量:{}", usersByRoleId.size());usersByRoleId.forEach(user -> log.info("用户:{}", user));}@Testpublic void testExists() {// 测试存在的用户boolean exists1 = userService.existsUser(1L, 1L);log.info("用户(1,1)是否存在:{}", exists1);assertTrue(exists1);// 测试不存在的用户boolean exists2 = userService.existsUser(999L, 999L);log.info("用户(999,999)是否存在:{}", exists2);assertFalse(exists2);}
}

9. API测试请求示例

您可以使用以下curl命令测试API:

# 1. 新增用户
curl -X POST http://localhost:8080/api/users \-H "Content-Type: application/json" \-d '{"userId": 100,"roleId": 10,"username": "newuser","email": "newuser@example.com","phone": "15000150000","status": 1,"createTime": "2024-01-10"}'# 2. 根据复合key查询用户
curl http://localhost:8080/api/users/100/10# 3. 查询所有用户
curl http://localhost:8080/api/users# 4. 根据userId查询用户列表
curl http://localhost:8080/api/users/userId/1# 5. 根据roleId查询用户列表
curl http://localhost:8080/api/users/roleId/1# 6. 更新用户
curl -X PUT http://localhost:8080/api/users \-H "Content-Type: application/json" \-d '{"userId": 100,"roleId": 10,"username": "updateduser","email": "updated@example.com","phone": "15000150000","status": 1,"createTime": "2024-01-10"}'# 7. 删除用户
curl -X DELETE http://localhost:8080/api/users/100/10# 8. 判断用户是否存在
curl http://localhost:8080/api/users/exists/1/1# 9. 手动清除指定用户缓存
curl -X DELETE http://localhost:8080/api/users/cache/1/1# 10. 清除所有用户缓存
curl -X DELETE http://localhost:8080/api/users/cache/all

10. 关键特性说明

  1. 复合键缓存:使用userId + "_" + roleId作为缓存key,确保唯一性
  2. 缓存策略
    • @Cacheable:查询时先查缓存,缓存不存在再查数据库
    • @CacheEvict:增删改操作时清除相关缓存
    • @Caching:一个方法操作多个缓存
  3. 线程安全:使用ConcurrentHashMap模拟数据库,保证线程安全
  4. 缓存过期:Ehcache配置中设置了TTL过期时间
  5. 缓存穿透保护:使用unless = "#result == null"避免缓存null值

这个示例完整展示了SpringBoot整合Ehcache的增删改查操作,使用ConcurrentHashMap作为数据存储,以userId和roleId作为复合键进行缓存管理。

http://www.dtcms.com/a/410902.html

相关文章:

  • 结合 SSH 22 + 2222 备用端口 + 临时保护 + 长期守护 + 防火墙 的终极一行命令版本
  • 使用虚幻引擎时间轴制作一个弹跳小球
  • 网站推广和精准seo深圳网站设计兴田德润i简介
  • 从比分到直播流畅度:API 在体育观赛中的关键作用
  • JavaScript又忘了,忘了?太正常了!忘了?太正常了!重新上路:
  • 全新一代北斗三号短报文通信SoC芯片在北斗规模应用国际峰会发布
  • 佛山做企业网站的公司专业设计网站有哪些
  • 户用储能微型逆变器计量电表防逆流
  • 通过手动安装本地部署live-torrent (影视搜索,云播客户端)
  • 学做立体书的网站网站怎么做gps定位
  • 【实时Linux实战系列】实时系统的现场变更与灰度发布
  • 做个简单网站大概多少钱it培训机构排名北京
  • Spring Boot 自动配置之 TaskScheduler
  • .NET Framework 3.5官网下载与5种常见故障解决方法
  • nginx的访问控制、用户认证、https
  • 网站建设完整网站如何做图片特效
  • 服装类跟单系统:提升供应链管理效率的利器
  • 基于微信小程序的旅游景点系统【2026最新】
  • 网站建设升级网站开发项目架构
  • JxBrowser 7.44.0 版本发布啦!
  • Python 高效将 PDF 转换为 HTML 的实用指南
  • Ubuntu 24.04 LTS 安装GAMIT
  • 路由器设置网站做羞羞的事网站
  • 网站定制合同慈溪公司做网站
  • 单细胞神经元可视化-- HarmonyOS Next
  • 深入理解 Highcharts Stock:为金融 / 时间序列可视化量身打造
  • 分布式专题——22 Kafka集群工作机制详解
  • 专业建站公司收费标准合肥市网站建设 小程序
  • TimescaleDB 按多个维度(列)进行压缩
  • Nacos敏感信息加密