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 ...
随机推荐
- Java-条件语句、循环语句练习
题目一:一张纸的厚度大约是0.08mm,对折多少次之后能达到珠穆朗玛峰的高度(8848.13米)? double height=0.08; for(int i=1;i>0;i++) { heig ...
- Android开发学习之路-Service和Activity的通信
在很多时候,Service都不仅仅需要在后台运行,还需要和Activity进行通信,或者接受Activity的指挥,如何来实现,来看代码. 定义一个服务 // 创建一个服务,然后在onBind()中返 ...
- Atitti usrQBf1801 翻页控件规范 v2
Atitti usrQBf1801 翻页控件规范 v2 1. 参考api 参考easyui ,.net系列的1 1.1. 翻页流程 初始化翻页控件,以及绑定新页面event onSelectPa ...
- IOS开发之控件篇UICollectionViewControllor第一章 - 普通介绍
1.介绍 UICollectionView和UICollectionViewControllor是IOS6.0后引入的新控件 使用UICollectionView必须实现三个接口: UICollect ...
- zabbix 3.0 安装 ubuntu环境
zabbix 3.0 安装 标签(空格分隔): 开发 [TOC] 下载deb # wget http://repo.zabbix.com/zabbix/3.0/ubuntu/pool/main/z/z ...
- C++标准库vector类型详解
Vector简介 vector是定义在C++标准模板库,它是一个多功能.能够操作多种数据结构和算法的模板类(关于模板类我们后面会介绍,如何创建自己的模板类).vector是一个容器,能够像容器一样存放 ...
- .NET实现Office Excel自定义公式 广泛应用于报表与数据分析
在管理软件开发的功能点中,有相当一部分功能是与Excel做数据交互,产生Excel 数据报表.如果Excel报表的数据计算方法很有规律可循,则可以通过自定义公式来解决.比如常见的资产负债表,利润表,取 ...
- ASP.NET MVC中简单使用Autofac
项目中引入Autofac的目的是为了实现控制反转,即IoC,Inversion of Control.控制反转可以有效的降低类之间的相互依赖关系,增加架构的弹性,降低软件复杂度. 示例代码: IPro ...
- RWD Table Patterns – 响应式表格解决方案
在显示复杂的表格数据的时候,相信 Web 开发人员都碰到过显示不下的情况.RWD Table Patterns 是一个很好的响应式表格解决方案.它采用移动优先以及渐进增强的设计理念,在不支持响应式的浏 ...
- Web 前端开发精华文章集锦(jQuery、HTML5、CSS3)【系列十九】
<Web 前端开发精华文章推荐>2013年第七期(总第十九期)和大家见面了.梦想天空博客关注 前端开发 技术,分享各种增强网站用户体验的 jQuery 插件,展示前沿的 HTML5 和 C ...