避免对未获取的惰性对象进行Jackson序列化

r1c*_*ckr 75 java serialization spring hibernate jackson

我有一个简单的控制器返回一个User对象,这个用户的属性坐标有hibernate属性FetchType.LAZY.

当我尝试获取此用户时,我总是必须加载所有坐标以获取用户对象,否则当Jackson尝试序列化时,用户会抛出异常:

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;
}
Run Code Online (Sandbox Code Playgroud)

和控制器:

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

    User user = userService.getUser(username);

    return user;

}
Run Code Online (Sandbox Code Playgroud)

有办法告诉杰克逊不要序列化未被攻击的物体吗?我一直在寻找3年前发布的其他答案实现jackson-hibernate-module.但可能通过新的杰克逊功能可以实现.

我的版本是:

  • 春天3.2.5
  • Hibernate 4.1.7
  • 杰克逊2.2

提前致谢.

r1c*_*ckr 90

我终于找到了解决方案!感谢indybee给我一个线索.

Spring 3.1,Hibernate 4和Jackson-Module-Hibernate教程为Spring 3.1和早期版本提供了一个很好的解决方案.但是,自3.1.2版本以来,Spring拥有自己的MappingJackson2HttpMessageConverter,它具有与教程中几乎相同的功能,因此我们不需要创建这个自定义HTTPMessageConverter.

使用javaconfig我们也不需要创建HibernateAwareObjectMapper,我们只需要将Hibernate4Module添加到Spring已经拥有的默认MappingJackson2HttpMessageConverter并将其添加到应用程序的HttpMessageConverters中,因此我们需要:

  1. WebMvcConfigurerAdapter扩展我们的spring配置类并覆盖configureMessageConverters方法.

  2. 在该方法上添加MappingJackson2HttpMessageConverter和在previus方法中注册的Hibernate4Module.

我们的配置类应如下所示:

@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....
}
Run Code Online (Sandbox Code Playgroud)

如果你有一个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>
Run Code Online (Sandbox Code Playgroud)

希望这个答案是可以理解的,并帮助有人找到解决这个问题的方法,任何问题随时都可以问!

  • 感谢您分享解决方案.然而经过几次尝试后,我发现最新的杰克逊(_2.4.2_)确实_NOT_与春季4.0.6一起工作.我仍然有"无会话"错误.只有当我将杰克逊版本降级回2.3.4(或2.4.2)时,才开始工作. (3认同)
  • 这并不能解决 Spring Data REST 中的问题。 (2认同)

chr*_*arx 28

从Spring 4.2开始,使用Spring Boot和javaconfig,现在注册Hibernate4Module就像在配置中添加它一样简单:

@Bean
public Module datatypeHibernateModule() {
  return new Hibernate4Module();
}
Run Code Online (Sandbox Code Playgroud)

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

  • 对于Hibernate 5(如当前版本的Spring Boot),它是`Hibernate5Module`.它还需要依赖于`<groupId> com.fasterxml.jackson.datatype </ groupId> <artifactId> jackson-datatype-hibernate5 </ artifactId>` (17认同)
  • 惊人的!使用 Spring Boot 2.0.6 和 Hibernate 5 为我工作。 (2认同)

小智 14

这类似于@rick接受的解决方案.

如果您不想触及现有的消息转换器配置,您可以只声明一个Jackson2ObjectMapperBuilderbean:

@Bean
public Jackson2ObjectMapperBuilder configureObjectMapper() {
    return new Jackson2ObjectMapperBuilder()
        .modulesToInstall(Hibernate4Module.class);
}
Run Code Online (Sandbox Code Playgroud)

不要忘记将以下依赖项添加到Gradle文件(或Maven):

compile 'com.fasterxml.jackson.datatype:jackson-datatype-hibernate4:2.4.4'
Run Code Online (Sandbox Code Playgroud)

如果您有一个应用程序并且希望保留从application.properties文件修改Jackson功能的功能,则非常有用.


