避免对未获取的惰性对象进行 Jackson 序列化
Posted
技术标签:
【中文标题】避免对未获取的惰性对象进行 Jackson 序列化【英文标题】:Avoid Jackson serialization on non fetched lazy objects 【发布时间】:2014-03-09 14:55:37 【问题描述】:我有一个简单的控制器,它返回一个用户对象,这个用户有一个属性坐标,它具有休眠属性 FetchType.LAZY。
当我尝试获取这个用户时,总是要加载所有坐标才能获取用户对象,否则当Jackson尝试序列化用户时会抛出异常:
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提前致谢。
【问题讨论】:
这两个链接中描述的方法对我有用,Spring 3.1, Hibernate 4 and Jackson-Module-Hibernate 和 FasterXML / jackson-datatype-hibernate 谢谢indybee,我一直在看教程,spring 3.2.5已经有一个MappingJackson2HttpMessageConverter,但是我不能用它来避免非获取的惰性对象,我也尝试过实现教程中的一个,但什么都没有…… 您是否收到相同的错误“无法初始化代理”或其他错误? (我在 Spring 3.2.6 和 Hibernate 4.1.7 和 jackson 2.3 的文章中使用了额外的 HibernateAwareObjectMapper) 知道了!!由于版本 3.1.2 Spring 有他自己的 MappingJackson2HttpMessageConverter 并且具有与教程中的几乎相同的行为,但问题是我使用的是 Spring java config 并且我从 javaconfigs 开始。我试图制作一个 @Bean MappingJackson2HttpMessageConverter,而不是将其添加到 Spring 的 HttpMessageConverters 中,也许答案更清楚:) 很高兴它正在工作:) 【参考方案1】:我终于找到了解决办法!感谢 indybee 提供线索。
教程Spring 3.1, Hibernate 4 and Jackson-Module-Hibernate对Spring 3.1及更早版本有很好的解决方案。但由于 Spring 3.1.2 版本有自己的 MappingJackson2HttpMessageConverter,其功能与教程中的几乎相同,因此我们不需要创建此自定义 HTTPMessageConverter。
使用 javaconfig 我们也不需要创建 HibernateAwareObjectMapper,我们只需将 Hibernate4Module 添加到 Spring 默认的 MappingJackson2HttpMessageConverter已经有了并将其添加到应用程序的 HttpMessageConverters 中,所以我们需要:
从 WebMvcConfigurerAdapter 扩展我们的 spring 配置类并覆盖方法 configureMessageConverters。
在该方法上添加 MappingJackson2HttpMessageConverter 和在以前的方法中注册的 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....
如果你有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>
希望这个答案可以理解并帮助某人找到解决此问题的方法,任何问题都可以随时提出!
【讨论】:
感谢分享解决方案。然而,经过几次尝试,我发现目前最新的 jackson (2.4.2) 确实 NOT 与 spring 4.0.6 一起工作。我仍然有“无会话”错误。只有当我将杰克逊版本降级到 2.3.4(或 2.4.2)时,它才开始工作。 感谢您指出 MappingJackson2HttpMessageConverter!我一直在寻找这样的课程。 非常感谢!我刚刚测试了它以及它与 Spring 4.2.4、Jackson 2.7.1-1 和 JacksonDatatype 2.7.1 的配合 :) 这并不能解决 Spring Data REST 中的问题。 如果使用 Springboot 代替 Spring,如何解决相同的问题?我已经尝试注册 Hibernate4 模块(这是我目前拥有的版本),但这并没有解决我的问题。【参考方案2】:从 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
【讨论】:
对于 Hibernate 5(与当前版本的 Spring Boot 一样),它是Hibernate5Module
。它还需要对<groupId>com.fasterxml.jackson.datatype</groupId><artifactId>jackson-datatype-hibernate5</artifactId>
的依赖
在 Spring Boot 2.1 上也为我工作。开始让我发疯了。
不清楚如何实现。我收到不兼容的类型错误。
太棒了!使用 Spring Boot 2.0.6 和 Hibernate 5 为我工作。
感谢@chrismarx,如果允许的话,我会给你 1337 票赞成这个答案。【参考方案3】:
这类似于@rick 接受的解决方案。
如果您不想修改现有的消息转换器配置,您可以声明一个 Jackson2ObjectMapperBuilder
bean,例如:
@Bean
public Jackson2ObjectMapperBuilder configureObjectMapper()
return new Jackson2ObjectMapperBuilder()
.modulesToInstall(Hibernate4Module.class);
不要忘记将以下依赖项添加到您的 Gradle 文件(或 Maven)中:
compile 'com.fasterxml.jackson.datatype:jackson-datatype-hibernate4:2.4.4'
如果您有一个 spring-boot 应用程序并且希望保留从 application.properties
文件修改 Jackson 功能的能力,这很有用。
【讨论】:
我们如何使用application.properties
文件来实现这一点(配置Jackson 以避免延迟加载对象)?
这不是这里所说的。 @Luis Belloch 的解决方案建议您创建 Jackson2ObjectMapperBuilder,这样您就不会覆盖 spring boots 默认消息转换器并继续使用 application.properties 值来控制 jackson 属性。
对不起...我是新手,你会在哪里实现这个方法?使用spring boot 它使用不推荐使用WebMvcConfigurerAdapter的Spring 5
Hibernate4Module
是旧版【参考方案4】:
在 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
它将在应用程序启动时自动检测并注册模块。
但是有一个问题:
Issue Serializing Lazy @ManyToOne
【讨论】:
不使用 Spring Data Rest 时如何使用 Spring Boot 进行配置? 不够。你还需要@Bean hibernate5Module()
【参考方案5】:
我花了一整天的时间试图解决同样的问题。您可以在不更改现有消息转换器配置的情况下执行此操作。
在我看来,在jackson-datatype-hibernate 的帮助下,只需两个步骤即可解决此问题的最简单方法:
kotlin 示例(与 java 相同):
-
添加
build.gradle.kts
:
implementation("com.fasterxml.jackson.datatype:jackson-datatype-hibernate5:$jacksonHibernate")
-
创建
@Bean
@Bean
fun hibernate5Module(): Module = Hibernate5Module()
注意Module
是com.fasterxml.jackson.databind.Module
,而不是java.util.Module
另外一个好的做法是将@JsonBackReference
& @JsonManagedReference
添加到@OneToMany
& @ManyToOne
关系。 @JsonBackReference
在课堂上只能是 1。
【讨论】:
【参考方案6】:如果您使用 XML 配置并使用<annotation-driven />
,我发现您必须按照Jackson Github account 的建议将<message-converters>
嵌套在<annotation-driven>
中。
像这样:
<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 配置是旧版【参考方案7】:对于那些来这里寻找基于 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>
【讨论】:
【参考方案8】:以下解决方案适用于 Spring 4.3(非引导)和 Hibernate 5.1,出于性能原因,我们将所有 fetchtypes 从 fetch=FetchType.EAGER
的情况转换为 fetch=FetchType.LAZY
。由于延迟加载问题,我们立即看到了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 以获得类似于没有此模块的 Jackson 的行为。默认情况下会做出不兼容的假设。
我们的WebMvcConfigurerAdapter
里面有很多其他的配置,我们想避免另一个配置类,这就是为什么我们没有使用其他帖子中提到的WebMvcConfigurationSupport#addDefaultHttpMessageConverters
函数。 /p>
WebMvcConfigurerAdapter#configureMessageConverters
禁用 Spring 的所有内部消息转换器配置。我们宁愿避免与此相关的潜在问题。
使用 extendMessageConverters
可以访问所有自动配置的 Jackson 类,而不会丢失所有其他消息转换器的配置。
使用getObjectMapper#registerModule
,我们能够将Hibernate5Module
添加到现有转换器中。
此添加解决了 Hibernate 和延迟加载的问题,但导致生成的 JSON 格式存在残留问题。正如this github issue 中所报告的,hibernate-jackson 延迟加载模块当前忽略了@JsonUnwrapped 注释,从而导致潜在的数据错误。无论强制加载功能设置如何,都会发生这种情况。这个问题从 2016 年就一直存在。
注意
似乎通过将以下内容添加到延迟加载的类中,内置的 ObjectMapper 可以在不添加 hibernate5 模块的情况下工作:
@JsonIgnoreProperties( "handler","hibernateLazyInitializer" )
public class Anyclass
【讨论】:
解决方案可能更简单:kotlin @Bean fun hibernate5Module(): Module = Hibernate5Module()
【参考方案9】:
您可以使用 Spring Data Rest 项目中的以下帮助程序:
Jackson2DatatypeHelper.configureObjectMapper(objectMapper);
【讨论】:
不是关于这个问题【参考方案10】:我对这个问题做了一个非常简单的解决方案。
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public Set<Pendency> getPendencies()
return Hibernate.isInitialized(this.pendencies) ? Collections.unmodifiableSet(this.pendencies) : new HashSet<>();
在我的情况下,我给出了错误,因为每当我返回一个挂件时,作为一个好习惯,我将它转换为一个无法修改的列表,但它可能会或可能不会懒惰取决于我使用的方法为了获取实例(有或没有 fetch),我在 Hibernate 初始化它之前进行了测试,并添加了防止序列化空属性的注释并解决了我的问题。
【讨论】:
【参考方案11】:我们无法解决必须为所有属性调用 Hibernate.initialize(objX.getAttributeY())
的问题。所以在这里我们编写了一个 utils 类来调用所有具有 Entity 注释的属性的初始化方法。
集合仍然必须单独处理。
import org.hibernate.Hibernate;
import javax.persistence.Entity;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class HibernateInitialiseUtil<T>
private final List<Method> entityGetterMethods;
public HibernateInitialiseUtil(Class<T> clazz)
this.entityGetterMethods = this.getGettersMethodsOfEntityAttributes(clazz);
public void initializeAllEntityAttributes(T obj) throws InvocationTargetException, IllegalAccessException
for (Method getter : this.entityGetterMethods)
Hibernate.initialize(getter.invoke(obj));
private List<String> getNameOfEntityAttributes(final Class<T> targetClass)
Field[] attributes = targetClass.getDeclaredFields();
List<String> fields = new ArrayList<>();
for (Field attribute : attributes)
if (attribute.getType().isAnnotationPresent(Entity.class))
fields.add(attribute.getName());
return fields;
private List<Method> getGettersMethodsOfEntityAttributes(final Class<T> targetClass)
List<Method> resultList = new ArrayList<>();
for (String attributeName : getNameOfEntityAttributes(targetClass))
try
PropertyDescriptor pd = new PropertyDescriptor(attributeName, targetClass);
Method getter = pd.getReadMethod();
resultList.add(getter);
catch (IllegalArgumentException | IntrospectionException e)
e.printStackTrace();
return Collections.unmodifiableList(resultList);
【讨论】:
【参考方案12】:虽然这个问题与这个问题略有不同:Strange Jackson exception being thrown when serializing Hibernate object,但可以使用以下代码以相同的方式解决根本问题:
@Provider
public class MyJacksonJsonProvider extends JacksonJsonProvider
public MyJacksonJsonProvider()
ObjectMapper mapper = new ObjectMapper();
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
setMapper(mapper);
【讨论】:
【参考方案13】:我试过了,并且成功了:
//延迟加载的自定义配置
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);
【讨论】:
【参考方案14】:我尝试了@rick's useful answer,但遇到了问题,即“知名模块”(例如jackson-datatype-jsr310)尽管位于类路径中,但并未自动注册。 (这个blog post 解释了自动注册。)
扩展@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)时,杰克逊忽略了未初始化对象的代理,但尽管我使用了你的方法,但似乎“知名模块”没有被注册。我确实知道这一点,因为我的应用程序的某些部分已损坏,当我删除模块时,它们又可以工作了。
@DanielHenao 你研究过spring.io/blog/2014/12/02/…吗?根据Jackson-Antpath-Filter 的建议,我使用DelegatingWebMvcConfiguration
类(而不是WebMvcConfigurerAdapter
)切换到Hibernate5:@Override public void configureMessageConverters(List<HttpMessageConverter<?>> messageConverters) messageConverters.add(jacksonMessageConverter()); addDefaultHttpMessageConverters(messageConverters);
1. Hibernate4Module
是旧版。 2 SerializationFeature.WRITE_DATES_AS_TIMESTAMPS
- 与问题无关
@DmitryKaltovich ad 1) 在撰写本文时它不是遗产;广告 2) 好的,已删除。【参考方案15】:
spring boot的另一种解决方案:
配置:
spring.jpa.open-in-view=true
【讨论】:
这无济于事以上是关于避免对未获取的惰性对象进行 Jackson 序列化的主要内容,如果未能解决你的问题,请参考以下文章
当jackson序列化json对象时如何防止hibernate5延迟获取?