web-dev-qa-db-fra.com

Eviter la sérialisation de Jackson sur des objets paresseux non récupérés

J'ai un contrôleur simple qui retourne un objet User, cet utilisateur a des coordonnées d'attribut qui ont la propriété hibernate FetchType.LAZY.

Lorsque j'essaie d'obtenir cet utilisateur, je dois toujours charger toutes les coordonnées pour obtenir l'objet utilisateur. Sinon, lorsque Jackson tente de sérialiser l'utilisateur, il lève l'exception:

com.fasterxml.jackson.databind.JsonMappingException: impossible d'initialiser le proxy - pas de session

Cela est dû au fait que Jackson tente de récupérer cet objet non récupéré. Voici les objets:

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

Et le contrôleur:

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

    User user = userService.getUser(username);

    return user;

}

Il y a un moyen de dire à Jackson de ne pas sérialiser les objets non récupérés? J'ai cherché d'autres réponses postées il y a 3 ans en implémentant le module jackson-hibernate. Mais cela pourrait probablement être réalisé avec une nouvelle fonctionnalité jackson.

Mes versions sont:

  • Printemps 3.2.5 
  • Hibernate 4.1.7 
  • Jackson 2.2

Merci d'avance.

60
r1ckr

J'ai enfin trouvé la solution! merci à indybee de me donner un indice.

Le tutoriel Spring 3.1, Hibernate 4 et Jackson-Module-Hibernate propose une bonne solution pour Spring 3.1 et les versions antérieures. Mais depuis la version 3.1.2, Spring a son propre MappingJackson2HttpMessageConverter avec presque les mêmes fonctionnalités que celles du didacticiel. Il n’est donc pas nécessaire de créer ce HTTPMessageConverter personnalisé.

Avec javaconfig, nous n’avons pas besoin de créer un HibernateAwareObjectMapper aussi, il nous suffit d’ajouter le Hibernate4Module à la valeur par défaut MappingJackson2HttpMessageConverter et de l’ajouter au HttpMessageConverters par défaut il faut donc:

  1. Étendez notre classe de configuration printanière à partir de WebMvcConfigurerAdapter et substituez la méthode configureMessageConverters

  2. Sur cette méthode, ajoutez le MappingJackson2HttpMessageConverter avec le Hibernate4Module enregistré dans une méthode previus.

Notre classe de configuration devrait ressembler à ceci:

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

Si vous avez une configuration XML, vous n'avez pas besoin de créer votre propre MappingJackson2HttpMessageConverter, mais vous devez créer le mappeur personnalisé qui apparaît dans le tutoriel (HibernateAwareObjectMapper), votre configuration xml devrait donc ressembler à ceci:

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

J'espère que cette réponse sera compréhensible et aidera quelqu'un à trouver la solution à ce problème, toutes les questions sont libres de les poser!

88
r1ckr

A partir de Spring 4.2 et en utilisant Spring Boot et javaconfig, l'enregistrement de Hibernate4Module est désormais aussi simple que de l'ajouter à votre configuration:

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

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

22
chrismarx

Ceci est similaire à la solution acceptée par @rick.

Si vous ne voulez pas toucher à la configuration des convertisseurs de messages existants, vous pouvez simplement déclarer un bean Jackson2ObjectMapperBuilder tel que:

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

N'oubliez pas d'ajouter la dépendance suivante à votre fichier Gradle (ou Maven):

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

Utile si vous avez une application spring-boot et que vous souhaitez conserver la possibilité de modifier les fonctionnalités de Jackson à partir du fichier application.properties.

14
Luis Belloch

Dans le cas de Spring Data Rest, alors, bien que la solution publiée par @ r1ckr fonctionne, il suffit d'ajouter l'une des dépendances suivantes en fonction de votre version d'Hibernate:

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

ou

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

Dans Spring Data Rest, il y a une classe:

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

qui détectera et enregistrera automatiquement le module au démarrage de l’application.

Il y a cependant un problème:

Issue Serializing Lazy @ManyToOne

6
Alan Hay

Si vous utilisez la configuration XML et utilisez <annotation-driven />, j'ai constaté que vous devez imbriquer <message-converters> dans <annotation-driven> comme recommandé sur le compte Jackson Github .

Ainsi:

<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

