Vermeiden Sie die Jackson-Serialisierung für nicht abgerufene faule Objekte

83

Ich habe einen einfachen Controller, der ein Benutzerobjekt zurückgibt. Dieser Benutzer hat Attributkoordinaten mit der Eigenschaft FetchType.LAZY im Ruhezustand.

Wenn ich versuche, diesen Benutzer zu erhalten, muss ich immer alle Koordinaten laden, um das Benutzerobjekt zu erhalten. Andernfalls löst Jackson beim Versuch, den Benutzer zu serialisieren, die Ausnahme aus:

com.fasterxml.jackson.databind.JsonMappingException: Proxy konnte nicht initialisiert werden - keine Sitzung

Dies liegt daran, dass Jackson versucht, dieses nicht abgerufene Objekt abzurufen. Hier sind die Objekte:

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

Und der Controller:

@RequestMapping(value = "/user/{username}", method=RequestMethod.GET)
public @ResponseBody User getUser(@PathVariable String username) {

    User user = userService.getUser(username);

    return user;

}

Gibt es eine Möglichkeit, Jackson anzuweisen, die nicht abgerufenen Objekte nicht zu serialisieren? Ich habe nach anderen Antworten gesucht, die vor 3 Jahren veröffentlicht wurden, als das Jackson-Hibernate-Modul implementiert wurde. Aber wahrscheinlich könnte es mit einer neuen Jackson-Funktion erreicht werden.

Meine Versionen sind:

  • Feder 3.2.5
  • Ruhezustand 4.1.7
  • Jackson 2.2

Danke im Voraus.

r1ckr
quelle
3
Ansatz in diesen 2 Links beschrieben funktionierte für mich, Spring 3.1, Hibernate 4 und Jackson-Module-Hibernate und FasterXML / Jackson-Datentyp-Hibernate
Indybee
Vielen Dank Indybee, ich habe das Tutorial gesehen, Frühjahr 3.2.5 hat bereits einen MappingJackson2HttpMessageConverter, aber ich kann es nicht verwenden, um das nicht abgerufene faule Objekt zu vermeiden. Außerdem habe ich versucht, das im Tutorial zu implementieren, aber nichts. ..
r1ckr
1
Erhalten Sie den gleichen Fehler "Proxy konnte nicht initialisiert werden" oder etwas anderes? (Ich habe den zusätzlichen HibernateAwareObjectMapper verwendet, der in den Artikeln mit Spring 3.2.6 und Hibernate 4.1.7 und Jackson 2.3 beschrieben ist.)
Indybee
1
Ich habs!! Seit Version 3.1.2 hat Spring seinen eigenen MappingJackson2HttpMessageConverter und hat fast das gleiche Verhalten wie im Tutorial, aber das Problem war, dass ich Spring Java Config verwende und mit Javaconfigs beginne. Ich habe versucht, einen @Bean MappingJackson2HttpMessageConverter zu erstellen , anstatt ihn zu den HttpMessageConverters of Spring hinzuzufügen, vielleicht ist es in der Antwort klarer :)
r1ckr
Ich bin

Antworten:

93

Ich habe endlich die Lösung gefunden! danke an indybee für den hinweis.

Das Tutorial Spring 3.1, Hibernate 4 und Jackson-Module-Hibernate bieten eine gute Lösung für Spring 3.1 und frühere Versionen. Da Version 3.1.2 Spring jedoch über einen eigenen MappingJackson2HttpMessageConverter verfügt, der fast die gleiche Funktionalität wie im Lernprogramm aufweist, müssen wir diesen benutzerdefinierten HTTPMessageConverter nicht erstellen.

