it-roy-ru.com

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

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

Мои версии:

  • Весна 3.2.5
  • Hibernate 4.1.7
  • Джексон 2,2

Заранее спасибо.

65
r1ckr

Я наконец нашел решение! спасибо Indybee за предоставленную мне подсказку.

Учебник Spring 3.1, Hibernate 4 и Jackson-Module-Hibernate предлагает хорошее решение для Spring 3.1 и более ранних версий. Но, начиная с версии 3.1.2, Spring имеет свой собственный MappingJackson2HttpMessageConverter с почти такой же функциональностью, что и в учебнике, поэтому нам не нужно создавать это пользовательский HTTPMessageConverter.

С javaconfig нам не нужно создавать HibernateAwareObjectMapper тоже, нам просто нужно добавить Hibernate4Module по умолчанию MappingJackson2HttpMessageConverter , который уже есть у Spring, и добавьте его в HttpMessageConverters приложения, поэтому нам необходимо:

  1. Расширьте наш весенний конфигурационный класс из WebMvcConfigurerAdapter и переопределите метод configureMessageConverters .

  2. В этом методе добавьте 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>

Надеюсь, что этот ответ будет понятным и поможет кому-то найти решение этой проблемы, любые вопросы, не стесняйтесь задавать!

90
r1ckr

Начиная с Spring 4.2 и с использованием Spring Boot и javaconfig, зарегистрировать Hibernate4Module теперь так же просто, как добавить это в свою конфигурацию:

@Bean
public Module datatypeHibernateModule() {
  return new Hibernate4Module();
}

ссылка: https://spring.io/blog/2014/12/02/latest-jackson-integration-improvements-in-spring

26
chrismarx

Это похоже на принятое решение @rick.

Если вы не хотите трогать конфигурацию существующих конвертеров сообщений, вы можете просто объявить бин Jackson2ObjectMapperBuilder следующим образом:

@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.

14
Luis Belloch

В случае 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

6
Alan Hay

Если вы используете 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>

`

5
blackwood

Для тех, кто пришел сюда, чтобы найти решение для службы 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>  
3
user2968675

Следующее решение предназначено для Spring 4.3, (без загрузки) и Hibernate 5.1, где мы переместили все типы выборки в fetch=FetchType.LAZY из случаев fetch=FetchType.EAGER по соображениям производительности. Сразу же мы увидели исключение 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 {
1
sdw

Вы можете использовать следующий помощник из проекта Spring Data Rest:

Jackson2DatatypeHelper.configureObjectMapper(objectMapper);
1
Przemek Nowak

Хотя этот вопрос немного отличается от этого: при сериализации объекта Hibernate возникает исключение странного Джексона , основная проблема может быть исправлена ​​таким же образом с помощью этого кода:

@Provider
public class MyJacksonJsonProvider extends JacksonJsonProvider {
    public MyJacksonJsonProvider() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        setMapper(mapper);
    }
}
0
nevster

Я попытался полезный ответ @ rick , но столкнулся с проблемой того, что "известные модули", такие как jackson-datatype-jsr31 , не были автоматически зарегистрированы, несмотря на то, что они были на CLASSPATH. (Это сообщение в блоге объясняет автоматическую регистрацию.)

Продолжая ответ @ 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's 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);
    }
}
0
Markus Pscheidt

Я попробовал это, и работал:

// пользовательская конфигурация для отложенной загрузки

public static class HibernateLazyInitializerSerializer extends JsonSerializer<JavassistLazyInitializer> {

    @Override
    public void serialize(JavassistLazyInitializer initializer, JsonGenerator jsonGenerator,
            SerializerProvider serializerProvider)
            throws IOException, JsonProcessingException {
        jsonGenerator.writeNull();
    }
}

и настройте маппер:

    mapper = new JacksonMapper();
    SimpleModule simpleModule = new SimpleModule(
            "SimpleModule", new Version(1,0,0,null)
    );
    simpleModule.addSerializer(
            JavassistLazyInitializer.class,
            new HibernateLazyInitializerSerializer()
    );
    mapper.registerModule(simpleModule);
0
ahll