Evite la serialización de Jackson en objetos perezosos no recuperados

Tengo un controlador simple que devuelve un objeto Usuario, este usuario tiene unas coordenadas de atributo que tienen la propiedad de hibernación FetchType.LAZY.

Cuando trato de obtener este usuario, siempre tengo que cargar todas las coordenadas para obtener el objeto del usuario; de lo contrario, cuando Jackson intente serializar, el usuario arroja la excepción:

com.fasterxml.jackson.databind.JsonMappingException: no se pudo inicializar el proxy – sin sesión

Esto se debe a que Jackson está tratando de recuperar este objeto no alcanzado. Aquí están los objetos:

public class User{ @OneToMany(fetch = FetchType.LAZY, mappedBy = "user") @JsonManagedReference("user-coordinate") private List coordinates; } public class Coordinate { @ManyToOne @JoinColumn(name = "user_id", nullable = false) @JsonBackReference("user-coordinate") private User user; } 

Y el controlador:

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

¿Hay alguna forma de decirle a Jackson que no serialice los objetos que no se han caído? He estado buscando otras respuestas publicadas hace 3 años implementando el módulo jackson-hibernate. Pero probablemente se podría lograr con una nueva función de jackson.

Mis versiones son:

  • Primavera 3.2.5
  • Hibernate 4.1.7
  • Jackson 2.2

Gracias por adelantado.

¡Finalmente encontré la solución! gracias a indybee por darme una pista.

El tutorial Spring 3.1, Hibernate 4 y Jackson-Module-Hibernate tienen una buena solución para Spring 3.1 y versiones anteriores. Pero desde la versión 3.1.2 Spring tiene su propio MappingJackson2HttpMessageConverter con casi la misma funcionalidad que la del tutorial, por lo que no es necesario crear este HTTPMessageConverter personalizado.

Con javaconfig no es necesario crear un HibernateAwareObjectMapper , solo tenemos que agregar Hibernate4Module al MappingJackson2HttpMessageConverter predeterminado que Spring ya tiene y agregarlo a los HttpMessageConverters de la aplicación, por lo que debemos:

  1. Extienda nuestra clase de configuración de Spring desde WebMvcConfigurerAdapter y anule el método configureMessageConverters .

  2. En ese método, agregue el MappingJackson2HttpMessageConverter con el Hibernate4Module registrado en un método anterior.

Nuestra clase de configuración debería verse así:

 @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> converters) { //Here we add our custom-configured HttpMessageConverter converters.add(jacksonMessageConverter()); super.configureMessageConverters(converters); } //More configuration.... } 

Si tiene una configuración xml, tampoco necesita crear su propio MappingJackson2HttpMessageConverter, pero sí necesita crear el asignador personalizado que aparece en el tutorial (HibernateAwareObjectMapper), por lo que su configuración xml debería verse así:

        

Espero que esta respuesta sea comprensible y ayude a alguien a encontrar la solución para este problema, ¡cualquier pregunta puede formularla!

A partir de Spring 4.2 y utilizando Spring Boot y javaconfig, registrar Hibernate4Module ahora es tan simple como agregar esto a su configuración:

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

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

Esto es similar a la solución aceptada por @rick.

Si no desea tocar la configuración de convertidores de mensajes existentes, puede declarar un bean Jackson2ObjectMapperBuilder como:

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

No olvide agregar la siguiente dependencia a su archivo Gradle (o Maven):

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

Es útil si tiene una aplicación de arranque de resorte y desea mantener la capacidad de modificar las características de Jackson desde el archivo application.properties .

En el caso de Spring Data Rest, mientras que la solución publicada por @ r1ckr funciona, todo lo que se necesita es agregar una de las siguientes dependencias dependiendo de su versión de Hibernate:

  com.fasterxml.jackson.datatype jackson-datatype-hibernate4 ${jackson.version}  

o

  com.fasterxml.jackson.datatype jackson-datatype-hibernate5 ${jackson.version}  

Dentro de Spring Data Rest hay una clase:

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

que detectará automáticamente y registrará el Módulo en la puesta en marcha de la aplicación.

Sin embargo, hay un problema:

Problema Serializing Lazy @ManyToOne

Si usa la configuración XML y usa , descubrí que debe anidar dentro de como se recomienda en la cuenta de Jackson Github .

Al igual que:

          

`

Para aquellos que vinieron aquí buscando encontrar la solución para el servicio RESTful basado en Apache CXF, la configuración que lo corrige está a continuación:

       

Donde HibernateAwareObjectMapper se define como:

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

Se requiere la siguiente dependencia a partir de junio de 2016 (siempre que esté utilizando Hibernate5):

  com.fasterxml.jackson.datatype jackson-datatype-hibernate5 2.7.4  

Puede usar la siguiente ayuda del proyecto Spring Data Rest:

 Jackson2DatatypeHelper.configureObjectMapper(objectMapper); 

Probé la respuesta útil de @rick , pero me encontré con el problema de que los “módulos conocidos” como jackson-datatype-jsr310 no se registraron automáticamente a pesar de estar en el classpath. (Esta publicación del blog explica el registro automático).

Ampliando la respuesta de @rick , aquí hay una variación usando Spring’s Jackson2ObjectMapperBuilder para crear el ObjectMapper . Esto registra automáticamente los “módulos conocidos” y establece ciertas funciones además de la instalación del 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> converters) { converters.add(jacksonMessageConverter()); super.configureMessageConverters(converters); } } 

Aunque esta pregunta es ligeramente diferente a esta: excepción de Strange Jackson lanzada al serializar el objeto Hibernate , el problema subyacente se puede solucionar de la misma manera con este código:

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

Intenté esto y trabajé:

// configuración personalizada para la carga diferida

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

y configura mapper:

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