Mit javaconfig müssen wir auch keinen HibernateAwareObjectMapper erstellen . Wir müssen nur das Hibernate4Module zu dem Standard- MappingJackson2HttpMessageConverter hinzufügen , den Spring bereits hat, und es zu den HttpMessageConverters der Anwendung hinzufügen, also müssen wir:

  1. Erweitern Sie unsere Spring-Konfigurationsklasse von WebMvcConfigurerAdapter und überschreiben Sie die Methode configureMessageConverters .

  2. Fügen Sie bei dieser Methode den MappingJackson2HttpMessageConverter mit dem in einer vorherigen Methode registrierten Hibernate4Module hinzu.

Unsere Konfigurationsklasse sollte folgendermaßen aussehen:

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

Wenn Sie eine XML-Konfiguration haben, müssen Sie auch keinen eigenen MappingJackson2HttpMessageConverter erstellen, aber Sie müssen den personalisierten Mapper erstellen, der im Lernprogramm angezeigt wird (HibernateAwareObjectMapper), sodass Ihre XML-Konfiguration folgendermaßen aussehen sollte:

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

Hoffe, diese Antwort ist verständlich und hilft jemandem, die Lösung für dieses Problem zu finden. Fragen können Sie gerne stellen!

r1ckr
quelle
3
Vielen Dank, dass Sie die Lösung geteilt haben. Nach einigen Versuchen stellte ich jedoch fest, dass der neueste Jackson im Moment ( 2.4.2 ) NICHT mit Spring 4.0.6 funktioniert. Ich habe immer noch den Fehler "keine Sitzung". Erst als ich die Jackson-Version auf 2.3.4 (oder 2.4.2) zurückgestuft habe, hat es angefangen zu funktionieren.
Robin
1
Vielen Dank, dass Sie MappingJackson2HttpMessageConverter darauf hingewiesen haben! Ich suchte eine Weile nach einer solchen Klasse.
LanceP
1
Vielen Dank! Ich habe es gerade getestet und es funktioniert mit Spring 4.2.4, Jackson 2.7.1-1 und JacksonDatatype 2.7.1 :)
Isthar
1
Dies löst das Problem in Spring Data REST nicht.
Alessandro C
1
Wie kann man das gleiche Problem lösen, wenn Springboot anstelle von Spring verwendet wird? Ich habe versucht, das Hibernate4-Modul (das ist die Version, die ich derzeit habe) zu registrieren, aber das hat mein Problem nicht gelöst.
Ranjith Gampa
34

Ab Spring 4.2 und unter Verwendung von Spring Boot und javaconfig ist das Registrieren des Hibernate4Module jetzt so einfach wie das Hinzufügen zu Ihrer Konfiguration:

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

ref: https://spring.io/blog/2014/12/02/latest-jackson-integration-improvements-in-spring

Chrismarx
quelle
19
Für Hibernate 5 (wie in der aktuellen Version von Spring Boot) ist dies der Fall Hibernate5Module. Es braucht zusätzlich eine Abhängigkeit von<groupId>com.fasterxml.jackson.datatype</groupId><artifactId>jackson-datatype-hibernate5</artifactId>
Zeemee
1
Arbeitete auch für mich an Spring Boot 2.1. Begann mich verrückt zu machen.
Van Dame
Es ist nicht klar, wie dies umgesetzt werden soll. Ich erhalte einen inkompatiblen Typfehler.
EarthMind
2
Genial! Arbeitete für mich mit Spring Boot 2.0.6 und Hibernate 5.
GarouDan
Das war sehr hilfreich. Es sollte darauf hingewiesen werden, dass das Registrieren der Hibernate5Module-Klasseninstanz als Bean nicht ausreicht. Wenn Sie es auf eine einzelne ObjectMapper-Instanz anwenden möchten, können Sie das Modul automatisch in Ihre von Spring verwaltete Klasse verdrahten und dann bei der ObjectMapper-Instanz registrieren. @Autowire Module jacksonHibernateModule; ObjectMapper mapper = neuer ObjectMapper (); mapper.registerModule (this.jacksonHibernateModule).
Gburgalum01
12

Dies ähnelt der von @rick akzeptierten Lösung.

