单向关联中解决jackxon序列化对象时关联对象为空,无法序列化成json的问题

版权声明:本篇文章由IT_CREATE整理 https://blog.csdn.net/IT_CREATE/article/details/86604479

      在我们用hibernate做查询数据的时候,我们会遇到一些问题,就是我们后台向前端以json返回数据的时候,会遇到返回对象中的关联对象属性为空,无法进行序列化;这是因为后台序列化对象的时候位于我们的第一次查询和关联数据查询之间。就是说在关联对象的查询还没进行的时候就开始进行jackson的序列化了,而这时我们的关联对象还没有查询出来数据进行赋值,所以会出现这个问题,所以有以下的解决方案,但是这个方案有缺陷,就是对于单向关系有效,对于双向无效。

      为什么出现这样的问题呢,对于双向关系无效,因为这次操作只解决了如果序列化对象的关联对象为空就不进行序列化;虽然我们解决了空引用的问题,但是新问题又出现了,这是必然的。我们可以想象一下,如果是双向关系,我们查询成功了,那么必然双方都含有对方,那么你在序列化对方的时候,对方序列化你,就会陷入无限的循环序列化,死在里面。

      有一个办法可以解决这种问题,就是用@JsonIgnoreProperties这个注解去忽略对象上的关联属性,那么这个属性就不会参与序列化和反序列化。然后我们就可以根据手动的对两个对象进行装填,然后返回。

     

下面还是说一下提到的第一种方式(不能解决双向关联循环序列化,上面说了,下面是解决不能序列化成功的问题,而不是成功之后循环序列化的问题):

前情提要:解决这个关联对象为空的问题方法就是:

1、自己写继承ObjectMapper的CustomMapper类(类名无所谓,反正是自己定义)

2、向spring-mvc.xml配置文件中注册CustomMapper

3、在前端控制器去使用这个CustomMapper来替代我们之前使用的ObjectMapper进行对象序列化成json的类(使用方式和使用jackson的ObjectMapper类是一样的)

自定义的ObjectMapper和如何在spring-mvc中xml注册呢,链接:https://blog.csdn.net/IT_CREATE/article/details/86592388

找到:spring-mvc.xml配置(配置的是表现层的东西)   这段即可(CustomMapper的创建和注册都有

举例:查询用户信息的时候(用户中含有地址和其他信息两个关联对象类)

用户对象:(get、set方法就不贴出来了)UserBean:

@JsonFilter("userFilter")就是将利用Jackson的JsonFilter来实现动态过滤数据列,后面会用到这个过滤器,但这不是这次主要的

利用Jackson的JsonFilter来实现动态过滤数据列(数据列权限控制):http://www.cnblogs.com/tomcatandjerry/p/9139976.html

@Entity
@Table(name="t_user")
@OptimisticLocking(type=OptimisticLockType.VERSION)
@JsonFilter("userFilter")
public class UserBean implements Serializable{

	private static final long serialVersionUID = -1879857877221009050L;

	@Id
	@Column(name="id")
	@GenericGenerator(name="hibernate.id",strategy="identity")
	@GeneratedValue(generator="hibernate.id")
	private Integer id;
	
	@Column(name="login_name",length=20)
	private String loginName;
	
	@Column(name="user_name",length=20)
	private String userName;
	
	@Column(name="user_pwd",length=32)
	private String password;
	
	@Column(name="age")
	private Integer age;
	
	@Column(name="gender")
	private Integer gender;
	
	@Column(name="birthday")
	private Date birthday;
	
	@Column(name="create_time")
	private Date createTime;
	
	@Version
	private Integer version;
	
	/**
	 * 人拥有人员信息,单向关联
	 */
	@OneToOne(fetch=FetchType.LAZY)
	@Cascade(value= {CascadeType.ALL})
	@JoinColumn(name="fk_info_id")
	private UserInfoBean userInfo;
	/**
	 * 人拥有多个地址,单向关联
	 */
	
	@OneToMany(fetch=FetchType.LAZY)
	@Cascade(value= {CascadeType.ALL})
	@JoinColumn(name="fk_user_id")
	private Set<AddressBean> adds;
}

用户信息类 UserInfoBean:

@Entity
@Table(name="t_user_info")
public class UserInfoBean implements Serializable{

	/**
	 * 
	 */
	private static final long serialVersionUID = -7547766667713260207L;
	
	@Id
	@Column(name="id")
	@GenericGenerator(name="hibernate.id",strategy="identity")
	@GeneratedValue(generator="hibernate.id")
	private Integer id;
	
	@Column(name="idcard",length=18)
	private String idcard;
	
	@Column(name="job",length=20)
	private String job;
}

用户地址类 AddressBean:

@Entity
@Table(name="t_address")
public class AddressBean implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 876189137233337215L;
	
	@Id
	@Column(name="id")
	@GenericGenerator(name="hibernate.id",strategy="identity")
	@GeneratedValue(generator="hibernate.id")
	private Integer id;
	
	@Column(name="address",length=120)
	private String address;
	
	@Column(name="telphone",length=13)
	private String telphone;
}

