Уникайте серіалізації Джексона на невибраних ледачих об’єктах


83

У мене є простий контролер, який повертає об'єкт User, цей користувач має координати атрибутів, які мають властивість сплячого режиму 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. Але, ймовірно, цього можна було досягти за допомогою нової функції Джексона.

Мої версії:

  • Весна 3.2.5
  • Зимовий сон 4.1.7
  • Джексон 2.2

Заздалегідь спасибі.


3
Підхід, описаний у цих 2 посиланнях, спрацював для мене, Spring 3.1, Hibernate 4 та Jackson-Module-Hibernate та FasterXML / jackson-datatype-hibernate
indybee

Дякую indybee, я спостерігав за підручником, весна 3.2.5 вже має MappingJackson2HttpMessageConverter, але я не можу використовувати його, щоб уникнути невибраного лінивого об'єкта, також я намагався реалізувати той у підручнику, але нічого. ..
r1ckr

1
Ви отримуєте ту саму помилку "не вдалося ініціалізувати проксі" чи щось інше? (я використовував додатковий HibernateAwareObjectMapper, описаний у статтях з Spring 3.2.6 та Hibernate 4.1.7 та jackson 2.3)
indybee

1
Зрозумів!! Починаючи з версії 3.1.2 Spring, він має власний MappingJackson2HttpMessageConverter і має майже таку ж поведінку, як у підручнику, але проблема полягала в тому, що я використовую Spring java config і я починаю з javaconfigs. Я намагався зробити @Bean MappingJackson2HttpMessageConverter , замість того, щоб додавати його до HttpMessageConverters Spring, можливо, це зрозуміліше у відповіді :)
r1ckr

раді, що це працює :)
indybee

Відповіді:


93

Нарешті я знайшов рішення! дякую 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, зареєстрованого в попередньому методі.

Наш клас config повинен виглядати так:

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

Сподіваюся, ця відповідь буде зрозумілою і допоможе комусь знайти рішення цієї проблеми, будь-які запитання сміливо задавайте!


3
Дякуємо, що поділилися рішенням. Однак після кількох спроб я виявив, що останній джексон на даний момент ( 2.4.2 ) НЕ працює з spring 4.0.6. У мене все ще є помилка "немає сеансу". Лише коли я повернув оцінену версію джексона до 2.3.4 (або 2.4.2), вона почала працювати.
Робін

1
Дякуємо, що вказали MappingJackson2HttpMessageConverter! Я деякий час шукав такий клас.
LanceP

1
Дуже дякую! Я щойно перевірив його та його роботу з Spring 4.2.4, Jackson 2.7.1-1 та JacksonDatatype 2.7.1 :)
Істар

1
Це не вирішує проблему у Spring Data REST.
Алессандро С

1
Як вирішити те саме питання, якщо замість Spring використовується Springboot ?. Я спробував зареєструвати модуль Hibernate4 (це версія, яку я маю на даний момент), але це не вирішило моєї проблеми.
Ранджит Гампа

34

Починаючи з версії 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


19
Для Hibernate 5 (як і в поточній версії Spring Boot) це так Hibernate5Module. Це додатково потребує залежності від<groupId>com.fasterxml.jackson.datatype</groupId><artifactId>jackson-datatype-hibernate5</artifactId>
Zeemee 02

1
Працював у мене також на Spring Boot 2.1. Починав зводити мене з розуму.
Ван Дам

Незрозуміло, як це реалізувати. Я отримую помилку несумісного типу.
EarthMind

2
Чудово! Працював у мене з Spring Boot 2.0.6 і Hibernate 5.
GarouDan

Це було дуже корисно. Слід зазначити, що реєстрація екземпляра класу Hibernate5Module як компонента недостатня. Якщо ви хочете застосувати його до одного екземпляра ObjectMapper, ви можете автоматично підключити модуль до свого класу, керованого Spring, а потім зареєструвати його в екземплярі ObjectMapper. @Autowire Module jacksonHibernateModule; ObjectMapper mapper = новий ObjectMapper (); mapper.registerModule (this.jacksonHibernateModule).
gburgalum01

12

Це схоже на прийняте рішення @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'

Корисно, якщо у вас є додаток, і ви хочете зберегти можливість змінювати функції Джексона з application.propertiesфайлу.


Як ми можемо досягти цього (налаштування Джексона, щоб уникнути лінивого завантаженого об’єкта) за допомогою application.propertiesфайлу?
hemu

1
Тут не про це йдеться. Рішення @Luis Belloch пропонує вам створити Jackson2ObjectMapperBuilder, щоб не перевизначити перетворювач повідомлень за замовчуванням spring boots і продовжувати використовувати значення application.properties для управління властивостями джексона.
kapad

вибачте ... я новачок, де б ви застосували цей метод? При весняному завантаженні використовується Spring 5, який WebMvcConfigurerAdapter припинено
Ерік Хуанг,

Hibernate4Moduleє спадщиною
Дмитро Калтович

8

Тоді у випадку 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

який автоматично виявить і зареєструє Модуль при запуску програми.

Однак існує проблема:

