Лучший подход к управлению сложными запросами со сложным DTO - PullRequest
0 голосов
/ 16 апреля 2020

Я использую спецификации 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. Каков был бы оптимальный способ управления сложными и динамическими c запросами (фильтрами из пользовательских данных и т. Д. c.) Со сложными DTO, которые содержат вложенные DTO и множество свойств?

Ответы [ 2 ]

1 голос
/ 17 апреля 2020

Я не покупаю аргумент, что создание сущности быстрее, чем прямое использование DTO. JPA все еще должен обеспечить промежуточное представление до создания DTO. И даже если он работает быстрее или использует меньше памяти, возникает вопрос: это уместно?

Если производительность является причиной такого изменения кода, первое, что вы должны сделать, это реализовать надлежащий тест. См. Как запускать JMH из тестов JUnit?

У вас, как часто, есть различные варианты создания DTO.

С производными запросами (основанными на имени метода) или на основе @Query аннотаций вы можете использовать DTO на основе классов, то есть классы, которые выглядят как ваша сущность, но могут иметь меньше свойств. Или вы можете использовать интерфейсы, которые имеют только геттеры для интересующих вас значений. В обоих случаях вы можете заменить тип возвращаемого значения проекцией. См. https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections

Если вы используете JPQL или Criteria API, вы можете использовать выражения конструктора. См., Например, этот ответ для примера: { ссылка }

Конечно, когда вы настраиваете на этом уровне, вы должны учитывать, тратит ли JPA производительность, и сравнивать его с чем-то, что работает более напрямую. в базе данных, например JdbcTemplate, Querydsl или jOOQ.

0 голосов
/ 17 апреля 2020

Выбор сущностей, а затем создание DTO обычно является пустой тратой, поскольку сущности обычно имеют намного больше столбцов, чем вам действительно нужно. Если вы используете где-то нетерпеливое извлечение, вы также будете делать ненужные объединения. Если у вас есть вложенные коллекции или сложные выражения, которые вы хотите получить, проверьте Blaze-Persistence Entity-Views , библиотеку, которая работает поверх JPA и позволяет сопоставлять произвольные структуры с вашей моделью сущностей. Это позволяет вам отделить прогноз от вашей бизнес-логики c, то есть вы можете применить этот 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, вы можете определить подобное хранилище и напрямую использовать результат

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

Он сгенерирует запрос, который выберет именно то, что вы отобразили в OrderTableItemDto.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...