Избегайте сериализации Джексона на непривлекательных ленивых объектах

У меня есть простой контроллер, который возвращает объект 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 и добавляю аннотацию, которая предотвращает сериализацию пустого свойства и решает мою проблему.