JPA一對多循環參考的解決,JPA循環參考解決
說是解決,其實不是很完美的解決的,寫出來只是想記錄一下這個問題或者看一下有沒有哪位仁兄會的,能否知道一二。
下面說說出現問題:
問題是這樣的,當我查詢一個一對多的實體的時候,工具直接就爆了,差不多我就猜到是哪裡死迴圈了,最後等了好久,查看原因,果然是堆溢出,再然後是jsckson的錯誤。那麼必然是序列化的問題了。
這是jackson的錯誤:
at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:354) at java.lang.ClassLoader.loadClass(ClassLoader.java:425) at java.lang.ClassLoader.loadClass(ClassLoader.java:412) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308) at java.lang.ClassLoader.loadClass(ClassLoader.java:358) at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1617) at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1547) at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:691) at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:157) at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:656) at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:675) at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:157)
這是循環參考的錯誤:
嚴重: Servlet.service() for servlet [springDispatcherServlet] in context with path [/Shop] threw exception [Request processing failed; nested exception is org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError) (through reference chain: com.web.module.index.model.entity.Account["user"]->com.web.module.index.model.entity.User["accounts"]->org.hibernate.collection.internal.PersistentSet[0]->com.web.module.index.model.entity.Account["user"]->com.web.module.index.model.entity.User["accounts"]->org.hibernate.collection.internal.PersistentSet[0]->com.web.module.index.model.entity.Account["user"]->com.web.module.index.model.entity.User["accounts"]->org.hibernate.collection.internal.PersistentSet[0]->com.web.module.index.model.entity.Account["user"]->com.web.module.index.model.entity.User["accounts"]->org.hibernate.collection.internal.PersistentSet[0]->com.web.module.index.model.entity.Account["user"]->com.web.module.index.model.entity.User["accounts"]->org.hibernate.collection.internal.PersistentSet[0]->com.web.module.index.model.entity.Account["user"]->com.web.module.index.model.entity.User["accounts"]->org.hibernate.collection.internal.PersistentSet[0]->com.web.module.index.model.entity.Account["user"]->com.web.module.index.model.entity.User["accounts"]->org.hibernate.collection.internal.PersistentSet[0]-j。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。還有很多的相同的錯誤
下面是兩個實體:
User.java:
package com.web.module.index.model.entity;import java.io.Serializable;import java.util.HashSet;import java.util.Set;import javax.persistence.Entity;import javax.persistence.FetchType;import javax.persistence.Id;import javax.persistence.OneToMany;import javax.xml.bind.annotation.XmlAccessType;import javax.xml.bind.annotation.XmlAccessorType;import javax.xml.bind.annotation.XmlElement;import javax.xml.bind.annotation.XmlRootElement;import org.hibernate.validator.constraints.NotEmpty;import com.fasterxml.jackson.annotation.JsonIgnore;@XmlAccessorType(XmlAccessType.FIELD)@XmlRootElement(name="user")@Entitypublic class User implements Serializable{ /** * */ private static final long serialVersionUID = 1L; @XmlElement @Id private String id; /** * validate適用於springmvc */ @XmlElement //@NotEmpty private String name; @JsonIgnore @OneToMany(mappedBy="user",targetEntity=Account.class,fetch=FetchType.EAGER) private Set<Account> accounts=new HashSet<Account>(); public String getName() { return name; } public void setName(String name) { this.name = name; } public String getId() { return id; } public void setId(String id) { this.id = id; } public Set<Account> getAccounts() { return accounts; } public void setAccounts(Set<Account> accounts) { this.accounts = accounts; } @Override public String toString() { return "User [id=" + id + ", name=" + name + ", accounts=" + accounts + "]"; } }
Account.java:
package com.web.module.index.model.entity;import java.io.Serializable;import javax.persistence.CascadeType;import javax.persistence.Entity;import javax.persistence.FetchType;import javax.persistence.Id;import javax.persistence.JoinColumn;import javax.persistence.ManyToOne;import com.fasterxml.jackson.annotation.JsonIgnore;@Entitypublic class Account implements Serializable{ /** * */ private static final long serialVersionUID = 1L; @Id private String id; private String code; private String password; @JsonIgnore @JoinColumn(name="user_id") @ManyToOne(targetEntity=User.class,fetch=FetchType.EAGER) private User user; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public User getUser() { return user; } public void setUser(User user) { this.user = user; } @Override public String toString() { return "Account [id=" + id + ", code=" + code + ", password=" + password + ", user=" + user + "]"; } }
後來去網上看了一下,這個問題很多人遇到。解決方案也有很多.
1.在關聯的實體上面設定@JsonIgnore,這個註解的意思是表示在序列化的時候,忽略這個屬性.但是我現在的邏輯是在頁面中必須使用到這個關聯實體中的屬性,所以就不能這麼做了,不然在頁面中是取不出這個資料的。
Uncaught TypeError: Cannot read property 'name' of undefined(1,2都會出現)
2.採用單向多對一的形式,這樣就不會出現迴圈的問題,這個確實是個方案,但是如果在一的那邊需要使用到多的這邊的話,就不好搞了。所以感覺還是不是很滿意。
3.後來想了想,既然是這樣,要不我在一的那邊使用@JsonIgnore吧。目前在頁面中沒使用。其實這個是第二個是差不多的,有點不同的是除了頁面展示的時候不能夠顯示多的那面的資料,在其他的業務中還是能夠使用的。這也是我在前面說不是很滿意的解決辦法。
4.第四種解決就是前面的3差不多,當我們使用多的一邊的時候,可以正確的顯示,但是在我們使用一的那一端的時候,我們可以使用List自己拼裝,有點像下面的代碼:
@RequestMapping(value="result/{id}",method=RequestMethod.GET) public @ResponseBody List<?> result(@PathVariable("id") String id){ System.out.println(id); List<Map<String,Object>> list=Lists.newArrayList(); //Map<String,Object> map=new HashMap<String,Object>(); Map<String,Object> map=null; Random r=new Random(); DecimalFormat dfmt=new DecimalFormat("#,###.00"); for(int i=0;i<4;i++){ int price=r.nextInt(10)+1; int number=r.nextInt(100000)+10000; map=new HashMap<String,Object>(); map.put("tradegoods", "煤"+i); map.put("units", "頓"); map.put("consumer", "XX物流"+id); map.put("unitPrice", dfmt.format(price)); map.put("number", dfmt.format(number)); map.put("count", dfmt.format(price*number)); list.add(map); } //設定日期格式 return list; }
這樣jackson序列化的時候,就不會出錯了,而且使用起來就不用像A.B.name這樣了,而且使用起來也更加的簡單。我們在JS裡面就可以這樣使用:
if(id!=""&&id){ $.ajax({ type: 'GET', url: $ctx + '/example/demo/result/'+id, dataType: 'json', success: function(data) { for(var i=0;i<data.length;i++){ data[i].num=i+1; } //alert(JSON.stringify(data)); viewModel.result(data); $(".notice-hide").show(); $(".notice-show").hide(); }, error: function(req, textStatus, errorThrown){ } });
html:
<tbody data-bind="foreach: result"> <tr> <td data-bind="text:num"></td> <td data-bind="text:tradegoods"></td> <td data-bind="text:units"></td> <td data-bind="text:consumer"></td> <td data-bind="text:unitPrice" class="format_"></td> <td data-bind="text:number" class="format_"></td> <td data-bind="text:count" class="format_"></td> </tr> </tbody>
這樣就完美的解決了這個問題。