Dmi*_*ich 7

我花了一整天的时间试图解决同样的问题。您无需更改现有消息转换器配置即可完成此操作。

在我看来,在jackson-datatype-hibernate的帮助下,解决这个问题的最简单方法只需 2 个步骤:

kotlin 示例(与 java 相同):

  1. 加入build.gradle.kts
implementation("com.fasterxml.jackson.datatype:jackson-datatype-hibernate5:$jacksonHibernate")
Run Code Online (Sandbox Code Playgroud)
  1. 创造@Bean
   @Bean
   fun hibernate5Module(): Module = Hibernate5Module()
Run Code Online (Sandbox Code Playgroud)
  • 注意,Modulecom.fasterxml.jackson.databind.Module,不是java.util.Module

  • 另外一个好的做法是将@JsonBackReference&添加@JsonManagedReference@OneToMany&@ManyToOne关系中。@JsonBackReference班级里可能只有1个人。


Ala*_*Hay 6

在Spring Data Rest的情况下,当@ r1ckr发布的解决方案有效时,所需要的只是根据您的Hibernate版本添加以下依赖项之一:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-hibernate4</artifactId>
    <version>${jackson.version}</version>
</dependency>
Run Code Online (Sandbox Code Playgroud)

要么

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-hibernate5</artifactId>
    <version>${jackson.version}</version>
</dependency>
Run Code Online (Sandbox Code Playgroud)

在Spring Data Rest中有一个类:

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

它将在应用程序启动时自动检测并注册模块.

但是有一个问题:

问题序列化Lazy @ManyToOne


bla*_*ood 5

如果您使用XML配置和使用<annotation-driven />,我发现您有窝<message-converters><annotation-driven>的建议杰克逊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>
Run Code Online (Sandbox Code Playgroud)

`


sdw*_*sdw 5

以下解决方案适用于 Spring 4.3(非引导)和 Hibernate 5.1,出于性能原因,我们将所有 fetchtypes 转换为fetch=FetchType.LAZYfrom case fetch=FetchType.EAGERcom.fasterxml.jackson.databind.JsonMappingException: could not initialize proxy由于延迟加载问题,我们立即看到了异常。

首先,我们添加以下maven依赖:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-hibernate5</artifactId>
</dependency>  
Run Code Online (Sandbox Code Playgroud)

然后将以下内容添加到我们的 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;
}
Run Code Online (Sandbox Code Playgroud)

笔记:

  • 您需要创建并配置 Hibernate5Module 才能获得与没有此模块的 Jackson 类似的行为。默认值做出了不兼容的假设。

  • 我们的WebMvcConfigurerAdapter里面有很多其他配置,我们想避免另一个配置类,这就是为什么我们没有使用WebMvcConfigurationSupport#addDefaultHttpMessageConverters其他帖子中提到的函数。

  • WebMvcConfigurerAdapter#configureMessageConverters禁用 Spring 消息转换器的所有内部配置。我们宁愿避免与此相关的潜在问题。

  • 使用extendMessageConverters对所有自动配置的 Jackson 类的启用访问,而不会丢失所有其他消息转换器的配置。

  • 使用getObjectMapper#registerModule我们能够将其添加Hibernate5Module到现有的转换器中。

  • 该模块已添加到 JSON 和 XML 处理器中

此添加解决了 Hibernate 和延迟加载的问题,但导致了生成的 JSON 格式的残留问题。正如本 github 问题中所报告的,hibernate-jackson 延迟加载模块当前忽略 @JsonUnwrapped 注释,从而导致潜在的数据错误。无论强制加载功能设置如何,都会发生这种情况。这个问题自2016年以来就一直存在。


笔记

看来,通过将以下内容添加到延迟加载的类中,内置 ObjectMapper 无需添加 hibernate5 模块即可工作:

@JsonIgnoreProperties(  {"handler","hibernateLazyInitializer"} )
public class Anyclass {
Run Code Online (Sandbox Code Playgroud)