在Spring MVC + Hibernate开发项目的过程中,使用Json序列化Hibernate懒加载对象时会抛出org.hibernate.LazyInitializationException异常。原因是懒加载的对象是一个代理对象,并不是我们真正想要得到的对象,所以Json无法序列化。网上给出几种解决办法:
1. 使用OpenSessionInViewFilter,但经过本人多次测试未能解决(也许是我使用Hibernate4的原因,未证实)。
2. 在懒加载的对象上使用@JsonIgnore注解,这样在Json序列化的时候就会忽略懒加载的对象。这样一来报错的问题确实解决了,但我想在客户端拿到被@JsonIgnore注解的真正实体对象(不是懒加载的代理对象)的时候却没办法拿到了。因为使用了@JsonIgnore注解的对象,在Json序列化的时候,它不管你是代理对象还是真正的实体对象,它一概忽略掉。这样也不是我想要的结果。
3. 使用立即加载策略,这样就不存在懒加载了,自然不会报错。但这样就失去了懒加载的意义了。
4. Spring MVC + Hibernate3可以使用jackson-module-hibernate解决Json序列化异常(自行baidu、google)。
jackson-module-hibernate不再支持Hibernate4代理对象Json序列化,替代它的是Jackson2版本。更为可笑的是Spring3.1中的MappingJacksonHttpMessageConverter(Spring中的Json转换器)使用的Jackson1.x的版本。
在此给出Spring3.1, Hibernate4与Jackson2版本的解决方案:
首先下载Jackson2需要的jar包:
1. 使用Jackson2创建一个新的MappingJacksonHttpMessageConverter
package com.zdksii.pms.common.hibernate;
import java.io.IOException;
import java.nio.charset.Charset;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.util.Assert;
import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
/**
* 重写org.springframework.http.converter.json.MappingJacksonHttpMessageConverter, 以便支持Jackson 2
* @author xie linming
* @date 2014年12月26日
*/
public class MappingJackson2HttpMessageConverter extends AbstractHttpMessageConverter<Object> {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private ObjectMapper objectMapper = new ObjectMapper();
private boolean prefixJson = false;
public MappingJackson2HttpMessageConverter() {
super(new MediaType("application", "json", DEFAULT_CHARSET));
}
public void setObjectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
this.objectMapper = objectMapper;
}
public ObjectMapper getObjectMapper() {
return this.objectMapper;
}
public void setPrefixJson(boolean prefixJson) {
this.prefixJson = prefixJson;
}
public boolean canRead(Class<?> clazz, MediaType mediaType) {
JavaType javaType = getJavaType(clazz);
return (this.objectMapper.canDeserialize(javaType) && canRead(mediaType));
}
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return (this.objectMapper.canSerialize(clazz) && canWrite(mediaType));
}
protected boolean supports(Class<?> clazz) {
// should not be called, since we override canRead/Write instead
throw new UnsupportedOperationException();
}
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
JavaType javaType = getJavaType(clazz);
try {
return this.objectMapper.readValue(inputMessage.getBody(), javaType);
} catch (JsonProcessingException ex) {
throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
}
}
protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType());
@SuppressWarnings("deprecation")
JsonGenerator jsonGenerator = this.objectMapper.getJsonFactory().createJsonGenerator(outputMessage.getBody(), encoding);
try {
if (this.prefixJson) {
jsonGenerator.writeRaw("{} && ");
}
this.objectMapper.writeValue(jsonGenerator, object);
} catch (JsonProcessingException ex) {
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
}
}
protected JavaType getJavaType(Class<?> clazz) {
return TypeFactory.defaultInstance().constructType(clazz);
}
protected JsonEncoding getJsonEncoding(MediaType contentType) {
if (contentType != null && contentType.getCharSet() != null) {
Charset charset = contentType.getCharSet();
for (JsonEncoding encoding : JsonEncoding.values()) {
if (charset.name().equals(encoding.getJavaName())) {
return encoding;
}
}
}
return JsonEncoding.UTF8;
}
}
2. 创建一个HibernateAwareObjectMapper类,向ObjectMapper注册Hibernate4Module
package com.zdksii.pms.common.hibernate;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.hibernate4.Hibernate4Module;
/**
* 向ObjectMapper注册Hibernate4Module, Hibernate4使用Jackson 2, Hibernate3使用jackson-module-hibernate
* @author xie linming
* @date 2014年12月26日
*/
public class HibernateAwareObjectMapper extends ObjectMapper {
private static final long serialVersionUID = 7958167810745447350L;
public HibernateAwareObjectMapper() {
Hibernate4Module hm = new Hibernate4Module();
registerModule(hm);
}
}
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="com.zdksii.pms.common.hibernate.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="com.zdksii.pms.common.hibernate.HibernateAwareObjectMapper" />
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
这样就可以使用诸如@ResponseBody注解来序列化Hibernate代理对象了。如果Json序列化的是一个Hibernate代理对象,客户端得到的是null值。