说是解决,其实不是很完美的解决的,写出来只是想记录一下这个问题或者看一下有没有哪位仁兄会的,能否知道一二。

下面说说出现问题:

问题是这样的,当我查询一个一对多的实体的时候,工具直接就爆了,差不多我就猜到是哪里死循环了,最后等了好久,查看原因,果然是堆溢出,再然后是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@Idprivate String id;/*** validate适用于springmvc*/@XmlElement//@NotEmptyprivate 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;}@Overridepublic 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;@Idprivate 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;}@Overridepublic 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>

这样就完美的解决了这个问题。

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) ? 0 : _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=0;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<=0){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.将方法或者类上面的注解信息放入到缓存中去,而不用发每次都要提取一次

转载于:https://www.cnblogs.com/zr520/p/5357459.html

JPA一对多循环引用的解决相关推荐

  1. php 解决循环引用,excel循环引用如何解决

    excel循环引用如何解决? 方法一 需要找到"excel选项",这个地方在Office 2003版本里的"工具"选项里,而在Office 2007版本里不好找 ...

  2. iOS: NSTimer的循环引用(解决)

    首先有两个概念写在最前: 内存泄漏:系统分配的内存空间在使用完毕之后没有进行及时的回收,称之为发生了内存泄漏. 内存溢出:指在申请内存的时候,没有足够的内存空间可以使用,包括栈溢出和堆溢出. 下面开始 ...

  3. nstimer循环引用_解决NSTimer循环引用导致内存泄漏的六种方法

    demo放在了GitHub 内存泄漏的原因: self强引用timer.timer添加在runloop上,只要timer不销毁self就销毁不了.当然了你可以选择在viewWillDisappear中 ...

  4. python 循环引用的解决方法

    目录 1.问题描述 2.原因 3.示例 4.解决办法 1.问题描述 编写python代码,运行中出现了:'most likely due to a circular import '  这样的报错. ...

  5. java解决循环引用_Java 中如何避免循环引用,解决相互依赖的问题

    spring中通过依赖注入的方法来解决类的相互依赖问题!!! spring中通过依赖注入的方法来解决类的相互依赖问题!!! spring中通过依赖注入的方法来解决类的相互依赖问题!!! 只要不是构造函 ...

  6. python循环引用的解决办法

    原文地址:http://blog.csdn.net/handsomekang/article/details/19010407 在python中常常会遇到循环import即circular impor ...

  7. 相亲app开发,解决内存循环引用的问题

    循环引用是什么 ARC已经出来很久了,自动释放内存的确很方便,但是在相亲app开发应用中,并非绝对安全绝对不会产生内存泄露.导致iOS对象无法按预期释放的一个无形杀手是--循环引用.循环引用可以简单理 ...

  8. Spring中解决循环引用

    目录 一.前言 二.源码分析 1.getBean(String name)方法 2.getSingleton(beanName)方法 3.getSingleton(String beanName, O ...

  9. ios 内存管理的理解(四)ARC下循环引用问题

    案例 一个人拥有一只狗,一只狗拥有一个主人. @interface Person :NSObject@property(nonatomic,strong) Dog* dog; @end @interf ...

最新文章

  1. python如何统计字典里面健的数量_Python中使用Counter进行字典创建以及key数量统计的方法...
  2. 开源oracle client,oracle client安装与配置
  3. 《死亡笔记》等书遭禁 北京全市封杀“鬼故事”
  4. 成都软件工程师python_为什么每个软件工程师都应该学习Python?
  5. 国家开放大学本科计算机应用基础,【(精华版)最新国家开放大学电大本科《计算机应用基础》网络课网考形考作业一及三试题答案】.docx...
  6. TreeView中右击直接获取节点的方法
  7. 《跟唐老师学习云网络》 - 什么是VLAN和VXLAN
  8. 大数据之-Hadoop之HDFS_HDFS存储块的大小设置_设置成多少合理_为什么不能设置太小也不能设置太大---大数据之hadoop工作笔记0051
  9. 开放式可编程保险市场Tidal Finance完成由KR1领投的195万美元种子轮融资
  10. Java3种错误_3种常见的Class级别的错误
  11. 警告框(AlertView)与进度轮结合使用
  12. svn安装以及初步使用
  13. python 3.6 安装 win32 win32com模块
  14. 搭建Cobbler无人值守安装服务器
  15. 工具分享:图片水印工具(WaterMarker)
  16. Xsolla与Ubisoft游戏Rainbow Six Siege 和刺客信条:辛迪加展开战略合作
  17. 数理逻辑3 -- 形式数论13
  18. 前端遇到的那些技术难点
  19. 怎样形成计算机知识体系,打牢基础,形成网络化知识体系
  20. Jmeter将响应结果导出到文件

热门文章

  1. leetcode557. 反转字符串中的单词 III python,处理字符串的神!
  2. 数据结构【插入操作具体代码的实现】
  3. GCC如何产生core dump
  4. LeetCode - Easy - 191. Number of 1 Bits
  5. IPFS下载安装和配置
  6. 从程序员角度看ELF
  7. 解决跨域问题:No ‘Access-Control-Allow-Origin‘ header is present on the requested resource.
  8. 解决报错: Connecting to Kong on http ... Could not reach Kong on http://xxx.xxx.xxx.xxx:8001
  9. 10 张图带你深入理解Docker容器和镜像
  10. 对List集合中的元素进行排序