Java数据查询流程如何规范

wen java案例 35

本文目录导读:

Java数据查询流程如何规范

  1. 核心原则
  2. 标准查询流程清单
  3. 各层详细规范
  4. 关键规范节点
  5. 性能规范
  6. 代码示例(完整流程)
  7. 总结:一个规范的查询流程必须做到:

在Java开发中,规范的数据查询流程是保证代码可维护性、性能和安全性的关键,以下是一个从架构到编码的标准化流程规范:

核心原则

  1. 分层隔离:严格遵循 Controller -> Service -> DAO(Repository) -> DataSource 的分层,禁止跨层调用
  2. 最小化查询:只查询需要的字段,避免 SELECT *
  3. 安全第一:防止 SQL 注入,敏感数据脱敏。
  4. 资源释放:确保连接、流、Session 等资源的正确释放。

标准查询流程清单

graph TD
    A[客户端请求] --> B{Controller层}
    B --> C[参数校验 & 封装]
    C --> D[调用 Service 层]
    D --> E{Service 层}
    E -- 业务逻辑 & 缓存判断 --> F[调用 DAO 层]
    F --> G{DAO/Repository 层}
    G --> H[ORM 映射 / SQL 执行]
    H --> I[(数据库)]
    I --> H
    H --> J[结果集映射]
    J --> K[返回至 Service]
    K --> L[数据装配 / 缓存回写]
    L --> M[返回至 Controller]
    M --> N[视图/JSON序列化输出]

各层详细规范

Controller 层(唯一使命:接收与响应)

  • 职责:参数解析、格式验证、调用 Service、返回结果。
  • 禁止:包含任何业务逻辑、SQL 拼接、直接调用 DAO。
  • 规范代码示例
@RestController
@RequestMapping("/api/users")
public class UserController {
    @Autowired
    private UserService userService;
    @GetMapping("/{id}")
    public Result<UserVO> getUser(@PathVariable Long id) {
        // 1. 基础参数校验
        if (id == null || id <= 0) {
            return Result.fail("Invalid user ID");
        }
        // 2. 调用 Service
        UserVO userVO = userService.getUserById(id);
        // 3. 响应处理
        return Result.success(userVO);
    }
}

Service 层(业务核心,负责编排)

  • 职责:业务规则、事务控制、缓存策略、数据装配。
  • 规范
    • 使用 @Transactional 管理事务(读写分离时注意只读事务 readOnly=true)。
    • 大量数据查询时考虑分库分表或缓存(Redis)。
    • 组装多个 DAO 查询结果(数据聚合)。
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;
    @Autowired
    private OrderDao orderDao;
    @Autowired
    private RedisTemplate redisTemplate;
    @Override
    @Transactional(readOnly = true)
    public UserVO getUserById(Long id) {
        // 1. 缓存穿透保护:先查缓存
        String cacheKey = "user:" + id;
        UserVO cacheUser = (UserVO) redisTemplate.opsForValue().get(cacheKey);
        if (cacheUser != null) {
            return cacheUser;
        }
        // 2. 数据库查询
        UserDO userDO = userDao.selectById(id);
        if (userDO == null) {
            throw new BusinessException("User not found");
        }
        // 3. 业务规则校验
        if (userDO.getStatus() == 0) {
            throw new BusinessException("User has been disabled");
        }
        // 4. 数据装配(映射为 VO)
        UserVO userVO = UserConvert.INSTANCE.toVO(userDO);
        // 5. 回写缓存
        redisTemplate.opsForValue().set(cacheKey, userVO, 30, TimeUnit.MINUTES);
        return userVO;
    }
}

DAO/Repository 层(数据库读写)

  • 职责:执行 SQL/ORM 查询,映射结果。
  • 规范
    • 返回类型:原则只有 DO(数据库对象)或 List<DO>
    • 命名规则insert, insertBatch, selectById, selectListByXXX, updateById, deleteById
    • 分页:统一使用分页对象(如 Mybatis-Plus 的 Page),避免手动 LIMIT
// 使用 MyBatis-Plus 规范
@Repository
public interface UserDao extends BaseMapper<UserDO> {
    // 自定义查询
    @Select("SELECT id, name, email FROM user WHERE status = #{status} AND create_time > #{startTime}")
    List<UserDO> selectActiveUsers(@Param("status") Integer status,
                                   @Param("startTime") LocalDateTime startTime);
    // 分页查询
    IPage<UserDO> selectPageByCondition(Page<UserDO> page, @Param("condition") UserQueryDTO condition);
}

关键规范节点

参数传递规范

  • 原则禁止使用 Map 作为 DAO 或 Service 入参,必须封装为具体 DTO 或 Query 对象。

  • 优点

    • 编译期类型检查,避免运行时 ClassCastException
    • 方便参数校验注解(如 @NotNull, @Size)。
    • 代码可读性高。
  • 反例

    // 避免
    public List<UserVO> queryUsers(Map<String, Object> params);
    // 推荐
    public List<UserVO> queryUsers(UserQueryDTO query);

