Джексон ObjectMapper Hibernate проблема - PullRequest
0 голосов
/ 13 ноября 2018

Я работаю над онлайн-приложением Spring Boot 2.0 / Java 8.Я использую Hibernate в качестве ORM рамки.У меня есть две сущности, Order и OrderDetail, показанные ниже:

@Entity
@Table(name = "orders")
public class Order extends AbstractEntityUuid {

    @Column(name = "order_number", unique = true)
    private String orderNumber;

    @JsonBackReference
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id", nullable = false)
    private User user;

    @Column(name = "total_amount")
    private BigDecimal totalAmount = BigDecimal.ZERO;

    @CreatedDate
    @Column(name = "created_on", columnDefinition = "DATETIME", updatable = false)
    protected LocalDateTime created;

    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
    @JsonManagedReference
    private Set<OrderDetail> items = new HashSet<>();

    @OneToOne(fetch = FetchType.LAZY, orphanRemoval = true)
    @JoinColumn(nullable = false, name = "card_details_id")
    private CardDetails card;

    @OneToOne(fetch = FetchType.LAZY, orphanRemoval = true)
    @JoinColumn(nullable = false, name = "shipping_address_id")
    private Address shippingAddress;

    @OneToOne(fetch = FetchType.LAZY, orphanRemoval = true)
    @JoinColumn(name = "billing_address_id")
    private Address billingAddress;

    //getters and setters
}

@Entity
@Table(name = "order_detail")
public class OrderDetail extends AbstractPersistable<Long> {

    @Column(name = "quantity")
    private Integer quantity;

    @Column(name = "total_amount")
    private BigDecimal totalAmount = BigDecimal.ZERO;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "order_id", nullable = false)
    @JsonBackReference
    private Order order;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "product_id", nullable = false)
    private Product product;

    //getters and setters
}

Когда пользователь переходит к своим заказам, он должен иметь возможность видеть информацию, связанную только с самим заказом (без подробностей),По этой причине я получаю данные только из таблицы заказов.Ниже приводится мой репозиторий:

public interface OrderRepository extends CrudRepository<Order, Long> {

    @Query("FROM Order o WHERE o.user.email = ?1")
    List<Order> findOrdersByUser(String email);
}

В сервисе я просто вызываю вышеуказанный метод и преобразую его в аналог dto.

@Transactional(readOnly = true)
public List<OrdersPreviewDTO> getOrdersPreview(String email) {
    List<Order> orders = orderRepository.findOrdersByUser(email);
    return orderConverter.convertToOrderPreviewDTOs(orders);
}

В преобразователе используется Джексон ObjectMapper объект под капотом.

List<OrdersPreviewDTO> convertToOrderPreviewDTOs(List<Order> orders) {
    return orders.stream()
     .map(o -> objectMapper.convertValue(o, OrdersPreviewDTO.class))
     .collect(toList());
}

objectMapper вводится Spring и определяется в классе конфигурации:

@Bean
public ObjectMapper objectMapper() {
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.findAndRegisterModules();
    return objectMapper;
}

Объект OrdersPreviewDTO dto содержит толькоподмножество сущности Order, потому что, как я уже упоминал, на странице Orders я хочу показывать только высокоуровневые свойства заказов пользователя и ничего не относиться к их деталям.

@JsonIgnoreProperties(ignoreUnknown = true)
public class OrdersPreviewDTO {

    private String orderNumber;

    @JsonFormat(pattern = "dd/MM/yyyy HH:mm")
    private LocalDateTime created;

    private BigDecimal totalAmount;

    @JsonCreator
    public OrdersPreviewDTO(
            @JsonProperty("orderNumber") String orderNumber,
            @JsonProperty("created") LocalDateTime created,
            @JsonProperty("totalAmount") BigDecimal totalAmount) {
        this.orderNumber = orderNumber;
        this.created = created;
        this.totalAmount = totalAmount;
    }

    //getters and setters
}

Все работает нормально,сущность порядка автоматически преобразуется Джексоном в его аналог dto.Проблема выявляется при рассмотрении запроса, выполняемого Hibernate под капотом.В Hibernate разверните коллекцию сведений о заказе для каждого заказа и выполните запрос для извлечения данных из каждой дочерней коллекции:

select carddetail0_.id as id1_2_0_, carddetail0_.brand as brand2_2_0_, carddetail0_.created as created3_2_0_, carddetail0_.exp_month as exp_mont4_2_0_, carddetail0_.exp_year as exp_year5_2_0_, carddetail0_.last4 as last6_2_0_ from card_details carddetail0_ where carddetail0_.id=?

