json+hibernate死循环问题的一点见解,有需要的朋友可以参考下。

【问题】如题所示,在我们使用hibernate框架而又需要将对象转化为json的时候,如果配置了双向的关联关系,就会出现这个死循环问题

异常信息:

Method public java.lang.String org.apache.commons.lang.exception.NestableRuntimeException.getMessage(int) threw an exception when invoked on net.sf.json.JSONException: There is a cycle in the hierarchy!
The problematic instruction:
----------
==> ${msgs[0][0]} [on line 76, column 25 in org/apache/struts2/dispatcher/error.ftl]
---------- Java backtrace for programmers:
----------
freemarker.template.TemplateModelException: Method public java.lang.String org.apache.commons.lang.exception.NestableRuntimeException.getMessage(int) threw an exception when invoked on net.sf.json.JSONException: There is a cycle in the hierarchy!
at freemarker.ext.beans.SimpleMethodModel.exec(SimpleMethodModel.java:130)
at freemarker.ext.beans.SimpleMethodModel.get(SimpleMethodModel.java:138)
at freemarker.core.DynamicKeyName.dealWithNumericalKey(DynamicKeyName.java:111)
at freemarker.core.DynamicKeyName._getAsTemplateModel(DynamicKeyName.java:90)
at freemarker.core.Expression.getAsTemplateModel(Expression.java:89)
at freemarker.core.Expression.getStringValue(Expression.java:93)
at freemarker.core.DollarVariable.accept(DollarVariable.java:76)
at freemarker.core.Environment.visit(Environment.java:209)
at freemarker.core.MixedContent.accept(MixedContent.java:92)
at freemarker.core.Environment.visit(Environment.java:209)
at freemarker.core.IfBlock.accept(IfBlock.java:82)
at freemarker.core.Environment.visit(Environment.java:209)
at freemarker.core.IfBlock.accept(IfBlock.java:82)
at freemarker.core.Environment.visit(Environment.java:209)
at freemarker.core.MixedContent.accept(MixedContent.java:92)
at freemarker.core.Environment.visit(Environment.java:209)
at freemarker.core.Environment.process(Environment.java:189)
at freemarker.template.Template.process(Template.java:237)
at org.apache.struts2.dispatcher.Dispatcher.sendError(Dispatcher.java:748)
at org.apache.struts2.dispatcher.Dispatcher.serviceAction(Dispatcher.java:505)
at org.apache.struts2.dispatcher.ng.ExecuteOperations.executeAction(ExecuteOperations.java:77)
at org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter.doFilter(StrutsPrepareAndExecuteFilter.java:91)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:861)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:606)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
at java.lang.Thread.run(Thread.java:619)
Caused by: java.lang.NullPointerException
at freemarker.ext.beans.SimpleMemberModel.unwrapArguments(SimpleMemberModel.java:85)
at freemarker.ext.beans.SimpleMethodModel.exec(SimpleMethodModel.java:106)
... 33 more

关键字是net.sf.json.JSONException: There is a cycle in the hierarchy!,意思是在层次关系里有一个循环

【原因】为什么会这样呢?原因在于你要转化的对象里配置了对另外一个对象的关联,而那个对象里又配置了对你这个对象的关联。比如我的两个类叫做Shop(商店)和Staff(员工),一个商店可以有多个员工,所以我给这两个对象配置了双向的一对多和多对一的关联关系。这时候问题就出现了,JSON lib在把shop对象转化为json字符串的时候,发现shop里有个Set<Staff>,它就会去级联的把Set<Staff>转化为json字符串,在它遍历Set的时候,发现Staff里又有一个Shop对象,这时候它又会去尝试把shop转化为json字符串,然后就发现shop里又有Set<Staff>,如此周而复始,就形成了死循环。

【解决】问题清楚了,如何解决呢?我百度了一下,也找到了数十条的资料,但大都只是都说明了要用jsonConfig.setJsonPropertyFilter(new PropertyFilter(){}),而对于其中的参数和if语句该如何写,并没有一个说的很明白的。为此我查看了json-lib的源码,并进行了尝试,总结如下:

1,思路是在JSONObject把Shop对象转化为json字符串的时候,在中间加一道过滤,如果当前要转化的属性是Set并且属性名是staffs,那么就进行过滤

2,代码如下:

Map<String, Object> map=new HashMap<String, Object>();
map.put("shops", list);
map.put("total", total); JsonConfig jsonConfig = new JsonConfig();
jsonConfig.setJsonPropertyFilter(new PropertyFilter() {
public boolean apply(Object obj, String name, Object value) {
if(obj instanceof Set||name.equals("staffs")){
return true;
}else{
return false;
}
}
}); return JSONObject.fromObject(map,jsonConfig);

