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

下面说说出现问题:

问题是这样的,当我查询一个一对多的实体的时候,工具直接就爆了,差不多我就猜到是哪里死循环了,最后等了好久,查看原因,果然是堆溢出,再然后是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一对多循环引用的解决的更多相关文章

  1. JPA一对多循环引用的解决&&JackSon无限递归问题

    说是解决,其实不是很完美的解决的,写出来只是想记录一下这个问题或者看一下有没有哪位仁兄会的,能否知道一二. 下面说说出现问题: 问题是这样的,当我查询一个一对多的实体的时候,工具直接就爆了,差不多我就 ...

  2. C#项目间循环引用的解决办法,有图有真相

    C#项目间循环引用的解决办法,有图有真相 程序间的互相调用接口,c#禁止互相引用,海宏软件,20160315 /// c#禁止互相引用,如果项目[订单]中有一个orderEdit单元,要在项目[进销存 ...

  3. 在mvc返回JSON时出错:序列化类型为“System.Data.Entity.DynamicProxies.Photos....这个会的对象时检测到循环引用 的解决办法

    在MVC中返回JSON时出错,序列化类型为“System.Data.Entity.DynamicProxies.Photos....这个会的对象时检测到循环引用. public ActionResul ...

  4. Block的使用及循环引用的解决

    Block是一个很好用的东西,这篇文章主要来介绍:1.什么是Block?2.Block的使用?3.Block的循环引用问题及解决. 1.什么是Block? 说这个问题之前,我先来说一下闭包(Closu ...

  5. EF中Json序列化对象时检测到循环引用的解决办法

    MVC4 EF中将数据表外键引用的是自身,转换成Json时,总是提示错误:“序列化类型为....的对象时检测到循环引用.”: 解决办法: 把要序列化的对象转为匿名对象去掉导航属性,如下 :本来是var ...

  6. IOS block 循环引用的解决

    在介绍block循环引用前我们先了解一下typeof. typeof是什么??? typeof 是一个一元运算,放在一个运算数之前,运算数可以是任意类型. 它返回值是一个字符串,该字符串说明运算数的类 ...

  7. 解决ASP.NET MVC返回的JsonResult 中 日期类型数据格式问题,和返回的属性名称转为“驼峰命名法”和循环引用问题

    DateTime类型数据格式问题 问题 在使用ASP.NET MVC 在写项目的时候发现,返回给前端的JSON数据,日期类型是 Date(121454578784541) 的格式,需要前端来转换一下才 ...

  8. 【JSON 注解】JSON循环引用1-----Jackson常用注解介绍 eq:@JsonIgnore

    循环引用:实体A与实体B有关系,A中有B作为字段,B中有A作为一个字段.查询A对象后,将A对象转化为JSON格式数据时,会因为序列化过程中导致A中有B字段,B字段中又有A,这样就引起了循环引用的问题! ...

  9. iOS循环引用问题

    今天面试问道了循环引用,所以就看了看,原来只是知道使用了Block容易造成循环引用.今天就来简单的介绍一些循环引用. 先来简单介绍一下什么是循环引用? 循环引用可以简单的理解成:A对象引用了B对象,B ...

随机推荐

  1. [Spring框架]Spring AOP基础入门总结一.

    前言:前面已经有两篇文章讲了Spring IOC/DI 以及 使用xml和注解两种方法开发的案例, 下面就来梳理一下Spring的另一核心AOP. 一, 什么是AOP 在软件业,AOP为Aspect ...

  2. Atitit  基于meta的orm,提升加速数据库相关应用的开发

    Atitit  基于meta的orm,提升加速数据库相关应用的开发 1.1. Overview概论1 1.2. Function & Feature功能特性1 1.2.1. meta api2 ...

  3. iOS-数据持久化基础-沙盒机制

    沙盒详解 1.IOS沙盒机制 IOS应用程序只能在为该改程序创建的文件系统中读取文件,不可以去其它地方访问,此区域被成为沙盒,所以所有的非代码文件都要保存在此,例如图像,图标,声音,映像,属性列表,文 ...

  4. Django ORM - 001 - 外键表查询主表信息

    开始用Django做web开发,我想大家都会遇到同样的问题,那就是如何高效快速的查询需要的数据,MVC都很简单,但是ORM折腾起来就有些费时间,我准备好好研究下Django ORM,所以会有一个系列的 ...

  5. 创建oracle数据库job服务:PlSqlDev操作job

    PlSqlDev操作job 创建job 1.选择job文件夹,右键     2.点击新建     3.对应填写完成,可以点击“查看SQL”查看sql语句,确定无误后,点击“应用”即创建完成 4.此时, ...

  6. universal image loader自己使用的一些感受

    1.全局入口的Application定义初始化: ImageLoaderConfiguration configuration = new ImageLoaderConfiguration.Build ...

  7. SQL Server中的版本号

        在SQL Server中,通常版本号的命名是大版本.小版本.累积更新这种形式,比如说9.X.XXX就是SQL Server 2005.下面我将把SQL Server中版本号对应的版本列出来,以 ...

  8. Linux下程序包管理工具RPM

    实验环境: CentOS release 6.6 (Final)  一台 IP地址:172.16.249.230 RPM 是 Red Hat Package Manager 的缩写,本意是Red Ha ...

  9. 了解HTML表单之input元素的23种type类型

    目录 传统类型 text password file radio checkbox hidden button image reset submit 新增类型 color tel email url ...

  10. backbone库学习-Router

    backbone库的结构http://www.cnblogs.com/nuysoft/archive/2012/03/19/2404274.html 本文的例子来自http://blog.csdn.n ...