# 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作为复合键进行缓存管理。