# springboot_redis_ehcache **Repository Path**: enzoism/springboot_redis_ehcache ## Basic Information - **Project Name**: springboot_redis_ehcache - **Description**: 使用Redis替换Ehcache进行数据缓存 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-09-25 - **Last Updated**: 2025-09-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Redis替换Ehcache进行缓存操作 ## 1. 添加依赖 ```xml org.springframework.boot spring-boot-starter-cache org.ehcache ehcache org.springframework.boot spring-boot-starter-web org.projectlombok lombok true ``` ## 2. Ehcache配置 ```yaml # application.yml spring: cache: type: ehcache ehcache: config: classpath:ehcache.xml server: port: 8080 ``` ```xml java.lang.String com.example.demo.entity.User 10 1000 java.lang.String java.util.List 5 100 ``` ## 3. 实体类 ```java 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模拟数据库) ```java 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 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 selectAll() { List users = new ArrayList<>(database.values()); log.info("查询所有用户,共{}条记录", users.size()); return users; } /** * 根据userId查询用户列表(一个userId可能有多个roleId) */ public List selectByUserId(Long userId) { List 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 selectByRoleId(Long roleId) { List 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. 服务层(带缓存逻辑) ```java 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 { @Autowired private 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 getAllUsers() { log.info("缓存未命中,查询数据库获取所有用户"); return userDao.selectAll(); } /** * 根据userId查询用户列表 */ @Cacheable(key = "'userId_' + #userId", unless = "#result == null || #result.isEmpty()") public List getUsersByUserId(Long userId) { log.info("缓存未命中,查询数据库获取用户列表:userId={}", userId); return userDao.selectByUserId(userId); } /** * 根据roleId查询用户列表 */ @Cacheable(key = "'roleId_' + #roleId", unless = "#result == null || #result.isEmpty()") public List 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. 控制器层 ```java 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 { @Autowired private UserService userService; /** * 新增用户 */ @PostMapping public ResponseEntity 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 getUserByCompositeKey( @PathVariable Long userId, @PathVariable Long roleId) { User user = userService.getUserByCompositeKey(userId, roleId); return user != null ? ResponseEntity.ok(user) : ResponseEntity.notFound().build(); } /** * 查询所有用户 */ @GetMapping public ResponseEntity> getAllUsers() { List users = userService.getAllUsers(); return ResponseEntity.ok(users); } /** * 根据userId查询用户列表 */ @GetMapping("/userId/{userId}") public ResponseEntity> getUsersByUserId(@PathVariable Long userId) { List users = userService.getUsersByUserId(userId); return ResponseEntity.ok(users); } /** * 根据roleId查询用户列表 */ @GetMapping("/roleId/{roleId}") public ResponseEntity> getUsersByRoleId(@PathVariable Long roleId) { List users = userService.getUsersByRoleId(roleId); return ResponseEntity.ok(users); } /** * 更新用户 */ @PutMapping public ResponseEntity 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 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 existsUser( @PathVariable Long userId, @PathVariable Long roleId) { boolean exists = userService.existsUser(userId, roleId); return ResponseEntity.ok(exists); } /** * 手动清除指定用户缓存 */ @DeleteMapping("/cache/{userId}/{roleId}") public ResponseEntity evictUserCache( @PathVariable Long userId, @PathVariable Long roleId) { userService.evictUserCache(userId, roleId); return ResponseEntity.ok().build(); } /** * 清除所有用户缓存 */ @DeleteMapping("/cache/all") public ResponseEntity evictAllUsersCache() { userService.evictAllUsersCache(); return ResponseEntity.ok().build(); } } ``` ## 7. 启动类 ```java 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. 测试用例 ```java 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 { @Autowired private UserService userService; @Test public 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); } @Test public void testBatchOperations() { // 测试查询所有用户 log.info("=== 测试查询所有用户 ==="); List allUsers = userService.getAllUsers(); log.info("所有用户数量:{}", allUsers.size()); allUsers.forEach(user -> log.info("用户:{}", user)); // 测试根据userId查询 log.info("=== 测试根据userId查询 ==="); List usersByUserId = userService.getUsersByUserId(1L); log.info("userId=1的用户数量:{}", usersByUserId.size()); usersByUserId.forEach(user -> log.info("用户:{}", user)); // 测试根据roleId查询 log.info("=== 测试根据roleId查询 ==="); List usersByRoleId = userService.getUsersByRoleId(1L); log.info("roleId=1的用户数量:{}", usersByRoleId.size()); usersByRoleId.forEach(user -> log.info("用户:{}", user)); } @Test public 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: ```bash # 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作为复合键进行缓存管理。