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@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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/458883.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

太原理工大学c语言课程设计报告,[太原理工大学C语言实验报告.doc

[太原理工大学C语言实验报告本科实验报告课程名称&#xff1a; 程序设计技术B实验项目&#xff1a;实验地点&#xff1a; 明向校区软件学院机房专业班级&#xff1a; 学号&#xff1a;学生姓名&#xff1a;指导教师&#xff1a; 呼克佑2014年 12月 日实验名称 实验一 C语言的运…

网页常用动态效果--悬浮广告

关键在于动态获取滚动坐标值 测试滚动事件 $(window).scroll(function(){ console.log($(window).scrolltop()); }) 获取三个高度&#xff1a;窗口高度&#xff0c;盒子高度以及滚动坐标值&#xff0c;将广告盒子设置为绝对定位&#xff0c;当鼠标滚动时&#xff0c;其top值为滚…

打印英文年历C语言函数,C语言打印年历

voidshow_year(int year){inti,j,k,t,n;                           // 用来辅助计数int table[24][21] {0};                     // 年历数组int month_day[12] {31,28,31,30,31,30,31,31,30,31,30,31}; // 每月上限天数i…

ubuntu14.04配置caffe

ubuntu 14.04 64bit 安装 请自行Google安装&#xff0c;并修改源。 cuda 7.5 安装 cuda7.5下载&#xff0c;选择ubuntu 14.04的deb包下载安装一些可能的依赖 sudo apt-get install linux-headers-$(uname -r) build-essential 安装cuda sudo dpkg –i cuda-repo-<distro>…

iOS开发API常用英语名词

iOS开发API常用英语名词 0. indicating 决定 1.in order to 以便 2.rectangle bounds 矩形尺寸 3.applied 应用 4.entirety 全部 5.technique 方法 6.truncating 截短 7.wrapping 换行 8.string 字符串 9.familiar style 简体 10.The styled text 主题样式 11.Constants 常量 …

Win10 IIS本地部署网站运行时图片和样式不正常?

后期会在博客首发更新&#xff1a;http://dnt.dkill.net 异常处理汇总-服 务 器 http://www.cnblogs.com/dunitian/p/4522983.html 启用关闭win功能&#xff0c;开启一下静态内容 收工~

设置IIS会话过期时间

打开默认网站----双击ASP--展开会话属性---更改超时时间-转载于:https://www.cnblogs.com/genesis/p/4816128.html

89c51单片机汇编语言,AT89C2051时钟程序——MCS51单片机汇编程序

;; AT89C2051时钟程序 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;定时器T0、T1溢出周期为50MS&#xff0c;T0为秒计数用&#xff0c; T1为调整时闪烁用&#xff0c;;P3.7为调整按钮&#xff0c;P1口 为字符输出口&#xff0c;采用共阳显示管。;;;;;;;;;;;;;;;;;;;;;;;;;;;;…

c语言结构体指针初始化

*************************************************** 更多精彩&#xff0c;欢迎进入&#xff1a;http://shop115376623.taobao.com *************************************************** 记得上周在饭桌上和同事讨论C语言的崛起时&#xff0c;讲到了内存管理方面 我说所有指…

WWDC 2013 Session笔记 - iOS7中弹簧式列表的制作

这是我的WWDC2013系列笔记中的一篇&#xff0c;完整的笔记列表请参看这篇总览。本文仅作为个人记录使用&#xff0c;也欢迎在许可协议范围内转载或使用&#xff0c;但是还烦请保留原文链接&#xff0c;谢谢您的理解合作。如果您觉得本站对您能有帮助&#xff0c;您可以使用RSS或…

H5学习之旅-H5列表(8)

列表的基本语法 ol&#xff1a;有序列表 ul&#xff1a;无序列表 li&#xff1a;列表项 dl&#xff1a;列表 dt&#xff1a;列表项 dd&#xff1a;列表描述 常用列表 1.无序列表&#xff1a;使用标签 ul&#xff0c;li 属性&#xff1a;disc&#xff08;默认实心圆&#xff09;…

c语言 文件夹是否纯真,C语言面试题大汇总之华为面试题(转)

1、局部变量能否和全局变量重名&#xff1f;答&#xff1a;能&#xff0c;局部会屏蔽全局。要用全局变量&#xff0c;需要使用 ":: "局部变量可以与全局变量同名&#xff0c;在函数内引用这个变量时&#xff0c;会用到同名的局部变量&#xff0c;而不会用到全局变量。…

virtual析构函数的作用?

*************************************************** 更多精彩&#xff0c;欢迎进入&#xff1a;http://shop115376623.taobao.com *************************************************** 大家知道&#xff0c;析构函数是为了在对象不被使用之后释放它的资源&#xff0c;虚函…

[codevs1039]数的划分

这一题实际上是组合数学里面的经典问题&#xff0c;跟第二类Stirling数有些相似。可以把一个数值为n的数看成n个小球&#xff0c;划分的份数k看作是k个盒子&#xff0c;那么本题的要求就是&#xff1a; 将n个小球放到k个盒子中&#xff0c;小球之间与盒子之间没有区别&#xff…

c语言中用文件处理数据,C语言文件处理 -C语言从文件中读写格式化数据

从图 1 所示的文件 fin.txt 中读取学生姓名、身高和体重&#xff0c;计算并显示它们的平均值&#xff0c;并且将显示结果保存到文件 fout.txt 中。图 1&#xff1a;输入输出文件示例C语言代码清单 1&#xff1a;读取学生姓名、身高和体重&#xff0c;计算并显示它们的平均值#in…

MySQL于ON DUPLICATE KEY UPDATE采用

今天我们做的推断插入用途MySQL于ON DUPLICATE KEY UPDATE。现在&#xff0c;Mark下面&#xff01;假设你想做的事&#xff0c;再有就是在数据库中插入数据没有数据、如果有数据更新数据&#xff0c;然后你可以选择ON DUPLICATE KEY UPDATE。 ON DUPLICATE KEY UPDATE可以在UNI…

C++中Static作用和使用方法

*************************************************** 更多精彩&#xff0c;欢迎进入&#xff1a;http://shop115376623.taobao.com *************************************************** 1、什么是static? static 是C中很常用的修饰符&#xff0c;它被用来控制变量的…

三星+android+7.0+自动纠正单词,升级党必看!三星S/Note系列更新Android 7.0指南

上周&#xff0c;雷科技(微信ID&#xff1a;leitech)曾为大家提供了一份Android 7.0升级预测名单。现在为了增加针对性&#xff0c;这次笔者就以三星最热门的两大旗舰S和Note系列为例&#xff0c;给大家提供一个全方面的更新指南。截至目前&#xff0c;三星S系列和Note系列的正…

iOS开发出错whose view is not in the window hierarchy!的解决

大熊猫猪侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 一个简单的单窗口App在运行时出现错误: 2016-04-07 14:28:48.411 BlurViewAndPopView[4364:168520] Warning: Attempt to present <UIAlertCon…

互联网年鉴

最近互联网行业&#xff0c;有一个词说的比较多了&#xff0c;“资本寒冬”。作为&#xff0c;一个在这个行业里干的人&#xff0c;真心是好怕怕呢。 妈蛋&#xff0c;真怕哪天就突然失业了呀。所以&#xff0c;无聊的(操蛋的)去整理了一些98年开始一直到现在的互联网中一些自己…