在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值。