10

我已经阅读了很多关于这些东西的内容,目前我正在开发一个更大的 Web 应用程序及其相应的后端。

但是,我从一个设计开始,我要求存储库从数据库中获取数据并将其映射到 DTO。为什么选择 DTO?仅仅是因为直到现在基本上一切都是简单的东西,没有更多的复杂性是必要的。如果它变得更复杂一点,那么我开始直接在服务层中映射例如 1 对 n 关系。就像是:

// This is Service-Layer
public List<CarDTO> getCarsFromOwner(Long carOwnerId) {

    // Entering Repository-Layer
    List<CarDTO> cars = this.carRepository = this.carRepository.getCars(carOwnerId);
    Map<Long, List<WheelDTO>> wheelMap = this.wheelRepository.getWheels(carId);

    for(CarDTO car : cars) {
        List<WheelDTO> wheels = wheelMap.get(car.getId());
        car.setWheels(wheels);
    }

    return cars;
}

这当然可行,但事实证明,有时事情会变得比这更复杂,我开始意识到如果我对此不做任何事情,代码可能看起来很丑陋。

当然我可以wheelMap在. CarRepository_cars wheelsgetCars(Long ownerId)

我显然缺少业务层,对吧?但我根本无法理解它的最佳实践。

假设我有Car一个Owner业务对象。我的代码会是这样的吗:

// This is Service-Layer
public List<CarDTO> getCarsFromOwner(Long carOwnerId) {

    // The new Business-Layer
    CarOwner carOwner = new CarOwner(carOwnerId);
    List<Car> cars = carOwner.getAllCars();

    return cars;
}

这看起来很简单,但内部会发生什么?问题特别针对CarOwner#getAllCars().

我想这个函数将使用 Mappers 和 Repositories 来加载数据,并且特别是关系映射部分得到处理:

List<CarDTO> cars = this.carRepository = this.carRepository.getCars(carOwnerId);
Map<Long, List<WheelDTO>> wheelMap = this.wheelRepository.getWheels(carId);

for(CarDTO car : cars) {
    List<WheelDTO> wheels = wheelMap.get(car.getId());
    car.setWheels(wheels);
}

但是怎么做?是CarMapper提供功能getAllCarsWithWheels()getAllCarsWithoutWheels()?这也会将CarRepositoryWheelRepository移入CarMapper,但这是存储库的正确位置吗?

如果有人可以向我展示上面代码的一个很好的实际示例,我会很高兴。


附加信息

我没有使用 ORM——而是使用 jOOQ。它本质上只是一种编写 SQL 的类型安全的方式(顺便说一句,使用它很有趣)。

这是一个示例:

public List<CompanyDTO> getCompanies(Long adminId) {

    LOGGER.debug("Loading companies for user ..");

    Table<?> companyEmployee = this.ctx.select(COMPANY_EMPLOYEE.COMPANY_ID)
        .from(COMPANY_EMPLOYEE)
        .where(COMPANY_EMPLOYEE.ADMIN_ID.eq(adminId))
        .asTable("companyEmployee");

    List<CompanyDTO> fetchInto = this.ctx.select(COMPANY.ID, COMPANY.NAME)
        .from(COMPANY)
        .join(companyEmployee)
            .on(companyEmployee.field(COMPANY_EMPLOYEE.COMPANY_ID).eq(COMPANY.ID))
            .fetchInto(CompanyDTO.class);

    return fetchInto;
}
4

2 回答 2

1

Pattern Repository 属于数据访问对象的模式组,通常意味着对相同类型对象的存储抽象。想想可以用来存储对象的 Java 集合——它有哪些方法?它是如何运作的?

根据这个定义,Repository 不能与 DTO 一起使用——它是域实体的存储。如果您只有 DTO,那么您需要更通用的 DAO,或者可能是 CQRS 模式。通常有一个存储库的单独接口和实现,例如,在 Spring Data 中(它自动生成实现,因此您只需指定接口,可能,从通用超接口 CrudRepository 继承基本 CRUD 操作)。例子:

class Car {
   private long ownerId;
   private List<Wheel> wheels;
}

@Repository 
interface CarRepository extends CrudRepository<Car,Long> {
   List<Car> findByOwnerId(long id);
}

当您的域模型是对象树并将它们存储在关系数据库中时,事情会变得复杂。根据这个问题的定义,你需要一个 ORM。将关系内容加载到对象模型中的每一段代码都是一个 ORM,因此您的存储库将有一个 ORM 作为实现。通常,JPA ORM 会在场景后连接对象,更简单的解决方案(如基于 JOOQ 或普通 JDBC 的自定义映射器)必须手动完成。没有什么灵丹妙药可以有效且正确地解决所有 ORM 问题:如果您选择编写自定义映射,最好将布线保留在存储库中,这样业务层(服务)将使用真正的对象模型运行。在您的示例中,CarRepository了解Cars。Car知道Wheels,所以CarRepository已经对 s 有传递依赖Wheel。在CarRepository#findByOwnerId()方法中,您可以Wheel通过添加连接直接在同一查询中获取 Car 的 s,或者将此任务委托给WheelRepository然后只进行接线。此方法的用户将收到完全初始化的对象树。例子:

class CarRepositoryImpl implements CarRepository {

  public List<Car> findByOwnerId(long id) {
     // pseudocode for some database query interface
     String sql = select(CARS).join(WHEELS); 
     final Map<Long, Car> carIndex = new HashMap<>();
     execute(sql, record -> { 
          long carId = record.get(CAR_ID);
          Car car = carIndex.putIfAbsent(carId, Car::new);
          ... // map the car if necessary
          Wheel wheel = ...; // map the wheel
          car.addWheel(wheel);
     }); 
     return carIndex.values().stream().collect(toList());
  }
}

业务层(有时也称为服务层)的作用是什么?业务层对对象执行特定于业务的操作,如果这些操作需要是原子的,则管理事务。基本上,它知道何时发出事务开始、事务提交和回滚的信号,但不知道这些消息在事务管理器实现中实际触发什么。从业务层的角度来看,只有对对象的操作、事务的边界和隔离,没有别的。它不必知道映射器或位于 Repository 接口后面的任何东西。

于 2016-09-17T17:38:44.310 回答
0

在我看来,没有正确的答案。这实际上取决于选择的设计决策,这本身取决于您的堆栈和您/团队的舒适度。

情况1:

我不同意您声明中以下突出显示的部分:

“当然,我可以在 CarRepository 中加载 wheelMap,在那里进行车轮映射,并且只返回完整的对象,但是由于 SQL 查询有时看起来很复杂,我不想获取所有汽车及其车轮,还要处理getCars(Long ownerId) 中的映射"

上面的 sql join 将很简单。此外,由于数据库针对连接和获取数据进行了优化,它可能会更快。现在,我将这种方法称为 Case1,因为如果您决定使用 sql 连接通过存储库提取数据,则可以遵循这种方法。而不是仅仅使用 sql 进行简单的 CRUD,然后在 java 中操作对象。(以下)

案例2:存储库仅用于获取与“一个”表对应的“每个”域对象的数据。

在这种情况下,您所做的已经是正确的。如果你永远不会单独使用 WheelDTO,那么就没有必要为它做单独的服务。您可以在汽车服务中准备好一切。但是,如果您单独需要 WheelDTO,则为每个服务提供不同的服务。在这种情况下,可以在服务层之上有一个帮助层来执行对象创建。我不建议通过创建存储库并为每个 repo 加载所有连接来从头开始实现 orm(直接使用 hibernate 或 mybatis)

恕我直言,无论您采用上述哪种方法,服务或业务层都只是对其进行补充。所以,没有硬性规定,尽量根据自己的要求灵活变通。如果您决定使用 ORM,上述某些内容将再次发生变化。

于 2016-09-18T03:26:36.777 回答