Випуск Серіалізація ледачого @ManyToOne


Як налаштувати це з пружинним завантаженням, коли ви не використовуєте Spring Data Rest?
Ерік Хуанг

Недостатньо. Вам теж потрібно@Bean hibernate5Module()
Дмитро Калтович

4

Якщо ви використовуєте конфігурацію 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>

`


Конфігурація xml є застарілою
Дмитро Калтович,

3

Для тих, хто прийшов сюди, шукаючи рішення для послуги 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>  

1

Наступне рішення призначене для 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

Це доповнення вирішило проблему зі сплячим режимом та ледачим завантаженням, але спричинило залишкову проблему згенерованого формату JSON . Як повідомляється у цьому випуску github , модуль ледачого навантаження hibernate-jackson в даний час ігнорує анотацію @JsonUnwrapped, що призводить до можливих помилок даних. Це відбувається незалежно від налаштування функції примусового навантаження. Проблема існує з 2016 року.


Примітка

Здається, додаючи наступне до класів, які ледаче завантажуються, вбудований ObjectMapper працює без додавання модуля hibernate5:

@JsonIgnoreProperties(  {"handler","hibernateLazyInitializer"} )
public class Anyclass {

Рішення може бути набагато простішим: kotlin @Bean fun hibernate5Module(): Module = Hibernate5Module()
Дмитро Калтович,


0

Я дуже просто вирішив цю проблему.

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public Set<Pendency> getPendencies() {
    return Hibernate.isInitialized(this.pendencies) ? Collections.unmodifiableSet(this.pendencies) : new HashSet<>();
}

У моєму випадку я давав помилку, оскільки щоразу, коли я повертав очікування, як хорошу практику я перетворював її на список, який не можна змінити, але як він міг чи не міг бути лінивим, залежно від методу, який я використовував для отримання екземпляру (із завантаженням чи без нього), я виконую тест перед його ініціалізацією Hibernate і додаю анотацію, яка перешкоджає серіалізації порожнього властивості та вирішує мою проблему.


0

Я цілий день намагався вирішити одну і ту ж проблему. Ви можете зробити це, не змінюючи конфігурацію існуючих перетворювачів повідомлень.

На мій погляд, найпростіший спосіб вирішити цю проблему лише за 2 кроки за допомогою jackson-datatype-hibernate :

приклад kotlin (те саме, що і java):

  1. Додати build.gradle.kts:
implementation("com.fasterxml.jackson.datatype:jackson-datatype-hibernate5:$jacksonHibernate")
  1. Створити @Bean
   @Bean
   fun hibernate5Module(): Module = Hibernate5Module()

  • Зверніть увагу , що Moduleце com.fasterxml.jackson.databind.ModuleНЕjava.util.Module

  • Також гарною практикою є додавання @JsonBackReference& @JsonManagedReferenceдо @OneToMany& @ManyToOneвідносин. @JsonBackReferenceможе бути лише 1 у класі.


-1

Хоча це питання дещо відрізняється від цього: дивний виняток Джексона, який виникає під час серіалізації об'єкта Hibernate , основну проблему можна виправити таким же чином за допомогою цього коду:

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

-1

Я спробував це і працював:

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

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);


-1

Я спробував корисну відповідь @ rick , але зіткнувся з проблемою, що "відомі модулі", такі як jackson-datatype-jsr310 , не реєструвались автоматично, незважаючи на те, що вони знаходились у класі. (Цей запис у блозі пояснює автоматичну реєстрацію.)

Розширюючи відповідь @ rick, ось варіант, використовуючи Spring's 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()
                            .modulesToInstall(hibernate4Module());
        return new MappingJackson2HttpMessageConverter(builder.build());
  }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(jacksonMessageConverter());
        super.configureMessageConverters(converters);
    }
}

Чи є у вас оновлення щодо цієї теми? У мене зараз проблема. Звичайно, коли я додаю hibernate5Module(оскільки я використовую сплячий режим 5), Джексон ігнорує проксі- сервіс неініціалізованих об'єктів, але, схоже, "загальновідомі модулі" не реєструються, незважаючи на те, що я використовую твій підхід. Я це знаю, тому що деякі частини моєї програми не працюють, і коли я виймаю модуль, вони знову працюють.
Даніель Хенао,

@DanielHenao Ви вивчали spring.io/blog/2014/12/02/… ? Я перейшов на Hibernate5, використовуючи DelegatingWebMvcConfigurationклас (замість WebMvcConfigurerAdapter), як запропоновано Jackson-Antpath-Filter :@Override public void configureMessageConverters(List<HttpMessageConverter<?>> messageConverters) { messageConverters.add(jacksonMessageConverter()); addDefaultHttpMessageConverters(messageConverters); }
Markus Pscheidt

1. Hibernate4Moduleє спадщиною. 2 SerializationFeature.WRITE_DATES_AS_TIMESTAMPS- не про питання
Дмитро Калтович

@DmitryKaltovich ad 1) на момент написання статті це не було спадщиною; оголошення 2) добре, видалено.
Маркус Пшайдт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.