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")
@Entity
public 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; @Entity
public 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=;i<data.length;i++){
data[i].num=i+;
}
//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>
这样就完美的解决了这个问题。
5添加Filter的方式进行动态的过滤属性 ,上面的解决方法还是或多或少的影响到我们正常的使用类,下面说的方法是不会影响放到原有的类的。
jsckson的ObjectMapper有一个
public final void addMixInAnnotations(Class<?> target, Class<?> mixinSource)
{
_mixInAnnotations.put(new ClassKey(target), mixinSource);
} public final Class<?> findMixInClassFor(Class<?> cls) {
return (_mixInAnnotations == null) ? null : _mixInAnnotations.get(new ClassKey(cls));
} public final int mixInCount() {
return (_mixInAnnotations == null) ? : _mixInAnnotations.size();
}
这样的方法,这个方法的使用就要结合JsonIgnoreProperties注解一起来进行使用。我们需要定义一个接口,这个接口的作用是用来专门的过滤属性的。
还是针对上面的例子,我们要解决问题的话 ,我们需要在定义一个接口:
package com.hotusm.jackson; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @JsonIgnoreProperties(ignoreUnknown=true,value={"user"})
public interface AccountFilter {
}
这个接口非常简单,就是一个注解,注解其中的value就是表示的是我们需要将那些属性给忽略掉,增加了这么一个接口后,我们就可以使用上面提到的方法。
objectMapper.addMixInAnnotations(Account.class, AccountFilter.class);
之后再使用这个objectmapper的时候,在account类上面的user就不会被忽略掉了,通过这种方式,我们不用修改原来类的任何地方。但是这种方式需要我们重新创建一个接口,所以下面一种就是解决这种每次都要创建的痛苦了。
6.利用自定义注解的方式来进行过滤,这种方式也是看到其他人使用,感觉非常好,也就做一个简单的总结。
大概的讲一下思路
1.还是使用addMixInAnnotations方法,但是不需要我们每次都创建一个接口而是采用全注解的形式来。也许会很奇怪,前面的方法命名
是传入两个class啊 ,我们不手动创建的话,那该怎样的去调用呢。这里我们使用字节码技术Javassist来动态的创建class。
2.大概的思路就是我们自定义方法级别注解,注解上面可以指定某些类上的哪些属性需要忽略。然后对这些方法进行增强,增强逻辑中获取到这些注解中的类以及这个类上面忽略的
下面是上面理论的一个简单的实践:
第一步:自定义注解:
package com.hotusm.jackson.annotation; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; @Target(ElementType.METHOD)
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface IgnoreProperty {
/**
* 指定类
*/
Class<?> pojo(); /**
*指定上面的类那些属性需要过滤的
*/
String[] value();
}
上面这个注解就是我们后面要使用到的动态的在方法上面直接指定类需要忽略的属性。
第二步:对ObjectMapper进行装饰(写的例子,不是很优雅)
package com.hotusm.jackson.annotation; import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashSet; import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.ArrayMemberValue;
import javassist.bytecode.annotation.BooleanMemberValue;
import javassist.bytecode.annotation.MemberValue;
import javassist.bytecode.annotation.StringMemberValue; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper; public class ObjectMapperBuilder { public ObjectMapper build(Method method) throws CannotCompileException{
IgnoreProperty ignoreProperty = method.getAnnotation(IgnoreProperty.class);
String[] value = ignoreProperty.value();
Class<?> pojo = ignoreProperty.pojo();
checkParamter(method,value,pojo);
Class<?> clazz=doBuild(value);
ObjectMapper objectMapper=new ObjectMapper();
objectMapper.addMixInAnnotations(pojo, clazz);
return objectMapper;
}
/**
* 根据传入的参数构造一个class
* @throws CannotCompileException
*/
public Class<?> doBuild(String[] values) throws CannotCompileException{
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeInterface("ProxyMixInAnnotation" + System.currentTimeMillis());
ClassFile classFile = cc.getClassFile();
ConstPool cp = classFile.getConstPool();
AnnotationsAttribute attr = new AnnotationsAttribute(cp,
AnnotationsAttribute.visibleTag);
Annotation jsonIgnorePropertiesAnnotation = new Annotation(
JsonIgnoreProperties.class.getName(), cp);
BooleanMemberValue ignoreUnknownMemberValue = new BooleanMemberValue(false, cp);
//
ArrayMemberValue arrayMemberValue = new ArrayMemberValue(cp);
Collection<MemberValue> memberValues = new HashSet<MemberValue>();
for(int i=;i<values.length;i++){
StringMemberValue memberValue = new StringMemberValue(cp);// 将name值设入注解内
memberValue.setValue(values[i]);
memberValues.add(memberValue);
}
arrayMemberValue.setValue(memberValues.toArray(new MemberValue[]{}));
jsonIgnorePropertiesAnnotation.addMemberValue("value", arrayMemberValue);
jsonIgnorePropertiesAnnotation.addMemberValue("ignoreUnknown", ignoreUnknownMemberValue);
attr.addAnnotation(jsonIgnorePropertiesAnnotation);
classFile.addAttribute(attr);
Class clazz = cc.toClass();
return clazz;
}
protected void checkParamter(Object... objs){
boolean isTrue=true;
if(objs==null||objs.length<=){
isTrue=false;
}
for(Object obj:objs){
if(obj==null){
isTrue=false;
}
}
if(!isTrue){
throw new RuntimeException("参数出现错误");
}
}
}
上面这一步我们已经看到了熟悉的addMixInAnnotations。后面的参数就是我们使用javassist根据value数组创建的动态类,这个动态类增加了一个很重要的注解就是JsonIgnoreProperties(这个注解就是我们6中讲的过滤属性的),现在通过build方法返回的ObjectMapper已经满足了动态的过滤属性的。
下面是一个测试:
@Test
@IgnoreProperty(pojo=Article.class,value={"user"})
public void testJacksonAnnotation(){
User user=new User();
user.setName("hotusm");
Article a1=new Article();
a1.setTitle("t1");
a1.setUser(user);
Article a2=new Article();
a2.setTitle("t2");
a2.setUser(user);
Article a3=new Article();
a3.setTitle("t3");
a3.setUser(user);
List<Article> as=new ArrayList<Article>();
as.add(a1);
as.add(a2);
as.add(a3);
user.setArticles(as);
ObjectMapper objectMapper;
try {
objectMapper = new ObjectMapperBuilder().build(Main.class.getMethod("testJacksonAnnotation"));
String str = objectMapper.writeValueAsString(user);
System.out.println(str);
} catch (Exception e) {
e.printStackTrace();
}
}
在打印出来的json数据我们就可以明显的看出来已经把Article中的user属性给过滤掉了。(注意,user和article是一对多的关系)
总结:因为上面写的一个例子只是为了显示出问题,并没有进行代码的优化,以及功能的完善,如果是要在生产过程中使用的话,我们完全可以这样做:1.注解可以在类或者是方法上面2.所有多出来的操作都应该是对客户端程序员来说是透明的,我们可以通过方法的增强以及对ObjectMapper进行装饰。3.将方法或者类上面的注解信息放入到缓存中去,而不用发每次都要提取一次
JPA一对多循环引用的解决的更多相关文章
- JPA一对多循环引用的解决&&JackSon无限递归问题
说是解决,其实不是很完美的解决的,写出来只是想记录一下这个问题或者看一下有没有哪位仁兄会的,能否知道一二. 下面说说出现问题: 问题是这样的,当我查询一个一对多的实体的时候,工具直接就爆了,差不多我就 ...
- C#项目间循环引用的解决办法,有图有真相
C#项目间循环引用的解决办法,有图有真相 程序间的互相调用接口,c#禁止互相引用,海宏软件,20160315 /// c#禁止互相引用,如果项目[订单]中有一个orderEdit单元,要在项目[进销存 ...
- 在mvc返回JSON时出错:序列化类型为“System.Data.Entity.DynamicProxies.Photos....这个会的对象时检测到循环引用 的解决办法
在MVC中返回JSON时出错,序列化类型为“System.Data.Entity.DynamicProxies.Photos....这个会的对象时检测到循环引用. public ActionResul ...
- Block的使用及循环引用的解决
Block是一个很好用的东西,这篇文章主要来介绍:1.什么是Block?2.Block的使用?3.Block的循环引用问题及解决. 1.什么是Block? 说这个问题之前,我先来说一下闭包(Closu ...
- EF中Json序列化对象时检测到循环引用的解决办法
MVC4 EF中将数据表外键引用的是自身,转换成Json时,总是提示错误:“序列化类型为....的对象时检测到循环引用.”: 解决办法: 把要序列化的对象转为匿名对象去掉导航属性,如下 :本来是var ...
- IOS block 循环引用的解决
在介绍block循环引用前我们先了解一下typeof. typeof是什么??? typeof 是一个一元运算,放在一个运算数之前,运算数可以是任意类型. 它返回值是一个字符串,该字符串说明运算数的类 ...
- 解决ASP.NET MVC返回的JsonResult 中 日期类型数据格式问题,和返回的属性名称转为“驼峰命名法”和循环引用问题
DateTime类型数据格式问题 问题 在使用ASP.NET MVC 在写项目的时候发现,返回给前端的JSON数据,日期类型是 Date(121454578784541) 的格式,需要前端来转换一下才 ...
- 【JSON 注解】JSON循环引用1-----Jackson常用注解介绍 eq:@JsonIgnore
循环引用:实体A与实体B有关系,A中有B作为字段,B中有A作为一个字段.查询A对象后,将A对象转化为JSON格式数据时,会因为序列化过程中导致A中有B字段,B字段中又有A,这样就引起了循环引用的问题! ...
- iOS循环引用问题
今天面试问道了循环引用,所以就看了看,原来只是知道使用了Block容易造成循环引用.今天就来简单的介绍一些循环引用. 先来简单介绍一下什么是循环引用? 循环引用可以简单的理解成:A对象引用了B对象,B ...
随机推荐
- PSR规范
背景 Framework Interoperability Group(框架可互用性小组),简称 FIG,成立于 2009 年.FIG 最初由几位知名 PHP 框架开发者发起,在吸纳了许多优秀的大脑和 ...
- 3D打印:三维智能数字化创造(全彩)
3D打印:三维智能数字化创造(全彩)(全球第一本系统阐述3D打印与3D智能数字化的专业著作) 吴怀宇 编 ISBN 978-7-121-22063-0 2014年1月出版 定价:99.00元 42 ...
- Android Activity 启动模式和任务栈
在了解了基本的Activity的生命周期后,我们能够很好的在一个Activity上面做相关的业务.但是这是不够的,因为Android通过任务栈来保存整个APP的Activity,合理的调度任务栈才能够 ...
- android 权限大全
教程 博客 淘帖 论坛›eoe·Android开发资源区›Android开发实例教程 191507 12 / 2 页下一页 android 权限大全 『癲瘋霸気』 于 2013-4-3 10: ...
- 关于Git和Github你不知道的十件事
Git 和 GitHub都是非常强大的工具.即使你已经使用他们很长时间,你也很有可能不知道每个细节.我整理了Git和GitHub可能提高日常效率的10个常用技巧. GitHub 快捷键: t 和 w ...
- 深入理解line-height与vertical-align
前面的话 line-height.font-size.vertical-align是设置行内元素布局的关键属性.这三个属性是相互依赖的关系,改变行间距离.设置垂直对齐等都需要它们的通力合作.在CSS字 ...
- AIX碎碎念
1. 查看系统版本 oslevel 2. 查看系统内存 svmon -G (注意,size的单位为4k) 3. 查看机器型号 uname -Mu 4. 查看机器硬件信息,如cpu,内存等 prtcon ...
- 电路相关知识--读<<继电器是如何成为CPU的>>
电路相关知识–读<<继电器是如何成为CPU的>> */--> *///--> *///--> 电路相关知识–读<<继电器是如何成为CPU的> ...
- app令牌的一个token实现
app登陆验证不能使用session来判断了.然后查资料都说用令牌,没找到合适的方法,我的眼界太小.另外,越来越感觉基础的重要,比如,session是什么,我竟无言以对.不知道session是什么,怎 ...
- java中map插入相同的key
测试用例: package test; import org.junit.Test; import po.Person; import java.util.HashMap; import java.u ...