本文目录导读:

在Java开发中,规范的数据查询流程是保证代码可维护性、性能和安全性的关键,以下是一个从架构到编码的标准化流程规范:
核心原则
- 分层隔离:严格遵循 Controller -> Service -> DAO(Repository) -> DataSource 的分层,禁止跨层调用。
- 最小化查询:只查询需要的字段,避免
SELECT *。 - 安全第一:防止 SQL 注入,敏感数据脱敏。
- 资源释放:确保连接、流、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:不同服务间数据传输。
- 转换工具:统一使用
MapStruct、BeanCopier(如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());
}
性能规范
- N+1 查询问题:使用
IN查询 或 嵌套循环优化 解决,查询订单列表时,不要在循环里逐条查用户信息,而是先用IN查出所有用户再组装。 - 分压与限流:非核心查询使用
@Async异步?错!查询不要轻易异步,不仅增加复杂度,还可能造成线程池膨胀,应做限流保护(如 Sentinel)。 - 超时设置:重要查询设置
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);
}
一个规范的查询流程必须做到:
- 分层清晰(Controller-Service-DAO)
- 参数明确(DTO/Query,不使用 Map)
- 安全(参数化查询,SQL注入防御)
- 结果精确(返回 VO 且脱敏)
- 性能可控(分页,避免 N+1,设置超时)
- 异常友好(统一业务异常处理)
坚持这套流程,可以有效减少线上故障并提高团队协作效率。