Service层业务逻辑

发布于:2025-08-13 ⋅ 阅读:(14) ⋅ 点赞:(0)
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; // 确定不存在
    }
    // 正常缓存逻辑
}
  1. 分层清晰:职责单一,接口明确
  2. 缓存策略:提升查询性能
  3. 事务管理:保证数据一致性
  4. 异常处理:提供用户友好的错误信息
  5. 日志记录:便于监控和调试
  6. 容错机制:Redis故障不影响业务
  • 缓存加速:热点数据毫秒级响应

  • 分页支持:大数据量处理能力

  • 连接池:数据库连接复用

  • 事务优化:批量操作支持

  • 接口设计:易于扩展新功能

  • 缓存抽象:支持多种缓存实现

  • 配置外化:便于环境切换

  • 监控友好:丰富的日志信息


网站公告

今日签到

点亮在社区的每一天
去签到