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


83

我有一个返回用户对象的简单控制器,该用户的属性坐标具有休眠属性FetchType.LAZY。

当我尝试获取该用户时,我总是必须加载所有坐标才能获取用户对象,否则,当杰克逊尝试序列化User时,将抛出异常:

com.fasterxml.jackson.databind.JsonMappingException:无法初始化代理-没有会话

这是由于Jackson试图获取此未获取的对象。这里是对象:

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

和控制器:

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

    User user = userService.getUser(username);

    return user;

}

有没有办法告诉杰克逊不要序列化未提取的对象?我一直在寻找3年前发布的实现jackson-hibernate-module的其他答案。但是可能可以通过新的杰克逊功能来实现。

我的版本是:

  • 春天3.2.5
  • 休眠4.1.7
  • 杰克逊2.2

提前致谢。



谢谢indybee,我一直在看教程,春季3.2.5已经有了一个MappingJackson2HttpMessageConverter,但是我不能用它来避免未获取的惰性对象,而且我也尝试在教程中实现一个对象,但是没有实现。 ..
r1ckr 2014年

1
您是否收到相同的错误“无法初始化代理”或其他错误?(我使用Spring 3.2.6,Hibernate 4.1.7和jackson 2.3的文章中描述的其他HibernateAwareObjectMapper)
indybee 2014年

1
得到它了!!从版本3.1.2开始,Spring具有自己的MappingJackson2HttpMessageConverter,其行为与本教程中的行为几乎相同,但是问题是我正在使用Spring java config,并且从javaconfigs开始。我试图制作一个@Bean MappingJackson2HttpMessageConverter,而不是将其添加到Spring的HttpMessageConverters中,也许在答案中更清楚了:)
r1ckr 2014年

很高兴它正在工作:)
indybee 2014年

Answers:


93

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

Spring 3.1,Hibernate 4和Jackson-Module-Hibernate教程为Spring 3.1和早期版本提供了一个很好的解决方案。但由于Spring 3.1.2版具有自己的MappingJackson2HttpMessageConverter,其功能与本教程中的功能几乎相同,因此我们无需创建此自定义HTTPMessageConverter。

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

  1. WebMvcConfigurerAdapter扩展我们的spring config类,并重写configureMessageConverters方法。

  2. 在该方法中添加MappingJackson2HttpMessageConverterHibernate4Module在previus方法进行注册。

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

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

如果您具有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>

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


3
感谢您分享解决方案。试了几次后,但是,我发现,在目前(最新杰克逊2.4.2)确实具有弹簧4.0.6工作。我仍然有“无会话”错误。只有当我将杰克逊版本降级到2.3.4(或2.4.2)时,它才开始起作用。
罗宾(Robin)

1
感谢您指出MappingJackson2HttpMessageConverter!我有一段时间寻找这样的课程。
LanceP

1
非常感谢你!我刚刚测试了它及其与Spring 4.2.4,Jackson 2.7.1-1和JacksonDatatype 2.7.1一起使用的能力:)
Isthar

1
这不能解决Spring Data REST中的问题。
Alessandro C

1
如果使用Springboot代替Spring,如何解决同一问题?我已经尝试注册Hibernate4模块(这是我当前使用的版本),但是并不能解决我的问题。
ranjith Gampa,

34

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

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

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


19
对于Hibernate 5(与当前版本的Spring Boot一样),它是Hibernate5Module。它还需要依赖<groupId>com.fasterxml.jackson.datatype</groupId><artifactId>jackson-datatype-hibernate5</artifactId>
Zeemee'1

1
在Spring Boot 2.1上也为我工作。开始让我发疯。
Van Dame '18

目前尚不清楚如何实现这一目标。我收到不兼容的类型错误。
EarthMind

2
太棒了!使用Spring Boot 2.0.6和Hibernate 5为我工作
。– GarouDan

这非常有帮助。应该指出的是,将Hibernate5Module类实例注册为bean是不够的。如果希望将其应用于单个ObjectMapper实例,则可以将该模块自动连接到Spring托管的类中,然后在ObjectMapper实例中注册它。@Autowire模块jacksonHibernateModule; ObjectMapper映射器=新的ObjectMapper(); mapper.registerModule(this.jacksonHibernateModule)。
gburgalum01

12

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

如果您不想修改现有的消息转换器配置,则可以声明一个Jackson2ObjectMapperBuilderbean,例如:

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

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

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

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


我们如何使用application.properties文件实现此目的(配置Jackson以避免延迟加载对象)?
hemu

1
这不是这里所说的。@Luis Belloch的解决方案建议您创建Jackson2ObjectMapperBuilder,以便不覆盖Spring Boot默认消息转换器,而继续使用application.properties值来控制jackson属性。
kapad