Wenn Sie die vorhandene Konfiguration von Nachrichtenkonvertern nicht berühren möchten, können Sie einfach eine Jackson2ObjectMapperBuilderBean wie folgt deklarieren :

@Bean
public Jackson2ObjectMapperBuilder configureObjectMapper() {
    return new Jackson2ObjectMapperBuilder()
        .modulesToInstall(Hibernate4Module.class);
}

Vergessen Sie nicht, Ihrer Gradle-Datei (oder Maven) die folgende Abhängigkeit hinzuzufügen:

compile 'com.fasterxml.jackson.datatype:jackson-datatype-hibernate4:2.4.4'

Nützlich, wenn Sie eine haben Anwendung und Sie möchten die Möglichkeit behalten, Jackson-Funktionen aus der application.propertiesDatei zu ändern .

Luis Belloch
quelle
Wie können wir dies mithilfe einer application.propertiesDatei erreichen (Jackson konfigurieren, um ein verzögert geladenes Objekt zu vermeiden) ?
Hemu
1
Das wird hier nicht gesagt. Die Lösung von @Luis Belloch schlägt vor, dass Sie den Jackson2ObjectMapperBuilder erstellen, damit Sie den Standardnachrichtenkonverter von Spring Boots nicht überschreiben und weiterhin die application.properties-Werte verwenden, um die Jackson-Eigenschaften zu steuern.
Kapad
Entschuldigung ... Ich bin neu, wo würden Sie diese Methode implementieren? Mit Spring Boot Es verwendet Spring 5, das WebMvcConfigurerAdapter veraltet ist
Eric Huang
Hibernate4Moduleist Vermächtnis
Dmitry Kaltovich
8

Im Fall von Spring Data Rest müssen Sie, während die von @ r1ckr veröffentlichte Lösung funktioniert, abhängig von Ihrer Hibernate-Version nur eine der folgenden Abhängigkeiten hinzufügen:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-hibernate4</artifactId>
    <version>${jackson.version}</version>
</dependency>

oder

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-hibernate5</artifactId>
    <version>${jackson.version}</version>
</dependency>

Innerhalb von Spring Data Rest gibt es eine Klasse:

org.springframework.data.rest.webmvc.json.Jackson2DatatypeHelper

Dadurch wird das Modul beim Start der Anwendung automatisch erkannt und registriert.

Es gibt jedoch ein Problem:

Problem beim Serialisieren von Lazy @ManyToOne

Alan Hay
quelle
Wie konfigurieren Sie dies mit Spring Boot, wenn Sie Spring Data Rest nicht verwenden?
Eric Huang
Nicht genug. Sie brauchen auch@Bean hibernate5Module()
Dmitry Kaltovich
4

Wenn Sie XML - Konfigurations und Nutzung verwenden <annotation-driven />, fand ich , dass Sie nisten haben <message-converters>innen <annotation-driven>wie auf dem empfohlenen Jackson Github - Konto .

Wie so:

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