结果映射与脱敏

  • DO:数据库表直接映射对象(POJO + @TableName, @TableField)。
  • VO:前端视图对象,只包含需要展示的字段,且必须脱敏(如手机号 138****1234)。
  • DTO:不同服务间数据传输。
  • 转换工具:统一使用 MapStructBeanCopier(如 Spring BeanUtils),禁止手动 get/set
// 脱敏枚举
public enum SensitiveType {
    PHONE,
    EMAIL,
    ID_CARD
}
// 工具类
public String desensitize(String value, SensitiveType type) {
    // 根据类型脱敏...
}

SQL 规范

  • *禁止 `SELECT **:明确指定所需字段,列变更时SELECT *` 会返回脏数据或导致内存浪费。
  • 使用 LIMIT 限制结果集:防止大数据量 OOM(OutOfMemoryError)。
  • JOIN 数量控制:单表查询优先,超过 3 张表 JOIN 考虑冗余字段或 ES 搜索引擎。
  • 参数化查询:永远使用 #{value}(MyBatis)或 (JDBC),禁止 拼接
// 正确
@Select("SELECT id, name FROM user WHERE name = #{name}")
List<UserDO> findByName(@Param("name") String name);
// 错误(SQL注入风险)
@Select("SELECT id, name FROM user WHERE name = '${name}'")
List<UserDO> findByName(@Param("name") String name);

异常处理

  • DAO 层:不捕获异常,向上抛出运行时异常(如 DataAccessException)。
  • Service 层:捕获 DAO 异常,转换为业务异常(BusinessException)。
  • 全局拦截@RestControllerAdvice 统一处理,返回友好错误码。
@ExceptionHandler(BusinessException.class)
public Result<?> handleBusinessException(BusinessException e) {
    log.warn("Business error: {}", e.getMessage());
    return Result.fail(e.getCode(), e.getMessage());
}

性能规范

  1. N+1 查询问题:使用 IN 查询嵌套循环优化 解决,查询订单列表时,不要在循环里逐条查用户信息,而是先用 IN 查出所有用户再组装。
  2. 分压与限流:非核心查询使用 @Async 异步?错!查询不要轻易异步,不仅增加复杂度,还可能造成线程池膨胀,应做限流保护(如 Sentinel)。
  3. 超时设置:重要查询设置 StatementTimeout,防止慢 SQL 拖垮数据库。
    // MyBatis 全局
    spring.datasource.hikari.connection-timeout=30000
    spring.datasource.hikari.validation-timeout=5000

代码示例(完整流程)

Controller -> Service -> DAO -> DB 并返回脱敏结果

// 1. Controller
@GetMapping("/list")
public Result<PageResult<UserVO>> listUsers(@Valid UserQueryDTO query) {
    PageResult<UserVO> result = userService.pageQueryUsers(query);
    return Result.success(result);
}
// 2. Service
@Service
public class UserServiceImpl implements UserService {
    @Override
    public PageResult<UserVO> pageQueryUsers(UserQueryDTO query) {
        // 构建分页参数
        Page<UserDO> page = new Page<>(query.getPageNum(), query.getPageSize());
        // 执行查询(条件由 Mybatis-Plus 的 QueryWrapper 或 XML 处理)
        IPage<UserDO> userPage = userDao.selectPage(page, query);
        // 转换并脱敏
        List<UserVO> voList = userPage.getRecords().stream()
            .map(user -> {
                UserVO vo = UserConvert.INSTANCE.toVO(user);
                if (StringUtils.isNotBlank(vo.getPhone())) {
                    vo.setPhone(SensitiveUtil.maskPhone(vo.getPhone()));
                }
                return vo;
            })
            .collect(Collectors.toList());
        // 封装为统一分页返回对象
        return new PageResult<>(voList, userPage.getTotal(), userPage.getCurrent(), userPage.getSize());
    }
}
// 3. DAO
@Repository
public interface UserDao extends BaseMapper<UserDO> {
    // 继承 BaseMapper 自动拥有 CRUD
    IPage<UserDO> selectPage(Page<UserDO> page, @Param("query") UserQueryDTO query);
}

一个规范的查询流程必须做到:

  1. 分层清晰(Controller-Service-DAO)
  2. 参数明确(DTO/Query,不使用 Map)
  3. 安全(参数化查询,SQL注入防御)
  4. 结果精确(返回 VO 且脱敏)
  5. 性能可控(分页,避免 N+1,设置超时)
  6. 异常友好(统一业务异常处理)

坚持这套流程,可以有效减少线上故障并提高团队协作效率。

抱歉,评论功能暂时关闭!