【问题标题】:Best approach to manage complex queries with complex DTO使用复杂 DTO 管理复杂查询的最佳方法
【发布时间】:2020-07-30 01:53:16
【问题描述】:

我正在使用 Spring JPA 规范来创建条件查询。我的代码如下所示:

 public CollectionWrapper<OrderTableItemDto> getOrders(Integer page,
                                         Integer size,
                                         String search,
                                         OrderShow show) {

    Pageable pageable = PageRequest.of(page, size, Sort.by(OrderEntity_.ID).descending());

    try {
        Specification<OrderEntity> specifications = buildOrderSpecification(show, search);
        Page<OrderEntity> orders = orderDao.findAll(specifications, pageable);

        List<OrderTableItemDto> result = orders.getContent().stream().map(order -> {
            OrderTableItemDto orderTableItemDto = new OrderTableItemDto();
            orderTableItemDto.setCreatedDate(order.getCreatedDate());
            orderTableItemDto.setCustomerId(order.getCustomer().getId());
            orderTableItemDto.setId(order.getId());
            orderTableItemDto.setStatus(order.getStatus());
            orderTableItemDto.setTotalCost(order.getTotalCost());
            orderTableItemDto.setOrderType(order.getOrderType());
            orderTableItemDto.setOrderTypeLabel(order.getOrderType().getLabel());
            if(order.getOrderInShippingMethod() != null) {
                orderTableItemDto.setShipped(OrderShippingStatus.DELIVERED.equals(order.getOrderInShippingMethod().getStatus()));
            } else {
                orderTableItemDto.setShipped(false);
            }
            StringBuilder sb = new StringBuilder();
            sb.append("#")
                    .append(order.getId())
                    .append(" ");
            if(order.getOrderType().equals(OrderType.WEB_STORE)) {
                sb
                        .append(order.getBilling().getFirstName())
                        .append(" ")
                        .append(order.getBilling().getLastName());

            } else {
                sb.append(order.getCustomer().getFullName());
            }

            orderTableItemDto.setTitle(sb.toString());
            return orderTableItemDto;
        }).collect(Collectors.toList());
        return new CollectionWrapper<>(result, orders.getTotalElements());

有人告诉我这是不好的方法,我应该使用投影 (DTO) 从数据库中读取数据,因为创建实体并在之后映射它们的成本很高。问题是我不知道如何将规范与 DTO 结合起来。使用包含嵌套 DTO 和许多属性的复杂 DTO 来管理复杂和动态查询(来自用户输入的过滤器等)的最佳方法是什么?

【问题讨论】:

    标签: hibernate spring-boot jpa spring-data-jpa dto


    【解决方案1】:

    获取实体然后创建 DTO 通常是一种浪费,因为实体通常具有比您真正需要的多得多的列。如果您在某处使用渴望获取,您还将进行不必要的连接。如果您有想要获取的嵌套集合或复杂表达式,请查看Blaze-Persistence Entity-Views,这是一个在 JPA 之上工作的库,它允许您将任意结构映射到您的实体模型。它允许您将投影与业务逻辑分离,即您可以将该 DTO 应用于现有查询。您的用例的实体视图可能如下所示

    @EntityView(OrderEntity.class)
    interface OrderTableItemDto {
      @IdMapping
      Long getId();
      Date getCreatedDate();
      String getStatus();
      BigDecimal getTotalCost();
      @Mapping("orderType.label")
      String getOrderTypeLabel();
      @Mapping("CASE WHEN orderInShippingMethod.status = OrderShippingStatus.DELIVERED THEN true ELSE false END")
      boolean isShipped();
      @Mapping("CONCAT('#', id, CASE WHEN orderType = OrderType.WEB_STORE THEN CONCAT(billing.firstName, ' ', billing.lastName) ELSE customer.fullName END)")
      String getTitle();
    }
    

    通过 Blaze-Persistence 提供的 spring 数据集成,您可以像这样定义一个存储库并直接使用结果

    @Transactional(readOnly = true)
    interface OrderRepository extends Repository<OrderEntity, Long> {
      Page<OrderTableItemDto> findAll(Specification<OrderEntity> specification, Pageable pageable);
    }
    

    它将生成一个查询,仅选择您在 OrderTableItemDto 中映射的内容。

    【讨论】:

      【解决方案2】:

      我不认同创建实体比直接使用 DTO 更快的论点。 JPA 仍然必须在创​​建 DTO 之前提供中间表示。 即使它更快或使用更少的内存,问题是:它是否相关?

      如果性能是此代码更改的原因,您应该做的第一件事是实施适当的基准测试。见How to run JMH from inside JUnit tests?

      您经常有各种选项来创建 DTO。

      使用派生查询(基于方法名称的查询)或基于@Query 注释,您可以使用基于类的 DTO,即看起来像您的实体但可能具有较少属性的类。 或者你可以使用只有你感兴趣的值的 getter 的接口。 在这两种情况下,您都可以用投影替换返回类型。 见https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections

      如果您使用 JPQL 或 Criteria API,您可以使用构造函数表达式。 例如,请参阅此答案以获取示例:https://stackoverflow.com/a/12286281/66686

      当然,当您在此级别上进行调整时,您应该考虑 JPA 是否在浪费性能,并将其与更直接在数据库上工作的东西进行比较,例如 JdbcTemplate、Querydsl 或 jOOQ。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-07-08
        • 1970-01-01
        • 1970-01-01
        • 2022-12-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-09-02
        相关资源
        最近更新 更多