select address0_.id as id1_1_0_, address0_.created as created2_1_0_, address0_.last_modified as last_mod3_1_0_, address0_.city as city4_1_0_, address0_.country as country5_1_0_, address0_.first_name as first_na6_1_0_, address0_.last_name as last_nam7_1_0_, address0_.postal_code as postal_c8_1_0_, address0_.state as state9_1_0_, address0_.street_address as street_10_1_0_, address0_.telephone as telepho11_1_0_ from address address0_ where address0_.id=?

select items0_.order_id as order_id4_4_0_, items0_.id as id1_4_0_, items0_.id as id1_4_1_, items0_.order_id as order_id4_4_1_, items0_.product_id as product_5_4_1_, items0_.quantity as quantity2_4_1_, items0_.total_amount as total_am3_4_1_ from order_detail items0_ where items0_.order_id=?

и других тонн.

Буду ли я изменять код вследующим образом Hibernate запускает только ожидаемый запрос к таблице заказов:

Эта строка кода:

objectMapper.convertValue(o, OrdersPreviewDTO.class)

заменяется следующим грязным исправлением:

new OrdersPreviewDTO(o.getOrderNumber(), o.getCreated(), o.getTotalAmount())

Запрос от Hibernate:

select order0_.id as id1_5_, order0_.billing_address_id as billing_6_5_, order0_.card_details_id as card_det7_5_, order0_.created_on as created_2_5_, order0_.one_address as one_addr3_5_, order0_.order_number as order_nu4_5_, order0_.shipping_address_id as shipping8_5_, order0_.total_amount as total_am5_5_, order0_.user_id as user_id9_5_
from orders order0_ cross join user user1_
where order0_.user_id=user1_.id and user1_.user_email=?

Мой вопрос.Есть ли способ сказать Джексону, чтобы он отображал только поле Dtos, чтобы он не вызывал отложенную загрузку через Hibernate для необязательных полей?

Спасибо

Ответы [ 3 ]

0 голосов
/ 13 ноября 2018

+ 1 для Essex Boy ответ . Я просто хочу добавить, что вы можете напрямую возвращать DTO из ваших запросов JPQL вместо использования Джексона. Это предотвращает преобразование из базы данных в ваш объект Order, а затем еще одно преобразование из Order объекта в OrdersPreviewDTO объект.

Например, вам нужно изменить свой запрос в своем хранилище, чтобы сделать это. Это было бы что-то вроде:

public interface OrderRepository extends CrudRepository<Order, Long> {

    @Query("SELECT new OrdersPreviewDTO(o.order_number, o.created_on, o.total_amount)) FROM Order o WHERE o.user.email = ?1")
    List<OrdersPreviewDTO> findOrdersByUser(String email);
}
0 голосов
/ 13 ноября 2018

Если OrdersPreviewDTO является строго подмножеством вашего Order класса, почему бы просто не использовать аннотацию @JsonView для автоматического создания простого представления в вашем контроллере? См. Например, https://spring.io/blog/2014/12/02/latest-jackson-integration-improvements-in-spring.

Если вам требуется DTO для ввода и вывода, также рассмотрите возможность использования http://mapstruct.org/

0 голосов
/ 13 ноября 2018

Короткий ответ - нет, не пытайтесь быть таким умным, вручную создайте свой DTO, чтобы управлять любой отложенной загрузкой, а затем используйте Джексона в DTO вне транзакции.

Длинный ответ - да,Вы можете переопределить MappingJackson2HttpMessageConverter и управлять тем, какие поля вызываются из сущности.

@Configuration
public class MixInWebConfig extends WebMvcConfigurationSupport {

    @Bean
    public MappingJackson2HttpMessageConverter customJackson2HttpMessageConverter2() {
        MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.addMixIn(DTO1.class, FooMixIn.class);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        jsonConverter.setObjectMapper(objectMapper);
        return jsonConverter;
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(customJackson2HttpMessageConverter2());
    }
}

Тогда

    @Override
    protected void processViews(SerializationConfig config, BeanSerializerBuilder builder) {
        super.processViews(config, builder);
        if (classes.contains(builder.getBeanDescription().getBeanClass())) {
            List<BeanPropertyWriter> originalWriters = builder.getProperties();
            List<BeanPropertyWriter> writers = new ArrayList<BeanPropertyWriter>();
            for (BeanPropertyWriter writer : originalWriters) {
                String propName = writer.getName();
                if (!fieldsToIgnore.contains(propName)) {
                    writers.add(writer);
                }
            }
            builder.setProperties(writers);
        }
    }
}

здесь - рабочий пример.

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