前端页面 js:

$('#btn01').bind("click", function(){
	var arrs = new Array("userInfo","adds");
	var data = {"id":26,"arrs":arrs};
		
	//Ajax在通过POST在传递  "数组"时,需要指定traditional:true
	//其目的是为了告知JS在序列化参数时,直接将arrs作为参数名称,而不是将arrs[]来作为参数名称
	//不做深度序列化
	console.log(data);
	var url = "users/getById";
	$.ajax({
		type: "POST",
		url: url,
		contentType:"application/x-www-form-urlencoded",
		traditional:true,
		data: data,
		success: function(msg){
			console.log(msg);
		}
	});
});

        从js中可以看出,我们要查询id为26的用户信息。上面向后台传的arrs指的是你不需要用户UserBean中的哪些字段。

        起到忽略这些字段作用的也就是我们在对象上添加了@JsonFilter("userFilter")起到的作用

后台controller代码:

/**
 * 
 * @param id
 * @param arrs 代表在使用Jackson序列化对象时,需要排除属性
 * @param out
 * @param res
 */
@RequestMapping(value = "/getById")
public void getUserBeanById(int id,String[] arrs,PrintWriter out, HttpServletResponse res) {
	UserBean user = null;
	try {
		user = userServiceImpl.getUserBeanById(id);
		//通过自己定义的ObjectMapper解决 页面需要关联对象时,做延迟加载的问题
		CustomMapper om = new CustomMapper();
			
		if(arrs != null && arrs.length > 0) {
			om.setFilterProvider(new SimpleFilterProvider().addFilter("userFilter",
					SimpleBeanPropertyFilter.serializeAllExcept(arrs)));
                //serializeAllExcept 表示序列化全部,除了指定字段
                //filterOutAllExcept 表示过滤掉全部,除了指定的字段
		}else {
			om.setFilterProvider(new SimpleFilterProvider().addFilter("userFilter",
					SimpleBeanPropertyFilter.serializeAll()));
		}
			
		String json = om.writeValueAsString(user);
		res.setContentType("application/json;charset=utf-8");
		out.print(json);
	} catch (Exception e) {
		// TODO: handle exception
		log.error("UserController-------getUserBeanById()", e);
	}

}

      通过前端传下来的值手动的指定你需要哪些数据,在用户对象上定义的@JsonFilter("userFilter")过滤器只是解决我们想要哪些属性的问题,并没有解决在序列化过程中出现关联对象为空的情况。假设我们没有传下来arrs,也就是我们没有想要过滤的字段,我们想把关联信息一起序列化,那么这里就用到了通过自己定义的ObjectMapper解决 页面需要关联对象时,做延迟加载的问题(也就是上面代码中的CustomMapper)

      前面已经提到了,在对象进行jackson需序列化的时候,做一次查询就要序列化一次,所以在还没有进行延迟加载的第二次查询的时候就开始进行了第一次序列化,这回导致关联对象为空的问题。

      比如这里先进行用户对象的查询,这是第一次查询,然后序列化,但是用户对象中包含的地址类和用户信息类还没有开始进行懒加载操作,也就是第二次查询,所以用户对象中包含的地址对象和用户信息对象的引用都为null,所以就无法序列化成功,会报错。

      因为我们用的是jackson的ObjectMapper来进行序列化成json的,所以我们需要在spring-mvc的配置文件中去注册我们自己的ObjectMapper,也就是我们自己写的继承了ObjectMapper的CustomMapper类。

      自定义的ObjectMapper和如何在spring-mvc中xml注册呢,链接:https://blog.csdn.net/IT_CREATE/article/details/86592388

找到:spring-mvc.xml配置(配置的是表现层的东西)   这段即可(CustomMapper的创建和注册都有

猜你喜欢

转载自blog.csdn.net/IT_CREATE/article/details/86604479