我有一个返回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;
}
控制器:
@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。但可能是通过新的杰克逊功能实现的。
我的版本是:
提前致谢。
答案 0 :(得分:90)
我终于找到了解决方案!感谢indybee给我一个线索。
教程Spring 3.1, Hibernate 4 and Jackson-Module-Hibernate为Spring 3.1及早期版本提供了一个很好的解决方案。但是,自3.1.2版本以来,Spring拥有自己的 MappingJackson2HttpMessageConverter ,其功能几乎与教程中的相同,因此我们不需要创建此自定义HTTPMessageConverter。
使用javaconfig我们也不需要创建 HibernateAwareObjectMapper ,我们只需要将 Hibernate4Module 添加到Spring的默认 MappingJackson2HttpMessageConverter 中已经将它添加到应用程序的HttpMessageConverters中,因此我们需要:
从 WebMvcConfigurerAdapter 扩展我们的spring配置类,并覆盖方法 configureMessageConverters 。
在该方法中添加 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....
}
如果你有一个xml配置,你也不需要创建自己的MappingJackson2HttpMessageConverter,但你需要创建教程中出现的个性化映射器(HibernateAwareObjectMapper),所以你的xml配置应如下所示: / p>
<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>
希望这个答案可以理解,并帮助有人找到解决这个问题的方法,任何问题随时都可以提出!
答案 1 :(得分:26)
从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
答案 2 :(得分:14)
这类似于@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功能的能力,这很有用。
答案 3 :(得分:6)
在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
将在应用程序启动时自动检测并注册模块。
但是有一个问题:
答案 4 :(得分:5)
如果您使用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>
`
答案 5 :(得分: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>
答案 6 :(得分:1)
您可以使用Spring Data Rest项目中的以下帮助程序:
Jackson2DatatypeHelper.configureObjectMapper(objectMapper);
答案 7 :(得分:1)
以下解决方案是针对Spring 4.3(非引导)和Hibernate 5.1的,出于性能原因,我们将所有fetchtypes从fetch=FetchType.LAZY
的情况转换为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
添加到现有的转换器中。
此新增功能解决了Hibernate和延迟加载的问题,但是导致生成的JSON格式存在残留问题。如this github issue中所述,hibernate-jackson延迟加载模块当前忽略@JsonUnwrapped注释,从而导致潜在的数据错误。无论强制加载功能设置如何,都会发生这种情况。自2016年以来一直存在问题。
注意
看来,通过将以下内容添加到延迟加载的类中,内置的ObjectMapper可以工作而无需添加hibernate5模块:
@JsonIgnoreProperties( {"handler","hibernateLazyInitializer"} )
public class Anyclass {
答案 8 :(得分:0)
我尝试了@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()
.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);
}
}
答案 9 :(得分:0)
虽然这个问题与此问题略有不同: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);
}
}
答案 10 :(得分:0)
我试过这个,然后工作:
//延迟加载的自定义配置
public static class HibernateLazyInitializerSerializer extends JsonSerializer<JavassistLazyInitializer> {
@Override
public void serialize(JavassistLazyInitializer initializer, JsonGenerator jsonGenerator,
SerializerProvider serializerProvider)
throws IOException, JsonProcessingException {
jsonGenerator.writeNull();
}
}
并配置mapper:
mapper = new JacksonMapper();
SimpleModule simpleModule = new SimpleModule(
"SimpleModule", new Version(1,0,0,null)
);
simpleModule.addSerializer(
JavassistLazyInitializer.class,
new HibernateLazyInitializerSerializer()
);
mapper.registerModule(simpleModule);
答案 11 :(得分:0)
我为这个问题提供了一个非常简单的解决方案。
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public Set<Pendency> getPendencies() {
return Hibernate.isInitialized(this.pendencies) ? Collections.unmodifiableSet(this.pendencies) : new HashSet<>();
}
在我的情况下,我给出了错误,因为每当我返回未决时,作为一种好的做法,我都会将其转换为无法修改的列表,但是根据我使用的方法,它可能会或可能不会变得很懒要获取实例(带有或不带有获取),我在Hibernate初始化它之前先进行测试,并添加注释,以防止序列化一个空属性并解决了我的问题。
答案 12 :(得分:0)
我整天都在尝试解决相同的问题。您可以在不更改现有消息转换器配置的情况下完成此操作。
我认为,在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个班级。
答案 13 :(得分:0)
我们无法解决必须为所有属性调用 Hibernate.initialize(objX.getAttributeY())
的问题。所以这里我们写了一个 utils 类来调用所有带有实体注解的属性的初始化方法。
集合仍然必须单独处理。
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);
}
}
答案 14 :(得分:-1)
弹簧靴的另一种解决方案:
组态:
spring.jpa.open-in-view=true