Pour ceux qui sont venus ici et qui cherchent la solution pour le service RESTful basé sur Apache CXF, la configuration qui la résout est la suivante:

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

Où HibernateAwareObjectMapper est défini comme:

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

La dépendance suivante est requise à partir de juin 2016 (à condition que vous utilisiez Hibernate5):

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

Vous pouvez utiliser l’assistant suivant du projet Spring Data Rest:

Jackson2DatatypeHelper.configureObjectMapper(objectMapper);
1
Przemek Nowak

Bien que cette question soit légèrement différente de celle-ci: Une exception étrange de Jackson est émise lors de la sérialisation d'un objet Hibernate , le problème sous-jacent peut être résolu de la même manière avec ce code:

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

J'ai fait une solution très simple à ce problème.

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

Dans mon cas, je donnais l'erreur parce que chaque fois que je renvoyais une suspension, c'était une bonne pratique que je convertissais cette liste en une liste qui ne pouvait pas être modifiée, mais comment elle pouvait être ou non paresseuse en fonction de la méthode utilisée pour obtenir l'instance. (avec ou sans récupération), je fais un test avant qu'il ne soit initialisé par Hibernate et ajoute l'annotation qui empêche la sérialisation d'une propriété vide et qui a résolu mon problème.

0
Pedro Bacchini

La solution suivante est pour Spring 4.3, (non-boot) et Hibernate 5.1 où nous avons transféré tous les types de fetch à fetch=FetchType.LAZY à partir des observations de fetch=FetchType.EAGER pour des raisons de performances. Nous avons immédiatement vu l'exception com.fasterxml.jackson.databind.JsonMappingException: could not initialize proxy en raison du problème de charge paresseuse. 

Tout d'abord, nous ajoutons la dépendance maven suivante:

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

Ensuite, les éléments suivants sont ajoutés à notre fichier de configuration 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;
}

Remarques:

  • Vous devez créer et configurer Hibernate5Module pour obtenir un comportement similaire à Jackson sans ce module. La valeur par défaut fait des hypothèses incompatibles.

  • Notre WebMvcConfigurerAdapter contient beaucoup d'autres configurations et nous voulions éviter une autre classe de configuration. C'est pourquoi nous n'avons pas utilisé la fonction WebMvcConfigurationSupport#addDefaultHttpMessageConverters à laquelle il a été fait référence dans d'autres publications.

  • WebMvcConfigurerAdapter#configureMessageConverters désactive toute la configuration interne des convertisseurs de messages de Spring. Nous avons préféré éviter les problèmes potentiels autour de cela. 

  • L’utilisation de extendMessageConverters permet d’accéder à toutes les classes de Jackson configurées automatiquement sans perdre la configuration de tous les autres convertisseurs de message.

  • En utilisant getObjectMapper#registerModule, nous avons pu ajouter le Hibernate5Module aux convertisseurs existants. 

  • Le module a été ajouté aux processeurs JSON et XML.

Cet ajout a résolu le problème de Hibernate et du chargement différé, mais a entraîné un problème résiduel de avec le format JSON généré. Comme indiqué dans ce problème de github }, le module de chargement paresseux hibernate-jackson ignore actuellement l'annotation @JsonUnwrapped, ce qui pourrait entraîner des erreurs de données. Cela se produit quel que soit le réglage de la fonctionnalité de chargement forcé. Le problème existe depuis 2016. 


Note

Il semble qu'en ajoutant les éléments suivants aux classes chargées paresseux, ObjectMapper intégré fonctionne sans ajouter le module hibernate5:

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

J'ai essayé la réponse utile de @ rick , mais je me suis heurté au problème que des "modules connus" tels que jackson-datatype-jsr310 n'étaient pas automatiquement enregistrés, même s'ils étaient sur le chemin de classe. (Cet article de blog explique l'enregistrement automatique.)

En développant la réponse de @ rick, voici une variante utilisant/ Jackson2ObjectMapperBuilder de Spring pour créer la variable ObjectMapper. Cela enregistre automatiquement les "modules connus" et définit certaines fonctionnalités en plus de l'installation du 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

J'ai essayé cela et j'ai travaillé:

// configuration personnalisée pour chargement paresseux

public static class HibernateLazyInitializerSerializer extends JsonSerializer<JavassistLazyInitializer> {

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

et configurez le mappeur:

    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