`

schwarzes Holz
quelle
XML-Konfiguration ist Vermächtnis
Dmitry Kaltovich
3

Für diejenigen, die hierher gekommen sind, um die Lösung für den Apache CXF-basierten RESTful-Service zu finden, ist die Konfiguration, die dies behebt, unten aufgeführt:

<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"/>

Wobei HibernateAwareObjectMapper definiert ist als:

public class HibernateAwareObjectMapper extends ObjectMapper {
    public HibernateAwareObjectMapper() {
        registerModule(new Hibernate5Module());
    }
}

Die folgende Abhängigkeit ist ab Juni 2016 erforderlich (vorausgesetzt, Sie verwenden Hibernate5):

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-hibernate5</artifactId>
    <version>2.7.4</version>
</dependency>  
user2968675
quelle
1

Die folgende Lösung ist für Spring 4.3 (ohne Boot) und Hibernate 5.1 vorgesehen, bei dem alle Fetchtypen fetch=FetchType.LAZYaus fetch=FetchType.EAGERLeistungsgründen von Fällen auf umgestellt wurden. Sofort sahen wir die com.fasterxml.jackson.databind.JsonMappingException: could not initialize proxyAusnahme aufgrund des Problems des verzögerten Ladens.

Zunächst fügen wir die folgende Maven-Abhängigkeit hinzu:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-hibernate5</artifactId>
</dependency>  

Anschließend wird unserer Java MVC-Konfigurationsdatei Folgendes hinzugefügt:

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

Anmerkungen:

  • Sie müssen das Hibernate5Module erstellen und konfigurieren, um ein ähnliches Verhalten wie bei Jackson ohne dieses Modul zu erzielen. Die Standardeinstellung macht inkompatible Annahmen.

  • Unsere WebMvcConfigurerAdapterenthält viele andere Konfigurationen und wir wollten eine andere Konfigurationsklasse vermeiden, weshalb wir die WebMvcConfigurationSupport#addDefaultHttpMessageConvertersFunktion, auf die in anderen Posts verwiesen wurde, nicht verwendet haben .

  • WebMvcConfigurerAdapter#configureMessageConvertersDeaktiviert die gesamte interne Konfiguration der Nachrichtenkonverter von Spring. Wir haben es vorgezogen, mögliche Probleme zu vermeiden.

  • Verwenden des extendMessageConvertersaktivierten Zugriffs auf alle automatisch konfigurierten Jackson-Klassen, ohne die Konfiguration aller anderen Nachrichtenkonverter zu verlieren.

  • Mit konnten getObjectMapper#registerModulewir die Hibernate5Modulezu den vorhandenen Konvertern hinzufügen .

  • Das Modul wurde sowohl dem JSON- als auch dem XML-Prozessor hinzugefügt

Dieser Zusatz löste das Problem mit dem Ruhezustand und dem verzögerten Laden, verursachte jedoch ein Restproblem mit dem generierten JSON-Format . Wie in diesem Github-Problem berichtet , ignoriert das Lazy-Load-Load-Modul von Hibernate-Jackson derzeit die Annotation @JsonUnwrapped, was zu potenziellen Datenfehlern führt. Dies geschieht unabhängig von der Einstellung der Kraftbelastungsfunktion. Das Problem besteht seit 2016.


Hinweis

Es scheint, dass der integrierte ObjectMapper durch Hinzufügen von Folgendes zu Klassen, die verzögert geladen werden, ohne Hinzufügen des Moduls hibernate5 funktioniert:

@JsonIgnoreProperties(  {"handler","hibernateLazyInitializer"} )
public class Anyclass {
sdw
quelle
Die Lösung könnte viel einfacher sein: kotlin @Bean fun hibernate5Module(): Module = Hibernate5Module()
Dmitry Kaltovich
0

Sie können den folgenden Helfer aus dem Spring Data Rest-Projekt verwenden:

Jackson2DatatypeHelper.configureObjectMapper(objectMapper);
Przemek Nowak
quelle
Nicht über die Frage
Dmitry Kaltovich
0

Ich habe eine sehr einfache Lösung für dieses Problem gefunden.

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

In meinem Fall habe ich den Fehler angegeben, weil ich ihn bei jeder Rückgabe von Abhängigkeiten als bewährte Methode in eine Liste konvertiert habe, die nicht geändert werden kann, die jedoch abhängig von der Methode, mit der ich die Instanz abgerufen habe, verzögert sein kann oder nicht (mit oder ohne Abruf) Ich führe einen Test durch, bevor er von Hibernate initialisiert wurde, und füge die Anmerkung hinzu, die das Serialisieren einer leeren Eigenschaft verhindert und mein Problem gelöst hat.

Pedro Bacchini
quelle
0

Ich habe den ganzen Tag versucht, das gleiche Problem zu lösen. Sie können dies tun, ohne die vorhandene Konfiguration der Nachrichtenkonverter zu ändern.

Meiner Meinung nach ist der einfachste Weg, dieses Problem nur mit 2 Schritten mit Hilfe von jackson-datatype-hibernate zu lösen :

Kotlin-Beispiel (wie Java):

  1. Hinzufügen build.gradle.kts:
implementation("com.fasterxml.jackson.datatype:jackson-datatype-hibernate5:$jacksonHibernate")
  1. Erstellen @Bean
   @Bean
   fun hibernate5Module(): Module = Hibernate5Module()

  • Beachten Sie, dass dies nicht der Fall Moduleistcom.fasterxml.jackson.databind.Modulejava.util.Module

  • Es empfiehlt sich auch, @JsonBackReference& @JsonManagedReferencezu @OneToMany& @ManyToOneBeziehungen hinzuzufügen . @JsonBackReferencekönnte nur 1 in der Klasse sein.

Dmitry Kaltovich
quelle
-1

Obwohl sich diese Frage geringfügig von der folgenden unterscheidet: Eine seltsame Jackson-Ausnahme, die beim Serialisieren des Hibernate-Objekts ausgelöst wird , kann das zugrunde liegende Problem auf dieselbe Weise mit diesem Code behoben werden:

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

Ich habe es versucht und gearbeitet:

// benutzerdefinierte Konfiguration für verzögertes Laden

public static class HibernateLazyInitializerSerializer extends JsonSerializer<JavassistLazyInitializer> {

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

und Mapper konfigurieren:

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

Eine weitere Lösung für Spring Boot: Konfiguration: spring.jpa.open-in-view=true

Bianbian
quelle
Das würde nicht helfen
Dmitry Kaltovich
-1

Ich habe die nützliche Antwort von @ rick ausprobiert, bin jedoch auf das Problem gestoßen, dass "bekannte Module" wie jackson-datatype-jsr310 nicht automatisch registriert wurden, obwohl sie sich im Klassenpfad befanden. (Dieser Blog-Beitrag erklärt die automatische Registrierung.)

Um die Antwort von @ rick zu erweitern, sehen Sie hier eine Variation, bei der Spring's Jackson2ObjectMapperBuilder zum Erstellen des ObjectMapper. Dadurch werden die "bekannten Module" automatisch registriert und zusätzlich zur Installation der Funktionen bestimmte Funktionen festgelegt 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);
    }
}
Markus Pscheidt
quelle
Haben Sie ein Update zu diesem Thema? Ich habe das Problem derzeit. Natürlich, wenn ich das hinzufüge hibernate5Module(weil ich Ruhezustand 5 verwende), ignoriert Jackson den Proxy von nicht initialisierten Objekten, aber es scheint, dass die "bekannten Module" nicht registriert werden, obwohl ich Ihren Ansatz verwende. Ich weiß das, weil einige Teile meiner Anwendung fehlerhaft sind und wenn ich das Modul entferne, funktionieren sie wieder.
Daniel Henao
@DanielHenao Hast du spring.io/blog/2014/12/02/… studiert ? Ich wechselte zu Hibernate5 und verwendete die DelegatingWebMvcConfigurationKlasse (anstelle von WebMvcConfigurerAdapter), wie vom Jackson-Antpath-Filter vorgeschlagen :@Override public void configureMessageConverters(List<HttpMessageConverter<?>> messageConverters) { messageConverters.add(jacksonMessageConverter()); addDefaultHttpMessageConverters(messageConverters); }
Markus Pscheidt
1. Hibernate4Moduleist Vermächtnis. 2 SerializationFeature.WRITE_DATES_AS_TIMESTAMPS- nicht über die Frage
Dmitry Kaltovich
@DmitryKaltovich ad 1) es war zum Zeitpunkt des Schreibens kein Vermächtnis; ad 2) ok, entfernt.
Markus Pscheidt