Избегайте сериализации Джексона на непривлекательных ленивых объектах
У меня есть простой контроллер, который возвращает объект User, этот пользователь имеет координаты атрибутов, которые имеют свойство hibernate FetchType.LAZY.
Когда я пытаюсь получить этого пользователя, мне всегда нужно загрузить все координаты, чтобы получить объект пользователя, иначе, когда Джексон попытается выполнить сериализацию пользователя, выдается исключение:
com.fasterxml.jackson.databind.JsonMappingException: не удалось инициализировать прокси - нет сеанса
Это связано с тем, что Джексон пытается извлечь этот необработанный объект. Вот объекты:
public class User{
@OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
@JsonManagedReference("user-coordinate")
private List<Coordinate> coordinates;
}
public class Coordinate {
@ManyToOne
@JoinColumn(name = "user_id", nullable = false)
@JsonBackReference("user-coordinate")
private User user;
}
И контроллер:
@RequestMapping(value = "/user/{username}", method=RequestMethod.GET)
public @ResponseBody User getUser(@PathVariable String username) {
User user = userService.getUser(username);
return user;
}
Есть ли способ сказать Джексону не сериализовать выделенные объекты? Я искал другие ответы, опубликованные 3 года назад, внедрение модуля jackson-hibernate. Но, возможно, это может быть достигнуто с помощью новой функции jackson.
Мои версии:
- Spring 3.2.5
- Hibernate 4.1.7
- Джексон 2.2
Спасибо заранее.
Ответы
Ответ 1
Наконец-то я нашел решение! благодаря indybee за то, что дал мне ключ.
Учебник Spring 3.1, Hibernate 4 и Jackson-Module-Hibernate имеют хорошее решение для Spring 3.1 и более ранних версий. Но поскольку версия 3.1.2 Spring имеет свой собственный MappingJackson2HttpMessageConverter с почти той же функциональностью, что и в учебнике, поэтому нам не нужно создавать этот настраиваемый HTTPMessageConverter.
С javaconfig нам не нужно создавать HibernateAwareObjectMapper, нам просто нужно добавить Hibernate4Module к стандартным MappingJackson2HttpMessageConverter, который Spring уже есть и добавить его в HttpMessageConverters приложения, поэтому нам нужно:
-
Расширьте наш Spring конфигурационный класс от WebMvcConfigurerAdapter и переопределите метод configureMessageConverters.
-
В этом методе добавьте MappingJackson2HttpMessageConverter с Hibernate4Module, зарегистрированным в методе previus.
Наш класс конфигурации должен выглядеть следующим образом:
@Configuration
@EnableWebMvc
public class MyConfigClass extends WebMvcConfigurerAdapter{
//More configuration....
/* Here we register the Hibernate4Module into an ObjectMapper, then set this custom-configured ObjectMapper
* to the MessageConverter and return it to be added to the HttpMessageConverters of our application*/
public MappingJackson2HttpMessageConverter jacksonMessageConverter(){
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper mapper = new ObjectMapper();
//Registering Hibernate4Module to support lazy objects
mapper.registerModule(new Hibernate4Module());
messageConverter.setObjectMapper(mapper);
return messageConverter;
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//Here we add our custom-configured HttpMessageConverter
converters.add(jacksonMessageConverter());
super.configureMessageConverters(converters);
}
//More configuration....
}
Если у вас есть xml-конфигурация, вам не нужно создавать свой собственный MappingJackson2HttpMessageConverter, но вам нужно создать персонализированный картограф, который появляется в учебнике (HibernateAwareObjectMapper), поэтому ваша xml-конфигурация должна выглядеть так:
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="com.pastelstudios.json.HibernateAwareObjectMapper" />
</property>
</bean>
</mvc:message-converters>
Надеюсь, что этот ответ будет понятным и поможет кому-то найти решение этой проблемы, любые вопросы не стесняйтесь спрашивать!
Ответ 2
Как и в случае с Spring 4.2 и используя Spring Boot и javaconfig, регистрация Hibernate4Module теперь просто, как добавление этого в вашу конфигурацию:
@Bean
public Module datatypeHibernateModule() {
return new Hibernate4Module();
}
ref: https://spring.io/blog/2014/12/02/latest-jackson-integration-improvements-in-spring
Ответ 3
Это похоже на принятое решение @rick.
Если вы не хотите прикасаться к существующей конфигурации конвертеров сообщений, вы можете просто объявить Jackson2ObjectMapperBuilder
bean как:
@Bean
public Jackson2ObjectMapperBuilder configureObjectMapper() {
return new Jackson2ObjectMapperBuilder()
.modulesToInstall(Hibernate4Module.class);
}
Не забудьте добавить следующую зависимость к вашему файлу Gradle (или Maven):
compile 'com.fasterxml.jackson.datatype:jackson-datatype-hibernate4:2.4.4'
Полезно, если у вас есть spring-boot приложения, и вы хотите сохранить возможность изменять функции Джексона из файла application.properties
.
Ответ 4
В случае Spring Data Rest, тогда, когда решение, отправленное @r1ckr, работает, все, что требуется, - это добавить одну из следующих зависимостей в зависимости от версии Hibernate:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate4</artifactId>
<version>${jackson.version}</version>
</dependency>
или
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate5</artifactId>
<version>${jackson.version}</version>
</dependency>
Внутри Spring Data Rest существует класс:
org.springframework.data.rest.webmvc.json.Jackson2DatatypeHelper
который автоматически определит и зарегистрирует модуль при запуске приложения.
Однако есть проблема:
Проблема Сериализация Lazy @ManyToOne
Ответ 5
Если вы используете конфигурацию XML и используете <annotation-driven />
, я обнаружил, что вам нужно вложить <message-converters>
внутрь <annotation-driven>
, как рекомендовано в учетной записи Jackson Github.
Так же:
<annotation-driven>
<message-converters>
<beans:bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<beans:property name="objectMapper">
<beans:bean class="com.pastelstudios.json.HibernateAwareObjectMapper" />
</beans:property>
</beans:bean>
</message-converters>
</annotation-driven>
`
Ответ 6
Для тех, кто пришел сюда, чтобы найти решение для службы RESTful на основе Apache CXF, настройка, которая ее исправляет, находится ниже:
<jaxrs:providers>
<bean class="com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider">
<property name="mapper" ref="objectMapper"/>
</bean>
</jaxrs:providers>
<bean id="objectMapper" class="path.to.your.HibernateAwareObjectMapper"/>
Где HibernateAwareObjectMapper определяется как:
public class HibernateAwareObjectMapper extends ObjectMapper {
public HibernateAwareObjectMapper() {
registerModule(new Hibernate5Module());
}
}
С июня 2016 года требуется следующая зависимость: при использовании Hibernate5:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate5</artifactId>
<version>2.7.4</version>
</dependency>
Ответ 7
Вы можете использовать следующий помощник из проекта Spring Data Rest:
Jackson2DatatypeHelper.configureObjectMapper(objectMapper);
Ответ 8
Следующее решение предназначено для Spring 4.3, (без загрузки) и Hibernate 5.1, где мы перевели все типы fetch=FetchType.LAZY
в fetch=FetchType.LAZY
из случаев fetch=FetchType.EAGER
по соображениям производительности. Сразу же мы увидели com.fasterxml.jackson.databind.JsonMappingException: could not initialize proxy
исключение com.fasterxml.jackson.databind.JsonMappingException: could not initialize proxy
из-за проблемы с com.fasterxml.jackson.databind.JsonMappingException: could not initialize proxy
загрузкой.
Сначала добавим следующую зависимость maven:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate5</artifactId>
</dependency>
Затем в наш файл конфигурации Java MVC добавляется следующее:
@Configuration
@EnableWebMvc
public class MvcConfig extends WebMvcConfigurerAdapter {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
Hibernate5Module h5module = new Hibernate5Module();
h5module.disable(Hibernate5Module.Feature.USE_TRANSIENT_ANNOTATION);
h5module.enable(Hibernate5Module.Feature.FORCE_LAZY_LOADING);
for (HttpMessageConverter<?> mc : converters){
if (mc instanceof MappingJackson2HttpMessageConverter || mc instanceof MappingJackson2XmlHttpMessageConverter) {
((AbstractJackson2HttpMessageConverter) mc).getObjectMapper().registerModule(h5module);
}
}
return;
}
Заметки:
-
Вам нужно создать и настроить Hibernate5Module, чтобы получить поведение, похожее на Джексона без этого модуля. Значение по умолчанию делает несовместимые предположения.
-
Наш WebMvcConfigurerAdapter
имеет много других настроек, и мы хотели избежать другого класса конфигурации, поэтому мы не использовали WebMvcConfigurationSupport#addDefaultHttpMessageConverters
которую ссылались другие посты.
-
WebMvcConfigurerAdapter#configureMessageConverters
отключает всю внутреннюю конфигурацию Spring конвертеров сообщений. Мы предпочли избежать потенциальных проблем вокруг этого.
-
Использование extendMessageConverters
позволило получить доступ ко всем автоматически конфигурируемым классам Джексона без потери конфигурации всех других конвертеров сообщений.
-
Используя getObjectMapper#registerModule
мы смогли добавить Hibernate5Module
к существующим конвертерам.
- Модуль был добавлен к процессорам JSON и XML
Это добавление решило проблему с Hibernate и отложенной загрузкой, но вызвало остаточную проблему с сгенерированным форматом JSON. Как сообщалось в этой проблеме github, модуль отложенной загрузки hibernate-jackson в настоящее время игнорирует аннотацию @JsonUnwrapped, что приводит к потенциальным ошибкам данных. Это происходит независимо от настройки функции принудительной загрузки. Проблема существует с 2016 года.
Заметка
Похоже, что при добавлении следующего к загруженным ленивым классам встроенный ObjectMapper работает без добавления модуля hibernate5:
@JsonIgnoreProperties( {"handler","hibernateLazyInitializer"} )
public class Anyclass {
Ответ 9
Я попробовал @полезный ответ, но столкнулся с проблемой, что "известные модули", такие как jackson-datatype-jsr310 не были автоматически зарегистрированы, несмотря на то, что они находятся на пути к классам. (Это сообщение в блоге объясняет автоматическую регистрацию.)
Развернувшись на @rick, ответьте здесь, используя Spring Jackson2ObjectMapperBuilder, чтобы создать ObjectMapper
. Это автоматически регистрирует "хорошо известные модули" и устанавливает некоторые функции в дополнение к установке Hibernate4Module
.
@Configuration
@EnableWebMvc
public class MyWebConfig extends WebMvcConfigurerAdapter {
// get a configured Hibernate4Module
// here as an example with a disabled USE_TRANSIENT_ANNOTATION feature
private Hibernate4Module hibernate4Module() {
return new Hibernate4Module().disable(Hibernate4Module.Feature.USE_TRANSIENT_ANNOTATION);
}
// create the ObjectMapper with Spring Jackson2ObjectMapperBuilder
// and passing the hibernate4Module to modulesToInstall()
private MappingJackson2HttpMessageConverter jacksonMessageConverter(){
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.modulesToInstall(hibernate4Module());
return new MappingJackson2HttpMessageConverter(builder.build());
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(jacksonMessageConverter());
super.configureMessageConverters(converters);
}
}
Ответ 10
Хотя этот вопрос немного отличается от этого: Исключение Strange Jackson, возникающее при сериализации объекта Hibernate, основная проблема может быть исправлена таким же образом с помощью этого кода
@Provider
public class MyJacksonJsonProvider extends JacksonJsonProvider {
public MyJacksonJsonProvider() {
ObjectMapper mapper = new ObjectMapper();
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
setMapper(mapper);
}
}
Ответ 11
Я пробовал это и работал:
//настраиваемая конфигурация для ленивой загрузки
public static class HibernateLazyInitializerSerializer extends JsonSerializer<JavassistLazyInitializer> {
@Override
public void serialize(JavassistLazyInitializer initializer, JsonGenerator jsonGenerator,
SerializerProvider serializerProvider)
throws IOException, JsonProcessingException {
jsonGenerator.writeNull();
}
}
и сконфигурируйте mapper:
mapper = new JacksonMapper();
SimpleModule simpleModule = new SimpleModule(
"SimpleModule", new Version(1,0,0,null)
);
simpleModule.addSerializer(
JavassistLazyInitializer.class,
new HibernateLazyInitializerSerializer()
);
mapper.registerModule(simpleModule);
Ответ 12
Я схожу с ума от этой проблемы. Ни одно из вышеперечисленных предложений не помогло мне. Я на hibernate 5.2.8, spring 4.3.6 и jackson-datatype-hibernate5 2.9.0
Любая идея, почему я продолжаю получать прокси не инициализированное исключение?
Благодарю.
ура
Ответ 13
Я сделал очень простое решение этой проблемы.
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public Set<Pendency> getPendencies() {
return Hibernate.isInitialized(this.pendencies) ? Collections.unmodifiableSet(this.pendencies) : new HashSet<>();
}
В моем случае я сообщал об ошибке, потому что всякий раз, когда я возвращал pendencies, в качестве хорошей практики я конвертировал его в список, который нельзя изменить, но как он может или не может быть ленивым в зависимости от метода, который я использовал чтобы получить экземпляр (с извлечением или без него), я делаю тест до его инициализации в Hibernate и добавляю аннотацию, которая предотвращает сериализацию пустого свойства и решает мою проблему.