Как преобразовать сущность с ленивыми свойствами в DTO, используя маппер модели Java - PullRequest
0 голосов
/ 11 февраля 2019

Я использую Spring JPA с hibernate, WebFlux и у меня проблемы с этим.Я пытался извлечь некоторые объекты из базы данных, но возникла исключительная ситуация отложенной загрузки при попытке преобразовать мой объект в DTO.Я понимаю, почему это происходит, и я могу избежать этого, создав DTO для каждого графа сущностей (чтобы получать только активные свойства), но это кажется излишним.Также я попытался пропустить ленивые свойства, используя setPropertyCondition() и Hibernate.isInitialized(), но было сгенерировано исключение, прежде чем условие свойства было проверено картографом модели.Я попытался вернуть объект сущности в свое отображение, но не смог найти правильную конфигурацию для использования Jackson Hibernate5Module.Итак, я спрашиваю вас, как мне использовать @EntityGraph с моими репозиториями, чтобы в контроллере не было ошибок, в идеале свойства с отложенной загрузкой должны быть нулевыми.

Я решил проблему с исключением из-за отложенной инициализации, пропустив ленивые свойства во времяDTO картирование процесса.Я создал для него собственный считыватель значений и применил его к экземпляру java model mapper.

public class CustomValueReader implements ValueReader<Object> {
    @Override
    public Object get(Object source, String memberName) {
        try {
            Field field = source.getClass().getDeclaredField(memberName);
            field.setAccessible(true);
            return field.get(source);
        }
        catch(Exception e) {
            return null;
        }
    }

    @Override
    public Member<Object> getMember(Object source, String memberName) {
        final Object value = get(source, memberName);
        Class<?> memberType = value != null ? value.getClass() : Object.class;
        return new Member<Object>(memberType) {
            @Override
            public Object getOrigin() {
                return null;
            }

            @Override
            public Object get(Object source, String memberName) {
                return CustomValueReader.this.get(source, memberName);
            }
        };
    }

    @Override
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public Collection<String> memberNames(Object source) {
        List<String> properties = new ArrayList<>();
        for (Field field : source.getClass().getDeclaredFields()) {
            field.setAccessible(true);//needed for accessing field using reflection
            boolean valid = true;
            Object fieldValue = null;
             try{
                 fieldValue = field.get(source);
             }
             catch(Exception e) {
                 valid = false;
             }
            boolean isInited = valid && Hibernate.isInitialized(fieldValue);
             if (isInited){
                 properties.add(field.getName());
             }
        }
        return properties;
    }

    @Override
    public String toString() {
        return "Costum";
    }
}

Применим его к mapper

mapper.getConfiguration().addValueReader(new CustomValueReader());

Также я заметил, что использование графа сущностей делает все свойства нетерпеливымиили ленивый (в зависимости от EntityGraph.EntityGraphType) игнорируя attributeNodes.Определение моего класса выглядит так:

@NamedEntityGraphs({
        @NamedEntityGraph(name = ClinicVisit.CLINIC_VISIT_ENTITY_GRAPH_WITH_PATIENT_AND_DEVICES,
                attributeNodes = @NamedAttributeNode(value = "patient",subgraph = "CVEG_DEVICES"),
                subgraphs = @NamedSubgraph(name = "CVEG_DEVICES", attributeNodes = @NamedAttributeNode("notificationDevices"))
        ),
        @NamedEntityGraph(name = ClinicVisit.CLINIC_VISIT_ENTITY_GRAPH_WITH_PATIENT,
                attributeNodes = @NamedAttributeNode(value = "patient")
        )
})


@Entity
@Table(name="clinic_visit_request")
@Getter
@Setter
public class ClinicVisit {

    public final static String CLINIC_VISIT_ENTITY_GRAPH_WITH_PATIENT_AND_DEVICES = "clinic_visit_request_patient_devices";
    public final static String CLINIC_VISIT_ENTITY_GRAPH_WITH_PATIENT = "clinic_visit_request_patient";

    @Id
    @GeneratedValue
    protected Long id;

    @Column(name = "visit_date")
    protected Date visitDate;

    @Column(name = "update_date")
    protected Date updateDate;

    protected Boolean informed;

    @Column( columnDefinition = "boolean default false", nullable = false)
    protected Boolean handled = false;

    @ManyToOne()
    protected ClinicUser doctor;

    @ManyToOne()
    protected ClinicUser patient;

    @ManyToOne()
    protected ClinicUser confirmer;
}

Определение метода моего репозитория:

@EntityGraph(value=ClinicVisit.CLINIC_VISIT_ENTITY_GRAPH_WITH_PATIENT,type= EntityGraph.EntityGraphType.FETCH)
   List<ClinicVisit> findAll();

Все отлично работает, это было мое недоразумение.У меня было три поля (доктор, пациент, подтверждающий) и я сохранял в них одного и того же пользователя, поэтому, когда я добавил NamedAttributeNode для пациента, я ожидал, что будет загружен только пациент, но другие поля были загружены, потому что гибернация умная.

Я понимаю, почему Hibernate5Module не работал.Я использую WebFlux, поэтому конфигурация для JacksonMapper должна выглядеть так:

@Configuration
public class AppConfig {

@Bean
Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer(){
    return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder
            .featuresToEnable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
            .propertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE)
            .modules(new Hibernate5Module());
}


@Bean
Jackson2JsonEncoder jackson2JsonEncoder(ObjectMapper mapper){
    return new Jackson2JsonEncoder(mapper);
}

@Bean
Jackson2JsonDecoder jackson2JsonDecoder(ObjectMapper mapper){
    return new Jackson2JsonDecoder(mapper);
}

@Bean
WebFluxConfigurer webFluxConfigurer(Jackson2JsonEncoder encoder, Jackson2JsonDecoder decoder) {
    return new WebFluxConfigurer() {
        @Override
        public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
            configurer.defaultCodecs().jackson2JsonEncoder(encoder);
            configurer.defaultCodecs().jackson2JsonDecoder(decoder);
        }
    };
}

}

1 Ответ

0 голосов
/ 11 февраля 2019

Настроить Hibernate5Module так же просто, как создать боб:

@Bean
public com.fasterxml.jackson.databind.Module datatypeHibernateModule() {
    return new Hibernate5Module();
}

См. https://stackoverflow.com/a/37840526/10609423

Прекрасно работает с Spring Boot 2.1.2.Если бы я мог, то сделал бы этот комментарий.

...