Java Jackson JSON 和 Hibernate JPA 问题的无限递归
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3325387/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Infinite Recursion with Hymanson JSON and Hibernate JPA issue
提问by Ta Sas
When trying to convert a JPA object that has a bi-directional association into JSON, I keep getting
当尝试将具有双向关联的 JPA 对象转换为 JSON 时,我不断收到
org.codehaus.Hymanson.map.JsonMappingException: Infinite recursion (StackOverflowError)
All I found is this threadwhich basically concludes with recommending to avoid bi-directional associations. Does anyone have an idea for a workaround for this spring bug?
我发现的只是这个线程,它基本上以建议避免双向关联结束。有没有人对这个春季错误的解决方法有想法?
------ EDIT 2010-07-24 16:26:22 -------
------ 编辑 2010-07-24 16:26:22-------
Codesnippets:
代码片段:
Business Object 1:
业务对象 1:
@Entity
@Table(name = "ta_trainee", uniqueConstraints = {@UniqueConstraint(columnNames = {"id"})})
public class Trainee extends BusinessObject {
@Id
@GeneratedValue(strategy = GenerationType.TABLE)
@Column(name = "id", nullable = false)
private Integer id;
@Column(name = "name", nullable = true)
private String name;
@Column(name = "surname", nullable = true)
private String surname;
@OneToMany(mappedBy = "trainee", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@Column(nullable = true)
private Set<BodyStat> bodyStats;
@OneToMany(mappedBy = "trainee", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@Column(nullable = true)
private Set<Training> trainings;
@OneToMany(mappedBy = "trainee", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@Column(nullable = true)
private Set<ExerciseType> exerciseTypes;
public Trainee() {
super();
}
... getters/setters ...
Business Object 2:
业务对象 2:
import javax.persistence.*;
import java.util.Date;
@Entity
@Table(name = "ta_bodystat", uniqueConstraints = {@UniqueConstraint(columnNames = {"id"})})
public class BodyStat extends BusinessObject {
@Id
@GeneratedValue(strategy = GenerationType.TABLE)
@Column(name = "id", nullable = false)
private Integer id;
@Column(name = "height", nullable = true)
private Float height;
@Column(name = "measuretime", nullable = false)
@Temporal(TemporalType.TIMESTAMP)
private Date measureTime;
@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinColumn(name="trainee_fk")
private Trainee trainee;
Controller:
控制器:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@Controller
@RequestMapping(value = "/trainees")
public class TraineesController {
final Logger logger = LoggerFactory.getLogger(TraineesController.class);
private Map<Long, Trainee> trainees = new ConcurrentHashMap<Long, Trainee>();
@Autowired
private ITraineeDAO traineeDAO;
/**
* Return json repres. of all trainees
*/
@RequestMapping(value = "/getAllTrainees", method = RequestMethod.GET)
@ResponseBody
public Collection getAllTrainees() {
Collection allTrainees = this.traineeDAO.getAll();
this.logger.debug("A total of " + allTrainees.size() + " trainees was read from db");
return allTrainees;
}
}
JPA-implementation of the trainee DAO:
JPA-实习生DAO的实现:
@Repository
@Transactional
public class TraineeDAO implements ITraineeDAO {
@PersistenceContext
private EntityManager em;
@Transactional
public Trainee save(Trainee trainee) {
em.persist(trainee);
return trainee;
}
@Transactional(readOnly = true)
public Collection getAll() {
return (Collection) em.createQuery("SELECT t FROM Trainee t").getResultList();
}
}
persistence.xml
持久化文件
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="RDBMS" transaction-type="RESOURCE_LOCAL">
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="hibernate.hbm2ddl.auto" value="validate"/>
<property name="hibernate.archive.autodetection" value="class"/>
<property name="dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect"/>
<!-- <property name="dialect" value="org.hibernate.dialect.HSQLDialect"/> -->
</properties>
</persistence-unit>
</persistence>
采纳答案by axtavt
You may use @JsonIgnore
to break the cycle.
你可以@JsonIgnore
用来打破这个循环。
回答by StaxMan
Also, Hymanson 1.6 has support for handling bi-directional references... which seems like what you are looking for (this blog entryalso mentions the feature)
此外,Hymanson 1.6 支持处理双向引用......这似乎是您正在寻找的(此博客条目也提到了该功能)
And as of July 2011, there is also "Hymanson-module-hibernate" which might help in some aspects of dealing with Hibernate objects, although not necessarily this particular one (which does require annotations).
截至 2011 年 7 月,还有“ Hymanson-module-hibernate”,它可能有助于处理 Hibernate 对象的某些方面,尽管不一定是这个特定的(确实需要注释)。
回答by Eugene Retunsky
Now Hymanson supports avoiding cycles without ignoring the fields:
现在 Hymanson 支持在不忽略字段的情况下避免循环:
Hymanson - serialization of entities with birectional relationships (avoiding cycles)
回答by Kurt Bourbaki
JsonIgnoreProperties [2017 Update]:
JsonIgnoreProperties [2017 更新]:
You can now use JsonIgnorePropertiesto suppress serialization of properties (during serialization), or ignore processing of JSON properties read (during deserialization). If this is not what you're looking for, please keep reading below.
您现在可以使用JsonIgnoreProperties来抑制属性的序列化(在序列化期间),或忽略读取 JSON 属性的处理(在反序列化期间)。如果这不是您要找的内容,请继续阅读下面的内容。
(Thanks to As Zammel AlaaEddine for pointing this out).
(感谢 As Zammel AlaaEddine 指出这一点)。
JsonManagedReference and JsonBackReference
JsonManagedReference 和 JsonBackReference
Since Hymanson 1.6 you can use two annotations to solve the infinite recursion problem without ignoring the getters/setters during serialization: @JsonManagedReference
and @JsonBackReference
.
从 Hymanson 1.6 开始,您可以使用两个注释来解决无限递归问题,而无需在序列化过程中忽略 getter/setter:@JsonManagedReference
和@JsonBackReference
.
Explanation
解释
For Hymanson to work well, one of the two sides of the relationship should not be serialized, in order to avoid the infite loop that causes your stackoverflow error.
为了让Hymanson 正常工作,关系的两端之一不应该被序列化,以避免导致你的stackoverflow 错误的无限循环。
So, Hymanson takes the forward part of the reference (your Set<BodyStat> bodyStats
in Trainee class), and converts it in a json-like storage format; this is the so-called marshallingprocess. Then, Hymanson looks for the back part of the reference (i.e. Trainee trainee
in BodyStat class) and leaves it as it is, not serializing it. This part of the relationship will be re-constructed during the deserialization (unmarshalling) of the forward reference.
因此,Hymanson 将引用的前向部分(您Set<BodyStat> bodyStats
在 Trainee 类中)转换为类似 json 的存储格式;这就是所谓的编组过程。然后,Hymanson 查找引用的后面部分(即Trainee trainee
在 BodyStat 类中)并保持原样,而不是对其进行序列化。这部分关系将在前向引用的反序列化(解组)期间重新构建。
You can change your code like this (I skip the useless parts):
您可以像这样更改代码(我跳过了无用的部分):
Business Object 1:
业务对象 1:
@Entity
@Table(name = "ta_trainee", uniqueConstraints = {@UniqueConstraint(columnNames = {"id"})})
public class Trainee extends BusinessObject {
@OneToMany(mappedBy = "trainee", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@Column(nullable = true)
@JsonManagedReference
private Set<BodyStat> bodyStats;
Business Object 2:
业务对象 2:
@Entity
@Table(name = "ta_bodystat", uniqueConstraints = {@UniqueConstraint(columnNames = {"id"})})
public class BodyStat extends BusinessObject {
@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinColumn(name="trainee_fk")
@JsonBackReference
private Trainee trainee;
Now it all should work properly.
现在一切都应该正常工作了。
If you want more informations, I wrote an article about Json and Hymanson Stackoverflow issues on Keenformatics, my blog.
如果您想了解更多信息,我在我的博客Keenformatics 上写了一篇关于Json 和 Hymanson Stackoverflow 问题的文章。
EDIT:
编辑:
Another useful annotation you could check is @JsonIdentityInfo: using it, everytime Hymanson serializes your object, it will add an ID (or another attribute of your choose) to it, so that it won't entirely "scan" it again everytime. This can be useful when you've got a chain loop between more interrelated objects (for example: Order -> OrderLine -> User -> Order and over again).
您可以检查的另一个有用的注释是@JsonIdentityInfo:使用它,每次 Hymanson 序列化您的对象时,它都会为其添加一个 ID(或您选择的另一个属性),这样它就不会每次都完全“扫描”它。当您在更多相互关联的对象(例如:Order -> OrderLine -> User -> Order 等)之间有一个链循环时,这会很有用。
In this case you've got to be careful, since you could need to read your object's attributes more than once (for example in a products list with more products that share the same seller), and this annotation prevents you to do so. I suggest to always take a look at firebug logs to check the Json response and see what's going on in your code.
在这种情况下,您必须小心,因为您可能需要多次读取对象的属性(例如,在具有共享同一卖家的更多产品的产品列表中),而此注释会阻止您这样做。我建议始终查看萤火虫日志以检查 Json 响应并查看代码中发生了什么。
Sources:
资料来源:
- Keenformatics - How To Solve JSON infinite recursion Stackoverflow(my blog)
- Hymanson References
- Personal experience
回答by Marcus
Also, using Hymanson 2.0+ you can use @JsonIdentityInfo
. This worked much better for my hibernate classes than @JsonBackReference
and @JsonManagedReference
, which had problems for me and did not solve the issue. Just add something like:
此外,使用 Hymanson 2.0+,您可以使用@JsonIdentityInfo
. 这对我的休眠类来说比@JsonBackReference
and更好@JsonManagedReference
,后者对我有问题并且没有解决问题。只需添加如下内容:
@Entity
@Table(name = "ta_trainee", uniqueConstraints = {@UniqueConstraint(columnNames = {"id"})})
@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@traineeId")
public class Trainee extends BusinessObject {
@Entity
@Table(name = "ta_bodystat", uniqueConstraints = {@UniqueConstraint(columnNames = {"id"})})
@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@bodyStatId")
public class BodyStat extends BusinessObject {
and it should work.
它应该工作。
回答by Klapsa2503
In my case it was enough to change relation from:
在我的情况下,改变关系就足够了:
@OneToMany(mappedBy = "county")
private List<Town> towns;
to:
到:
@OneToMany
private List<Town> towns;
another relation stayed as it was:
另一个关系保持原样:
@ManyToOne
@JoinColumn(name = "county_id")
private County county;
回答by Shane
There's now a Hymanson module (for Hymanson 2) specifically designed to handle Hibernate lazy initialization problems when serializing.
现在有一个 Hymanson 模块(用于 Hymanson 2)专门设计用于在序列化时处理 Hibernate 延迟初始化问题。
https://github.com/FasterXML/Hymanson-datatype-hibernate
https://github.com/FasterXML/Hymanson-datatype-hibernate
Just add the dependency (note there are different dependencies for Hibernate 3 and Hibernate 4):
只需添加依赖项(注意 Hibernate 3 和 Hibernate 4 有不同的依赖项):
<dependency>
<groupId>com.fasterxml.Hymanson.datatype</groupId>
<artifactId>Hymanson-datatype-hibernate4</artifactId>
<version>2.4.0</version>
</dependency>
and then register the module when intializing Hymanson's ObjectMapper:
然后在初始化 Hymanson 的 ObjectMapper 时注册模块:
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Hibernate4Module());
Documentation currently isn't great. See the Hibernate4Module codefor available options.
文档目前不是很好。有关可用选项,请参阅Hibernate4Module 代码。
回答by Manjunath BR
This worked perfectly fine for me. Add the annotation @JsonIgnore on the child class where you mention the reference to the parent class.
这对我来说非常好。在您提到对父类的引用的子类上添加注释 @JsonIgnore。
@ManyToOne
@JoinColumn(name = "ID", nullable = false, updatable = false)
@JsonIgnore
private Member member;
回答by Dahar Youssef
you can use DTO pattern create class TraineeDTO without any anotation hiberbnate and you can use Hymanson mapper to convert Trainee to TraineeDTO and bingo the error message disapeare :)
您可以使用 DTO 模式创建类 TraineeDTO,而无需任何注释 hiberbnate,您可以使用 Hymanson mapper 将 Trainee 转换为 TraineeDTO 并宾果游戏,错误消息消失:)
回答by OJVM
I had this problem, but I didn't want to use annotation in my entities, so I solved by creating a constructor for my class, this constructor must not have a reference back to the entities who references this entity. Let's say this scenario.
我遇到了这个问题,但我不想在我的实体中使用注释,所以我通过为我的类创建一个构造函数来解决,这个构造函数不能引用回引用这个实体的实体。让我们说这个场景。
public class A{
private int id;
private String code;
private String name;
private List<B> bs;
}
public class B{
private int id;
private String code;
private String name;
private A a;
}
If you try to send to the view the class B
or A
with @ResponseBody
it may cause an infinite loop. You can write a constructor in your class and create a query with your entityManager
like this.
如果您尝试向视图发送类B
或A
使用@ResponseBody
它可能会导致无限循环。你可以在你的类中编写一个构造函数并用你的方式创建一个查询entityManager
。
"select new A(id, code, name) from A"
This is the class with the constructor.
这是带有构造函数的类。
public class A{
private int id;
private String code;
private String name;
private List<B> bs;
public A(){
}
public A(int id, String code, String name){
this.id = id;
this.code = code;
this.name = name;
}
}
However, there are some constrictions about this solution, as you can see, in the constructor I did not make a reference to List bsthis is because Hibernate does not allow it, at least in version 3.6.10.Final, so when I need to show both entities in a view I do the following.
然而,这个解决方案有一些限制,正如你所看到的,在构造函数中我没有引用List bs这是因为 Hibernate 不允许它,至少在3.6.10.Final 版本中,所以当我需要时为了在视图中显示两个实体,我执行以下操作。
public A getAById(int id); //THE A id
public List<B> getBsByAId(int idA); //the A id.
The other problem with this solution, is that if you add or remove a property you must update your constructor and all your queries.
此解决方案的另一个问题是,如果添加或删除属性,则必须更新构造函数和所有查询。