json+hibernate死循環問題的一點看法

【問題】如題所示,在咱們使用hibernate框架而又須要將對象轉化爲json的時候,若是配置了雙向的關聯關係,就會出現這個死循環問題java

異常信息:apache

 

【緣由】爲何會這樣呢?緣由在於你要轉化的對象裏配置了對另一個對象的關聯,而那個對象裏又配置了對你這個對象的關聯。好比個人兩個類叫作Shop(商店)和Staff(員工),一個商店能夠有多個員工,因此我給這兩個對象配置了雙向的一對多和多對一的關聯關係。這時候問題就出現了,JSON lib在把shop對象轉化爲json字符串的時候,發現shop裏有個Set<Staff>,它就會去級聯的把Set<Staff>轉化爲json字符串,在它遍歷Set的時候,發現Staff裏又有一個Shop對象,這時候它又會去嘗試把shop轉化爲json字符串,而後就發現shop裏又有Set<Staff>,如此周而復始,就造成了死循環。json

 1 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!  
 2 The problematic instruction:  
 3 ----------  
 4 ==> ${msgs[0][0]} [on line 76, column 25 in org/apache/struts2/dispatcher/error.ftl]  
 5 ----------  
 6   
 7 Java backtrace for programmers:  
 8 ----------  
 9 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!  
10     at freemarker.ext.beans.SimpleMethodModel.exec(SimpleMethodModel.java:130)  
11     at freemarker.ext.beans.SimpleMethodModel.get(SimpleMethodModel.java:138)  
12     at freemarker.core.DynamicKeyName.dealWithNumericalKey(DynamicKeyName.java:111)  
13     at freemarker.core.DynamicKeyName._getAsTemplateModel(DynamicKeyName.java:90)  
14     at freemarker.core.Expression.getAsTemplateModel(Expression.java:89)  
15     at freemarker.core.Expression.getStringValue(Expression.java:93)  
16     at freemarker.core.DollarVariable.accept(DollarVariable.java:76)  
17     at freemarker.core.Environment.visit(Environment.java:209)  
18     at freemarker.core.MixedContent.accept(MixedContent.java:92)  
19     at freemarker.core.Environment.visit(Environment.java:209)  
20     at freemarker.core.IfBlock.accept(IfBlock.java:82)  
21     at freemarker.core.Environment.visit(Environment.java:209)  
22     at freemarker.core.IfBlock.accept(IfBlock.java:82)  
23     at freemarker.core.Environment.visit(Environment.java:209)  
24     at freemarker.core.MixedContent.accept(MixedContent.java:92)  
25     at freemarker.core.Environment.visit(Environment.java:209)  
26     at freemarker.core.Environment.process(Environment.java:189)  
27     at freemarker.template.Template.process(Template.java:237)  
28     at org.apache.struts2.dispatcher.Dispatcher.sendError(Dispatcher.java:748)  
29     at org.apache.struts2.dispatcher.Dispatcher.serviceAction(Dispatcher.java:505)  
30     at org.apache.struts2.dispatcher.ng.ExecuteOperations.executeAction(ExecuteOperations.java:77)  
31     at org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter.doFilter(StrutsPrepareAndExecuteFilter.java:91)  
32     at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)  
33     at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)  
34     at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)  
35     at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)  
36     at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)  
37     at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)  
38     at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)  
39     at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)  
40     at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:861)  
41     at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:606)  
42     at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)  
43     at java.lang.Thread.run(Thread.java:619)  
44 Caused by: java.lang.NullPointerException  
45     at freemarker.ext.beans.SimpleMemberModel.unwrapArguments(SimpleMemberModel.java:85)  
46     at freemarker.ext.beans.SimpleMethodModel.exec(SimpleMethodModel.java:106)  
47     ... 33 more  

【解決】問題清楚了,如何解決呢?我百度了一下,也找到了數十條的資料,但大都只是都說明了要用jsonConfig.setJsonPropertyFilter(new PropertyFilter(){}),而對於其中的參數和if語句該如何寫,並無一個說的很明白的。爲此我查看了json-lib的源碼,並進行了嘗試,總結以下:數組

1,思路是在JSONObject把Shop對象轉化爲json字符串的時候,在中間加一道過濾,若是當前要轉化的屬性是Set而且屬性名是staffs,那麼就進行過濾tomcat

2,代碼以下:app

 

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(value instanceof Set<?>&&name.equals("staffs")){  
        return true;  
    }else{  
        return false;  
    }  
   }  
});  
          
return JSONObject.fromObject(map,jsonConfig);  

如上,PropertyFilter是json-lib提供的進行屬性過濾的一個接口,具體的實現是由apply方法作的,那麼咱們就須要重寫apply方法。框架

       此方法有三個參數,第一個是Object類型的,是你要轉化的對象的類型(Shop);測試

                                           第二個參數是String類型,是你要過濾的屬性的名稱;spa

                                           第三個參數是Object類型的,是你要過濾的屬性的值(值多是String或其它類型的,因此用Object)。 .net

                      返回值是boolean類型的,返回true,會進行過濾;返回false,不會進行過濾。

      if語句的寫法就要根據實際的須要了,好比說我這裏要解決死循環,就要實現把Shop裏的Set<Staff> staffs屬性過濾掉,那個人if語句就應該如上面那樣寫。總而言之就是JSON-lib在轉化的時候,會對每一個屬性都調用這個apply方法,這樣咱們就要根據實際的業務須要,若是當前屬性符合你的if條件,那你就要返回true,進行過濾。是使用||仍是&&也要根據實際而定。

      這樣配置後,再測試,就發現獲取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(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(value instanceof Shop&&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(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);就好了,就是換個名字。

 

【擴展】到這裏,應該能解決你們的問題了。另外還有一種方法也要提一下,

1 JsonConfig jsonConfig = new JsonConfig();  
2 jsonConfig.setIgnoreDefaultExcludes(false); //設置默認忽略   
3 jsonConfig.setCycleDetectionStrategy(CycleDetectionStrategy.LENIENT);//設置循環策略爲忽略    解決json最頭疼的問題 死循環  
4 jsonConfig.setExcludes(new String[] {"staffs"});//此處是亮點,只要將所需忽略字段加到數組中便可  

這種配置也是比較好理解的,但也要注意屬性過濾問題,Shop過濾shop,Staff過濾staffs。

本文轉載自http://blog.csdn.net/superick/article/details/19421145

相關文章
相關標籤/搜索