抱歉...我是新手,您将在哪里实现此方法?使用Spring Boot,它使用不推荐使用WebMvcConfigurerAdapter的Spring 5
Eric Huang,

Hibernate4Module是遗产
德米特里·卡尔托维奇

8

那么,对于Spring Data Rest,@ r1ckr发布的解决方案有效时,所需要做的就是根据您的Hibernate版本添加以下依赖项之一:

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

要么

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

在Spring Data Rest中有一个类:

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

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

但是有一个问题:

序列化惰性@ManyToOne的问题


当您不使用Spring Data Rest时,如何使用spring boot配置它?
Eric Huang

不够。您还需要@Bean hibernate5Module()
Dmitry Kaltovich,

4

如果您使用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>

`


xml配置是旧版
Dmitry Kaltovich,

3

对于那些来这里寻找基于Apache CXF的RESTful服务的解决方案的人,修复该问题的配置如下:

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

其中HibernateAwareObjectMapper定义为:

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

从2016年6月开始,需要以下依赖关系(前提是您使用的是Hibernate5):

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

1

以下解决方案是针对Spring 4.3(非引导)和Hibernate 5.1的,出于性能原因,我们将所有fetchtypes转换为fetch=FetchType.LAZYfrom的情况fetch=FetchType.EAGER。随即我们发现com.fasterxml.jackson.databind.JsonMappingException: could not initialize proxy由于延迟加载问题而导致的异常。

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

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

然后将以下内容添加到我们的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;
}

笔记:

  • 您需要创建和配置Hibernate5Module才能获得与没有此模块的杰克逊相似的行为。默认设置不兼容。

  • 我们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 {

该解决方案可能更容易: kotlin @Bean fun hibernate5Module(): Module = Hibernate5Module()
Dmitry Kaltovich


0

我对这个问题做了一个非常简单的解决方案。

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

在我的情况下,我给出了错误,因为每当我返回未决时,作为一种好的做法,我都会将其转换为无法修改的列表,但是根据我用来获取实例的方法,它可能会或可能不会是懒惰的(带有或不带有获取),我在Hibernate初始化之前进行了测试,并添加了注释,以防止序列化一个空属性并解决了我的问题。


0

我整天都在尝试解决相同的问题。您可以在不更改现有消息转换器配置的情况下进行操作。

我认为,在jackson-datatype-hibernate的帮助下,仅需两个步骤即可解决此问题的最简单方法:

kotlin示例(与Java相同):

  1. 添加build.gradle.kts
implementation("com.fasterxml.jackson.datatype:jackson-datatype-hibernate5:$jacksonHibernate")
  1. 创建 @Bean
   @Bean
   fun hibernate5Module(): Module = Hibernate5Module()

  • 请注意,Modulecom.fasterxml.jackson.databind.Module不是java.util.Module

  • 好的做法是在&关系中添加@JsonBackReference&。可能只有1个班级。@JsonManagedReference@OneToMany@ManyToOne@JsonBackReference


-1

尽管此问题与以下问题略有不同:序列化Hibernate对象时抛出了奇怪的Jackson异常,但可以使用以下代码以相同的方式解决潜在的问题:

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

-1

我尝试了这个,并且工作:

//延迟加载的自定义配置

public static class HibernateLazyInitializerSerializer extends JsonSerializer<JavassistLazyInitializer> {

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

并配置映射器:

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


-1

我尝试了@rick有用的答案,但是遇到了一个问题,尽管“杰克逊-数据类型-jsr310 ”这样的“知名模块”即使在类路径中也没有自动注册。(此博客文章介绍了自动注册。)

扩展@rick的答案,以下是使用Spring的Jackson2ObjectMapperBuilder创建的一种变体ObjectMapper。这会自动注册“知名模块”并设置除安装以外的某些功能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);
    }
}

您是否有关于此主题的更新?我现在有问题。当然,当我添加hibernate5Module(因为我使用休眠5)时,杰克逊会忽略未初始化对象的代理,但是,尽管我使用了您的方法,但似乎未注册“知名模块”。我确实知道这一点,因为我的应用程序的某些部分坏了,而当我删除模块时,它们又可以工作了。
Daniel Henao

你@DanielHenao已经研究spring.io/blog/2014/12/02/...?我切换到Hibernate5,使用Jackson-Antpath-Filter建议的DelegatingWebMvcConfiguration类(而不是WebMvcConfigurerAdapter):@Override public void configureMessageConverters(List<HttpMessageConverter<?>> messageConverters) { messageConverters.add(jacksonMessageConverter()); addDefaultHttpMessageConverters(messageConverters); }
Markus Pscheidt

1.Hibernate4Module是遗产。2-SerializationFeature.WRITE_DATES_AS_TIMESTAMPS与问题
无关

@DmitryKaltovich广告1)在撰写本文时,这并不是遗产。广告2)确定,删除。
Markus Pscheidt,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.