Я использую 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);
}
};
}
}