如上,PropertyFilter是json-lib提供的进行属性过滤的一个接口,具体的实现是由apply方法做的,那么我们就需要重写apply方法。
此方法有三个参数,第一个是Object类型的,是你要过滤的属性的类型;第二个参数是String类型,是你要过滤的属性的名称;第三个参数是Object类型的,是你要过滤的属性的值(值可能是String或其它类型的,所以用Object)。 返回值是boolean类型的,返回true;就是进行过滤,返回false,就是不进行过滤。

if语句的写法就要根据实际的需要了,比如说我这里要解决死循环,就要实现把Shop里的Set<Staff> staffs属性过滤掉,那我的if语句就应该如上面那样写。总而言之就是JSON-lib在转化的时候,会对每个属性都调用这个apply方法,这样我们就要根据实际的业务需要,如果当前属性符合你的if条件,那你就要返回true,进行过滤。是使用||还是&&也要根据实际而定,比如你的Shop里有两个Set,那你就要使用&&。

这样配置后,再测试,就发现获取Shop的时候死循环问题已经不再出现了。

3,同理,Staff端也应该进行类似的配置

Map<String,Object> map=new HashMap<String, Object>();
map.put("staffs", list);
map.put("total", total); JsonConfig jsonConfig = new JsonConfig();
jsonConfig.setExcludes(new String[]{"handler","hibernateLazyInitializer"});
jsonConfig.setJsonPropertyFilter(new PropertyFilter() {
public boolean apply(Object obj, String name, Object value) {
if(obj instanceof Shop&&name.equals("shop")){
return true;
}else{
return false;
}
}
}); return JSONObject.fromObject(map,jsonConfig);

经过测试,也是没有问题的。

这里大家也可以看见jsonConfig.setExcludes(new String[]{"handler","hibernateLazyInitializer"});,这一行是为了防止hibernate延迟加载造成的异常而设置的。

4,到这里大功告成了吗?不,我在测试的时候发现了一个更严重的问题,如果按照上面做这样配置,那我获取shop的时候,生成的json字符串里staffs的Set不见了;获取Staff的时候,它的属性shop在json字符串里也不见了!稍加分析就可以知道这是上面配置造成的。按上面的配置,Shop里的Set<Staff>被过滤掉了,“过滤掉”的含义不是不级联的转化Staff里的Shop了,而是直接连Set<Staff>都不转化了。这可坏了,我配置双向关联关系就是为了关联显示,你把我的属性过滤掉了,那我还配置双向关联干嘛?我还这么大费周章的来解决死循环干嘛?

那么这个问题该如何解决呢?其实仔细一想,也不难,大家看我把Shop的配置改成下面这样

Map<String, Object> map=new HashMap<String, Object>();
map.put("shops", list);
map.put("total", total); JsonConfig jsonConfig = new JsonConfig();
jsonConfig.setJsonPropertyFilter(new PropertyFilter() {
public boolean apply(Object obj, String name, Object value) {
if(obj instanceof Staff||name.equals("shop")){
return true;
}else{
return false;
}
}
}); return JSONObject.fromObject(map,jsonConfig);

这样就可以获取到了,为什么呢?因为这样配置的话,在将Shop里的Set<Staff> staffs转化的时候,我们不过滤;而在将staffs里的每个Staff里的shop转化的时候,我们进行过滤,这样就既解决了死循环问题,又避免了Shop里的staffs被过滤掉的问题。
同理,Staff要这样配置

Map<String,Object> map=new HashMap<String, Object>();
map.put("staffs", list);
map.put("total", total); JsonConfig jsonConfig = new JsonConfig();
jsonConfig.setExcludes(new String[]{"handler","hibernateLazyInitializer"});
jsonConfig.setJsonPropertyFilter(new PropertyFilter() {
public boolean apply(Object obj, String name, Object value) {
if(obj instanceof Set||name.equals("staffs")){
return true;
}else{
return false;
}
}
}); return JSONObject.fromObject(map,jsonConfig);

可是我测试的时候却发现得到的字符串里只有total,staffs没有了?大家可能已经明白了,不仅Shop里的staffs被过滤掉了,map里的staffs也被过滤掉了。解决也很简单,把map.put("staffs",list);改成map.put("list",list);就行了,就是换个名字。【扩展】到这里,应该能解决大家的问题了。另外还有一种方法也要提一下,

JsonConfig jsonConfig = new JsonConfig();
jsonConfig.setIgnoreDefaultExcludes(false); //设置默认忽略
jsonConfig.setCycleDetectionStrategy(CycleDetectionStrategy.LENIENT);//设置循环策略为忽略 解决json最头疼的问题 死循环
jsonConfig.setExcludes(new String[] {"staffs"});//此处是亮点,只要将所需忽略字段加到数组中即可

