package com.example.usermanagement.service.impl;
import com.example.usermanagement.entity.User;
import com.example.usermanagement.mapper.UserMapper;
import com.example.usermanagement.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* 用户业务实现类
*/
@Service
@Transactional
public class UserServiceImpl implements UserService {
// 手动创建日志对象
private static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class);
@Autowired
private UserMapper userMapper;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// Redis键前缀
private static final String USER_KEY_PREFIX = "user:";
// 缓存过期时间(小时)
private static final long CACHE_EXPIRE_HOURS = 2;
@Override
public User createUser(User user) {
// 检查用户名是否已存在
User existUser = userMapper.selectByUsername(user.getUsername());
if (existUser != null) {
throw new RuntimeException("用户名已存在");
}
// 设置默认状态
if (user.getStatus() == null) {
user.setStatus(1);
}
// 插入数据库
userMapper.insert(user);
// 缓存到Redis
String key = USER_KEY_PREFIX + user.getId();
redisTemplate.opsForValue().set(key, user, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
log.info("创建用户成功:{}", user.getUsername());
return user;
}
@Override
public boolean deleteUser(Long id) {
// 先查询用户是否存在
User user = getUserById(id);
if (user == null) {
return false;
}
// 从数据库删除
int result = userMapper.deleteById(id);
// 从Redis删除
String key = USER_KEY_PREFIX + id;
redisTemplate.delete(key);
log.info("删除用户成功:ID={}", id);
return result > 0;
}
@Override
public User updateUser(User user) {
// 检查用户是否存在
User existUser = getUserById(user.getId());
if (existUser == null) {
throw new RuntimeException("用户不存在");
}
// 如果修改了用户名,检查新用户名是否已被使用
if (user.getUsername() != null && !user.getUsername().equals(existUser.getUsername())) {
User checkUser = userMapper.selectByUsername(user.getUsername());
if (checkUser != null) {
throw new RuntimeException("用户名已被使用");
}
}
// 更新数据库
userMapper.update(user);
// 更新缓存(先删除,下次查询时再缓存)
String key = USER_KEY_PREFIX + user.getId();
redisTemplate.delete(key);
log.info("更新用户成功:ID={}", user.getId());
return getUserById(user.getId());
}
@Override
public User getUserById(Long id) {
String key = USER_KEY_PREFIX + id;
// 先尝试从Redis查询(加异常处理)
try {
// 先从Redis查询
User user = (User) redisTemplate.opsForValue().get(key);
if (user != null) {
log.debug("从Redis获取用户:ID={}", id);
return user;
}
}
catch (Exception e) {
log.warn("Redis查询失败,降级到数据库查询:{}", e.getMessage());
}
// Redis没有,从数据库查询
User user = userMapper.selectById(id);
if (user != null) {
// 尝试存入Redis(加异常处理)
try {
// 存入Redis
redisTemplate.opsForValue().set(key, user, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
log.debug("从数据库获取用户并缓存:ID={}", id);
} catch (Exception e) {
log.warn("Redis存储失败,但数据查询正常:{}", e.getMessage());
}
}
return user;
}
@Override
public User getUserByUsername(String username) {
return userMapper.selectByUsername(username);
}
@Override
public List<User> getAllUsers() {
return userMapper.selectAll();
}
@Override
public List<User> getUsersByPage(Integer pageNum, Integer pageSize) {
// 计算偏移量
Integer offset = (pageNum - 1) * pageSize;
return userMapper.selectByPage(offset, pageSize);
}
@Override
public int getTotalCount() {
return userMapper.count();
}
@Override
public User getUserByEmail(String email) {
// 参数校验
if (email == null || email.trim().isEmpty()) {
log.warn("邮箱参数为空");
return null;
}
// 直接查询数据库(邮箱查询频率较低,不使用缓存)
User user = userMapper.selectByEmail(email.trim());
if (user != null) {
log.info("根据邮箱查询用户成功:{}", email);
} else {
log.info("未找到邮箱对应的用户:{}", email);
}
return user;
}
@Override
public List<User> searchUsersByUsername(String username, Integer pageNum, Integer pageSize) {
int offset = (pageNum - 1) * pageSize;
return userMapper.searchByUsername(username, offset, pageSize);
}
@Override
public int getSearchTotalCount(String username) {
return userMapper.countByUsername(username);
}
}
package com.example.usermanagement.service;
import com.example.usermanagement.entity.User;
import java.util.List;
/**
* 用户业务接口
*/
public interface UserService {
// 创建用户
User createUser(User user);
// 删除用户
boolean deleteUser(Long id);
// 更新用户
User updateUser(User user);
// 根据ID获取用户
User getUserById(Long id);
// 根据用户名获取用户
User getUserByUsername(String username);
// 获取所有用户
List<User> getAllUsers();
// 分页获取用户
List<User> getUsersByPage(Integer pageNum, Integer pageSize);
// 获取用户总数
int getTotalCount();
/**
* 根据邮箱查询用户
* @param email 邮箱地址
* @return 用户信息,如果不存在返回null
*/
User getUserByEmail(String email);
// 根据用户名搜索用户(分页)
List<User> searchUsersByUsername(String username, Integer pageNum, Integer pageSize);
// 获取搜索结果总数
int getSearchTotalCount(String username);
}
1. 接口设计分析(UserService)
1.1 接口设计原则
public interface UserService {
// 基础CRUD操作
User createUser(User user);
boolean deleteUser(Long id);
User updateUser(User user);
User getUserById(Long id);
// 扩展查询功能
User getUserByUsername(String username);
User getUserByEmail(String email);
List<User> getAllUsers();
// 分页与搜索
List<User> getUsersByPage(Integer pageNum, Integer pageSize);
List<User> searchUsersByUsername(String username, Integer pageNum, Integer pageSize);
int getTotalCount();
int getSearchTotalCount(String username);
}
设计优势:
- 单一职责:专注用户业务逻辑
- 方法命名规范:动词+名词的清晰命名
- 返回类型明确:便于调用者处理
- 参数设计合理:支持不同查询需求
2. 实现类架构分析
2.1 类声明与注解
@Service
@Transactional
public class UserServiceImpl implements UserService
@Service 深度解析:
- Spring组件:标识为业务层组件
- 自动扫描:Spring自动发现并注册为Bean
- 依赖注入:可被其他组件注入使用
@Transactional 事务管理:
- 类级别事务:所有public方法自动开启事务
- ACID保证:确保数据一致性
- 回滚机制:异常时自动回滚
2.2 依赖注入设计
@Autowired
private UserMapper userMapper;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
架构分层:
Controller → Service → Mapper → Database
↓
Redis Cache
依赖关系:
- UserMapper:数据持久化操作
- RedisTemplate:缓存管理
- 解耦设计:通过接口依赖,便于测试和扩展
2.3 常量配置
private static final String USER_KEY_PREFIX = "user:";
private static final long CACHE_EXPIRE_HOURS = 2;
配置优势:
- 集中管理:便于统一修改
- 命名规范:Redis key有统一前缀
- 过期策略:防止缓存堆积
3. 核心业务方法深度解析
3.1 创建用户方法
@Override
public User createUser(User user) {
// 1. 业务规则验证
User existUser = userMapper.selectByUsername(user.getUsername());
if (existUser != null) {
throw new RuntimeException("用户名已存在");
}
// 2. 数据完整性处理
if (user.getStatus() == null) {
user.setStatus(1); // 默认启用状态
}
// 3. 数据持久化
userMapper.insert(user);
// 4. 缓存同步
String key = USER_KEY_PREFIX + user.getId();
redisTemplate.opsForValue().set(key, user, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
// 5. 日志记录
log.info("创建用户成功:{}", user.getUsername());
return user;
}
业务流程分析:
第一步:业务规则验证
- 唯一性检查:确保用户名不重复
- 失败快速返回:及早发现问题,避免无效操作
第二步:数据完整性
- 默认值设置:确保必要字段有合理默认值
- 数据标准化:统一数据格式
第三步:数据持久化
- MyBatis操作:insert方法自动生成ID
- 事务保护:@Transactional确保原子性
第四步:缓存同步
- 写入缓存:新数据立即可用
- 过期时间:防止缓存无限增长
- 预热策略:避免缓存穿透
3.2 删除用户方法
@Override
public boolean deleteUser(Long id) {
// 1. 存在性检查
User user = getUserById(id);
if (user == null) {
return false;
}
// 2. 数据库删除
int result = userMapper.deleteById(id);
// 3. 缓存清理
String key = USER_KEY_PREFIX + id;
redisTemplate.delete(key);
log.info("删除用户成功:ID={}", id);
return result > 0;
}
缓存一致性策略:
- Cache-Aside模式:先删数据库,再删缓存
- 最终一致性:确保数据库和缓存数据同步
- 失败容错:即使缓存删除失败,数据库操作已完成
3.3 更新用户方法(核心逻辑)
@Override
public User updateUser(User user) {
// 1. 存在性验证
User existUser = getUserById(user.getId());
if (existUser == null) {
throw new RuntimeException("用户不存在");
}
// 2. 业务规则检查
if (user.getUsername() != null && !user.getUsername().equals(existUser.getUsername())) {
User checkUser = userMapper.selectByUsername(user.getUsername());
if (checkUser != null) {
throw new RuntimeException("用户名已被使用");
}
}
// 3. 数据更新
userMapper.update(user);
// 4. 缓存失效策略
String key = USER_KEY_PREFIX + user.getId();
redisTemplate.delete(key);
log.info("更新用户成功:ID={}", user.getId());
// 5. 返回最新数据
return getUserById(user.getId());
}
业务逻辑亮点:
用户名唯一性检查:
if (user.getUsername() != null && !user.getUsername().equals(existUser.getUsername()))
- 智能检查:只在用户名发生变化时才检查重复
- 性能优化:避免不必要的数据库查询
缓存更新策略:
- 删除缓存:而不是更新缓存
- 懒加载:下次查询时重新缓存
- 避免脏数据:确保缓存数据的准确性
3.4 查询用户方法(缓存核心)
@Override
public User getUserById(Long id) {
String key = USER_KEY_PREFIX + id;
// 1. 缓存查询(容错处理)
try {
User user = (User) redisTemplate.opsForValue().get(key);
if (user != null) {
log.debug("从Redis获取用户:ID={}", id);
return user;
}
} catch (Exception e) {
log.warn("Redis查询失败,降级到数据库查询:{}", e.getMessage());
}
// 2. 数据库查询
User user = userMapper.selectById(id);
if (user != null) {
// 3. 缓存回写(容错处理)
try {
redisTemplate.opsForValue().set(key, user, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
log.debug("从数据库获取用户并缓存:ID={}", id);
} catch (Exception e) {
log.warn("Redis存储失败,但数据查询正常:{}", e.getMessage());
}
}
return user;
}
缓存策略详解:
Cache-Aside模式:
1. 应用查询缓存
2. 缓存命中 → 返回数据
3. 缓存未命中 → 查询数据库
4. 将数据写入缓存
5. 返回数据
容错机制:
- Redis异常不影响业务:降级到数据库查询
- 缓存写入失败容忍:不影响数据读取
- 服务高可用:即使Redis宕机,服务仍可用
性能分析:
- 缓存命中:响应时间 < 10ms
- 缓存未命中:响应时间 50-100ms
- 缓存失效:不影响数据准确性
4. 分页查询实现
4.1 基础分页
@Override
public List<User> getUsersByPage(Integer pageNum, Integer pageSize) {
Integer offset = (pageNum - 1) * pageSize;
return userMapper.selectByPage(offset, pageSize);
}
分页计算:
- offset = (pageNum - 1) × pageSize
- 页码从1开始:符合用户习惯
- MySQL LIMIT语法:
LIMIT offset, pageSize
4.2 搜索分页
@Override
public List<User> searchUsersByUsername(String username, Integer pageNum, Integer pageSize) {
int offset = (pageNum - 1) * pageSize;
return userMapper.searchByUsername(username, offset, pageSize);
}
搜索策略:
- 模糊匹配:使用LIKE查询
- 分页支持:大数据量下的性能保证
- 索引优化:username字段建议加索引
5. 参数验证与异常处理
5.1 邮箱查询的参数验证
@Override
public User getUserByEmail(String email) {
// 参数校验
if (email == null || email.trim().isEmpty()) {
log.warn("邮箱参数为空");
return null;
}
// 数据标准化
User user = userMapper.selectByEmail(email.trim());
// 结果日志
if (user != null) {
log.info("根据邮箱查询用户成功:{}", email);
} else {
log.info("未找到邮箱对应的用户:{}", email);
}
return user;
}
验证策略:
- 空值检查:防止空指针异常
- 数据清洗:trim()去除首尾空格
- 日志记录:便于业务分析和问题排查
6. 缓存设计深度分析
6.1 缓存Key设计
private static final String USER_KEY_PREFIX = "user:";
String key = USER_KEY_PREFIX + user.getId();
Key设计原则:
- 命名空间:
user:
前缀避免冲突 - 唯一标识:使用主键ID确保唯一性
- 可读性:一看就知道是用户缓存
实际存储示例:
Redis Key: "user:1"
Redis Value: {"id":1,"username":"admin","email":"admin@example.com",...}
6.2 缓存生命周期
redisTemplate.opsForValue().set(key, user, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
生命周期管理:
- TTL设置:2小时自动过期
- 内存控制:防止缓存无限增长
- 数据新鲜度:定期更新确保数据时效性
6.3 缓存一致性保证
写操作流程:
1. 更新数据库
2. 删除缓存(而不是更新缓存)
3. 下次读取时重新缓存
为什么删除而不是更新?
- 避免并发问题:多线程更新缓存可能导致数据不一致
- 简化逻辑:删除操作更简单可靠
- 懒加载:按需加载,提高效率
7. 日志记录策略
7.1 日志级别使用
log.info("创建用户成功:{}", user.getUsername()); // 业务操作
log.debug("从Redis获取用户:ID={}", id); // 调试信息
log.warn("Redis查询失败,降级到数据库查询:{}", e.getMessage()); // 警告
日志分级:
- info:重要业务操作
- debug:详细调试信息
- warn:异常但不影响业务
- error:严重错误(代码中未使用,但应该有)
7.2 参数化日志
log.info("创建用户成功:{}", user.getUsername());
- 性能优化:避免字符串拼接
- 安全性:防止日志注入攻击
- 可读性:统一的日志格式
8. 事务管理分析
8.1 事务边界
@Transactional
public class UserServiceImpl implements UserService
事务范围:
- 类级别:所有public方法都在事务中
- 方法级别:可以在方法上单独配置
- 嵌套事务:方法间调用会合并事务
8.2 事务配置建议
// 只读事务优化
@Transactional(readOnly = true)
public User getUserById(Long id) {
// 查询操作
}
// 自定义事务配置
@Transactional(rollbackFor = Exception.class, timeout = 30)
public User createUser(User user) {
// 写操作
}
9. 性能优化要点
9.1 缓存命中率优化
// 缓存预热
public void warmUpCache() {
List<User> hotUsers = userMapper.selectHotUsers();
for (User user : hotUsers) {
String key = USER_KEY_PREFIX + user.getId();
redisTemplate.opsForValue().set(key, user, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
}
}
9.2 批量操作优化
// 批量查询
public List<User> getUsersByIds(List<Long> ids) {
// 1. 批量查询缓存
List<String> keys = ids.stream()
.map(id -> USER_KEY_PREFIX + id)
.collect(Collectors.toList());
List<Object> cachedUsers = redisTemplate.opsForValue().multiGet(keys);
// 2. 处理缓存未命中的数据
// 3. 批量查询数据库
// 4. 批量写入缓存
}
10. 错误处理和改进建议
10.1 异常处理改进
// 当前实现
throw new RuntimeException("用户名已存在");
// 建议改进
throw new BusinessException(ErrorCode.USERNAME_EXISTS, "用户名已存在");
10.2 参数验证增强
@Override
public User createUser(@Valid User user) {
// Bean Validation自动验证
// 减少手动检查代码
}
10.3 缓存策略优化
// 布隆过滤器防止缓存穿透
@Autowired
private BloomFilter<String> userBloomFilter;
public User getUserById(Long id) {
if (!userBloomFilter.mightContain("user:" + id)) {
return null; // 确定不存在
}
// 正常缓存逻辑
}
- 分层清晰:职责单一,接口明确
- 缓存策略:提升查询性能
- 事务管理:保证数据一致性
- 异常处理:提供用户友好的错误信息
- 日志记录:便于监控和调试
- 容错机制:Redis故障不影响业务
缓存加速:热点数据毫秒级响应
分页支持:大数据量处理能力
连接池:数据库连接复用
事务优化:批量操作支持
接口设计:易于扩展新功能
缓存抽象:支持多种缓存实现
配置外化:便于环境切换
监控友好:丰富的日志信息