You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java Web中DAO嵌套、实体跨引用架构问题及优化方案

问题背景

我正在开发采用MVC架构(JSP+SERVLETS+MYSQL,未使用任何ORM框架)的Java Web应用,希望通过JSTL核心标签库在JSP视图层渲染如下结构的预约信息表格:

ReceptionIDServiceNameReceptionCostYourMasterStatusDateTime
1Haircut20 USDPeter AndersonNew17.06.2022 10:00

基于该需求我设计了对应的Entity与DAO架构,以下为对应数据库Reception表的实体类定义:

public class Reception extends BaseEntity {
    private Service service;
    private User master;
    private User client;
    private int cost;
    private LocalDateTime receptionDateTime;
    private Status status;
    // 省略getter、setter方法
}

目前我存在架构层面的顾虑:Reception实体类中持有Service、User等其他实体类的引用,该设计导致我需要在一个DAO内部调用其他DAO的方法,相关实现代码如下:

public class ReceptionDAO implements GenericDAO<Reception> {

    public Reception mapReception(ResultSet resultSet) throws SQLException, DaoException {
        Reception reception = new Reception();

        long receptionId = resultSet.getLong("id");
        long serviceId = resultSet.getLong("service_id");
        long masterId = resultSet.getLong("master_id");
        long clientId = resultSet.getLong("client_id");
        int cost = resultSet.getInt("cost");
        LocalDateTime dateTime = resultSet.getTimestamp("reception_date_time").toLocalDateTime();
        int statusId = resultSet.getInt("status_id");

        UserDAO userDAO = new UserDAO();
        ServiceDAO serviceDAO = new ServiceDAO();

        reception.setId(receptionId);
        reception.setService(serviceDAO.findById(serviceId));
        reception.setMaster(userDAO.findById(masterId));
        reception.setClient(userDAO.findById(clientId));
        reception.setCost(cost);
        reception.setReceptionDateTime(dateTime);
        reception.setStatus(Status.getStatus(statusId));

        return reception;
    }
}
待解答疑问
  • 在一个实体类中持有其他实体对象的引用是否属于良好开发实践?
  • 在一个DAO内部嵌套调用其他DAO是否合理,是否属于不良架构实践?
  • 如果上述实现存在较严重的架构问题,应当采用什么方案进行优化解决?
问题解答

1. 实体持有其他实体引用的合理性

这是完全符合面向对象设计的常规实践,本质是领域模型的关联关系映射,不存在设计问题。页面需要同时展示预约关联的服务名、技师姓名,本来就需要获取关联实体的属性,在Reception里重复存储serviceName、masterName这类冗余字段反而违反数据库范式,后续数据更新很容易出现不一致问题。
唯一需要注意的是不要做无限制的深度嵌套,比如不要在Service里反向持有一堆关联的Reception、Reception里又嵌套其他关联对象形成循环引用,当前设计的引用层级完全没有问题。

2. DAO内部直接调用其他DAO的问题

这个实现属于典型的不良实践,核心问题有三个:

  • 会触发N+1查询问题:查询1条Reception记录就要额外执行3条SQL查询关联的Service、Master、Client,如果列表页加载100条预约,就会多产生300条SQL,性能会随数据量上涨快速恶化。
  • 职责越界耦合严重:DAO层的单一职责是负责对应单表的CRUD操作,在ReceptionDAO里直接实例化其他DAO,会让DAO之间形成强依赖,后续修改UserDAO、ServiceDAO的构造逻辑或者依赖规则时,所有嵌套调用的位置都需要同步修改,维护成本极高。
  • 事务管控困难:多个DAO的调用散落在数据映射逻辑中,后续需要加事务控制时,很难统一管理数据库连接和回滚逻辑。

3. 可落地的优化方案

针对当前不使用ORM框架的技术栈,有两个成熟的优化方向,按推荐优先级排序:

方案一:DAO层通过JOIN查询完成关联映射,不跨DAO调用

直接在ReceptionDAO的查询SQL中用LEFT JOIN关联service、user表(user表需要关联两次分别获取技师和客户信息),单次SQL就把需要的所有字段查询出来,在mapReception方法中直接从ResultSet中取关联表字段构建Service、User对象,完全不需要调用其他DAO。
示例核心查询SQL如下:

SELECT 
  r.*,
  s.name as service_name, s.price as service_price,
  mu.id as master_id, mu.name as master_name,
  cu.id as client_id, cu.name as client_name
FROM reception r
LEFT JOIN service s ON r.service_id = s.id
LEFT JOIN user mu ON r.master_id = mu.id
LEFT JOIN user cu ON r.client_id = cu.id
-- 按需追加查询条件

这个方案不需要额外做架构分层改造,查询性能最好,DAO之间没有任何耦合,完全适配当前技术栈,也不会出现N+1问题。

方案二:拆分单表DAO职责,关联组装逻辑下沉到Service层

保持每个DAO只负责自身对应单表的查询:ReceptionDAO只查询reception表,返回的Reception对象初始只存储serviceId、masterId、clientId这类外键值,不直接持有关联实体对象。业务组装逻辑放到ReceptionService层:先调用ReceptionDAO查出预约列表,提取列表中所有不重复的serviceId、masterId、clientId,通过WHERE id IN (?,?,?)的批量查询方式一次性取出所有关联的Service、User数据,再根据ID匹配后set到对应的Reception实例中,最后将组装完成的数据返回给视图层。
这个方案的优势是DAO职责更纯粹,后续如果有其他业务场景只需要Reception本身的字段、不需要关联数据时,不需要执行多余的关联查询,DAO层代码复用性更高。


内容的提问来源于stack exchange,提问作者Vadym

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 06:48:14