Spring3.1, Hibernate4與Jackson2處理Json序列化異常

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


3. 將MappingJackson2HttpMessageConverter加入到Spring容器中
<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值。






發佈了7 篇原創文章 · 獲贊 4 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章