wake-up-neo.com

Vermeiden Sie die Jackson-Serialisierung bei nicht abgerufenen Lazy-Objekten

Ich habe einen einfachen Controller, der ein Benutzerobjekt zurückgibt. Dieser Benutzer verfügt über Attributkoordinaten, die die Hibernate-Eigenschaft FetchType.LAZY aufweisen.

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

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

Dies ist darauf zurückzuführen, 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;

}

Es gibt eine Möglichkeit, Jackson anzuweisen, die nicht abgerufenen Objekte nicht zu serialisieren. Ich habe nach anderen Antworten gesucht, die vor 3 Jahren gepostet wurden und das Jackson-Hibernate-Modul implementieren. Aber es könnte wahrscheinlich mit einer neuen Jackson-Funktion erreicht werden.

Meine Versionen sind:

  • Frühling 3.2.5 
  • Ruhezustand 4.1.7 
  • Jackson 2.2

Danke im Voraus.

60
r1ckr

Ich habe endlich die Lösung gefunden! Vielen Dank an Indybee für die Hinweise.

Das Tutorial Spring 3.1, Hibernate 4 und Jackson-Module-Hibernate bietet eine gute Lösung für Spring 3.1 und frühere Versionen. Aber seit Version 3.1.2 hat Spring seinen eigenen MappingJackson2HttpMessageConverter mit fast derselben Funktionalität wie die im Tutorial, sodass wir diesen benutzerdefinierten HTTPMessageConverter nicht erstellen müssen.

Mit javaconfig brauchen wir auch keinen HibernateAwareObjectMapper zu erstellen, wir müssen lediglich das Hibernate4Module zum Standard MappingJackson2HttpMessageConverter hinzufügen, den Spring bereits hat, und ihn den HttpMessageConverters der Anwendung hinzufügen. also müssen wir:

  1. Erweitern Sie unsere Spring-Config-Klasse um WebMvcConfigurerAdapter und überschreiben Sie die Methode configureMessageConverters

  2. Fügen Sie bei dieser Methode den MappingJackson2HttpMessageConverter mit dem Hibernate4Module hinzu, das in einer previus-Methode registriert ist.

Unsere config-Klasse sollte so 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 über eine XML-Konfiguration verfügen, müssen Sie auch keinen eigenen MappingJackson2HttpMessageConverter erstellen. Sie müssen jedoch den personalisierten Mapper erstellen, der im Lernprogramm (HibernateAwareObjectMapper) angezeigt wird. Ihre XML-Konfiguration sollte daher wie folgt aussehen:

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

Hoffen Sie, dass diese Antwort verständlich ist und jemandem hilft, die Lösung für dieses Problem zu finden. Fragen stehen Ihnen gerne zur Verfügung!

88
r1ckr

Ab Spring 4.2 und mit 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

22
chrismarx

Dies ist der von @rick akzeptierten Lösung ähnlich.

Wenn Sie die Konfiguration der vorhandenen Nachrichtenkonverter nicht berühren möchten, können Sie einfach eine Jackson2ObjectMapperBuilder-Bean 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 spring-boot -Anwendung haben und die Möglichkeit haben möchten, Jackson-Funktionen von der application.properties-Datei zu ändern.

14
Luis Belloch

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

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

In 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

6
Alan Hay

Wenn Sie die XML-Konfiguration verwenden und <annotation-driven /> verwenden, habe ich festgestellt, dass Sie <message-converters> in <annotation-driven> wie in dem Jackson Github-Konto empfohlen verschachteln müssen.

So wie:

<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

Für diejenigen, die hierher gekommen sind, um nach einer Lösung für den Apache CXF-basierten RESTful-Service zu suchen, finden Sie die folgende Konfiguration:

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

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

Jackson2DatatypeHelper.configureObjectMapper(objectMapper);
1
Przemek Nowak

Obwohl diese Frage etwas anders ist als die folgende: Beim Serialisieren des Hibernate-Objekts wird eine seltsame Jackson-Ausnahme ausgelöst , das zugrunde liegende Problem kann mit diesem Code auf dieselbe Weise behoben werden:

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

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 ausgegeben, weil ich ihn, wenn ich Abhängigkeiten zurückgab, als bewährte Methode in eine Liste konvertiert habe, die nicht geändert werden kann, aber in Abhängigkeit von der Methode, mit der ich die Instanz abgerufen habe, möglicherweise nicht verzögert ist (mit oder ohne Fetch) 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 löst.

0
Pedro Bacchini

Die folgende Lösung ist für Spring 4.3, (Nicht-Boot) und Hibernate 5.1, bei der alle Fetchtypen aus Gründen von fetch=FetchType.LAZY aus Gründen der Leistung auf fetch=FetchType.EAGER umgestellt wurden. Sofort sahen wir die com.fasterxml.jackson.databind.JsonMappingException: could not initialize proxy-Ausnahmebedingung aufgrund des Lazy-Load-Problems. 

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

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

Dann wird der 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 Jackson ohne dieses Modul zu erhalten. Die Standardeinstellung macht inkompatible Annahmen.

  • Unsere WebMvcConfigurerAdapter enthält eine Menge anderer Konfigurationen und wir wollten eine andere Konfigurationsklasse vermeiden, weshalb wir die WebMvcConfigurationSupport#addDefaultHttpMessageConverters-Funktion nicht verwendet haben, auf die in anderen Beiträgen verwiesen wurde.

  • WebMvcConfigurerAdapter#configureMessageConverters deaktiviert die gesamte interne Konfiguration von Nachrichtenkonvertern von Spring. Wir haben es vorgezogen, mögliche Probleme in dieser Hinsicht zu vermeiden. 

  • Durch die Verwendung von extendMessageConverters ist der Zugriff auf alle automatisch konfigurierten Jackson-Klassen aktiviert, ohne dass die Konfiguration aller anderen Nachrichtenkonverter verloren geht.

  • Mit getObjectMapper#registerModule konnten wir den Hibernate5Module zu 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 Hibernate und Lazy Loading, verursachte jedoch ein -Restproblem mit dem generierten JSON-Format. Wie in diesem github-Problem berichtet, ignoriert das Lazy-Load-Modul "Hibernate-Jackson" derzeit die @JsonUnwrapped-Annotation, was zu potenziellen Datenfehlern führen kann. Dies geschieht unabhängig von der Einstellung der Kraftladefunktion. Das Problem besteht seit 2016. 


Hinweis

Es scheint, dass der eingebaute ObjectMapper durch das Hinzufügen von Folgendes zu Klassen, die faul geladen werden, ohne das Hibernate5-Modul hinzufügt:

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

Ich versuchte die nützliche Antwort von @ rick , stieß jedoch auf das Problem, dass "bekannte Module" wie jackson-datatype-jsr310 nicht automatisch registriert wurden, obwohl sie im Klassenpfad waren. (Dieser Blogbeitrag erklärt die automatische Registrierung.)

Um die Antwort von @ rick zu erweitern, wird hier eine Variation verwendet, die Spring's Jackson2ObjectMapperBuilder verwendet, um die ObjectMapper zu erstellen. Dadurch werden die "bekannten Module" automatisch registriert und zusätzlich zur Installation von Hibernate4Module bestimmte Funktionen festgelegt.

@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

Ich habe das ausprobiert und gearbeitet:

// Benutzerdefinierte Konfiguration für das langsame Laden

public static class HibernateLazyInitializerSerializer extends JsonSerializer<JavassistLazyInitializer> {

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

und konfigurieren Sie Mapper:

    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