这种配置也是比较好理解的,但也要注意属性过滤问题,Shop过滤shop,Staff过滤staffs。

java集合转换成json时问题和解决方法的更多相关文章

  1. Java对象转换成xml对象和Java对象转换成JSON对象

    1.把Java对象转换成JSON对象 apache提供的json-lib小工具,它可以方便的使用Java语言来创建JSON字符串.也可以把JavaBean转换成JSON字符串. json-lib的核心 ...

  2. C#中对象,字符串,dataTable、DataReader、DataSet,对象集合转换成Json字符串方法。

    C#中对象,字符串,dataTable.DataReader.DataSet,对象集合转换成Json字符串方法. public class ConvertJson { #region 私有方法 /// ...

  3. python2.7字典转换成json时中文字符串变成unicode的问题:

    参考:http://blog.csdn.net/u014431852/article/details/53058951 编码问题: python2.7字典转换成json时中文字符串变成unicode的 ...

  4. 将java类的泛型集合转换成json对象

    一般用extjs开发传输都是用json比较多,这个将来大家也许会用到... ConvertJsonUtils.java package com.sunweb.util.jsonfactory; imp ...

  5. JSON--List集合转换成JSON对象

    转自:http://www.cnblogs.com/xmaomao/p/3184542.html 1. 简单的手动放置 键值对 到JSONObject,然后在put到JSONArray对象里 List ...

  6. Java对象转换成Json字符串是无法获得对应字段名

    问题: 代码中已经标注 @JSONField(name = "attrs") private String abc; public String getA() { return a ...

  7. 如何将java对象转换成json数据

    package cn.hopetesting.com.test;import cn.hopetesting.com.domain.User;import com.fasterxml.jackson.c ...

  8. java对象转换成json

    package com.bjs.acrosstime.utils; import java.util.ArrayList; import java.util.Date; import java.uti ...

  9. FastJson将Java对象转换成json

    确保环境依赖都配置好! 1.在pom.xml导入依赖 <dependency> <groupId>com.alibaba</groupId> <artifac ...

随机推荐

  1. The C Programming Language Second Edition

    %12d  at least #include <stdio.h> main() { ,sum=,w=; ; ; w<=end; w++ ) { sum+=w; // for(wb= ...

  2. MySQL事务隔离级别,锁(转)

    add by zhj: 本文针对的是MySQL的InnoDB存储引擎,不适用于MySQL的其它存储引擎和其它数据库 原文:MySQL数据库事务隔离级别(Transaction Isolation Le ...

  3. CentOS 7.4 防火墙&网卡设置

    防火墙 查看防火墙状态 临时关闭防火墙 (关闭的是当前正在运行的防火墙,重启时还是会自启) 彻底关闭防火墙 (开机不会再自启) 开启防火墙 查看防火墙状态 网卡 查看网卡状态

  4. Django 视图系统(views)

    介绍 一个视图函数(类),简称视图,是一个简单的Python 函数(类),它接受Web请求并且返回Web响应. 响应可以是一张网页的HTML内容,一个重定向,一个404错误,一个XML文档,或者一张图 ...

  5. 《Mining of Massive Datasets》笔记(一)

    数据挖掘基本概念 数据挖掘定义 最广为接受得到定义是,数据挖掘是数据"模型"的发现过程.而"模型"却可以有多种含义. 1)统计建模 统计学家认为数据挖掘就是统计 ...

  6. PS批量修改照片大小

    最近发现一个好玩的东西,分享一下懒人的做法 1.先打开一张图片,调出动作面板 2.新建动作,开始记录. 3.按Ctrl + Alt + I 或者选择图像菜单----图像大小,调出修改图像大小对话框. ...

  7. MCU与FPGA通信

    1.MCU启动FPGA相应功能模块 通过译码器选择相应的功能模块,调用实现功能. 2.MCU与FPGA串口通信 SPI协议简单.可靠.易实现,速度快,推荐使用SPI.SPI为四线机制,包含MOSI.M ...

  8. Android:日常学习笔记(6)——探究活动(4)

    Android:日常学习笔记(6)——探究活动(4) 活动的启动模式 standard模式 standard是活动默认的启动模式,在不进行显示定义的情况下,所有活动都会自动使用这种启动模式. stan ...

  9. 前端之JQuery [续]

    JQuery使用技巧 1.prop属性实现全选,反选,取消功能 需求: 实现全选,反选,取消功能 代码如下: <!DOCTYPE html> <html lang="en& ...

  10. 【转】Linux下查看进程打开的文件句柄数

    ---查看系统默认的最大文件句柄数,系统默认是1024 # ulimit -n 1024 ----查看当前进程打开了多少句柄数 # lsof -n|awk '{print $2}'|sort|uniq ...