When trying to convert a JPA object that has a bi-directional association into JSON, I keep getting
当尝试将具有双向关联的JPA对象转换为JSON时,我一直在获取
org.codehaus.jackson.map.JsonMappingException: Infinite recursion (*Error)
All I found is this thread which basically concludes with recommending to avoid bi-directional associations. Does anyone have an idea for a workaround for this spring bug?
我所发现的是这个线程,它的结论基本上是建议避免双向关联。有人对这个spring bug有什么想法吗?
------ EDIT 2010-07-24 16:26:22 -------
------- ----编辑------- ------- ----。
Codesnippets:
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:
实习刀的实施:
@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 . 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>
18 个解决方案
#2
447
JsonIgnoreProperties [2017 Update]:
You can now use JsonIgnoreProperties to 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).
(感谢Zammel AlaaEddine指出这一点)。
JsonManagedReference and JsonBackReference
Since Jackson 1.6 you can use two annotations to solve the infinite recursion problem without ignoring the getters/setters during serialization: @JsonManagedReference
and @JsonBackReference
.
由于Jackson 1.6,您可以使用两个注释来解决无限递归问题,而不必在序列化期间忽略getters/setters: @JsonManagedReference和@JsonBackReference。
Explanation
解释
For Jackson 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 * error.
对于Jackson来说,关系的两个方面之一不应该被序列化,以避免导致*错误的infite循环。
So, Jackson 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 marshalling process. Then, Jackson 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.
因此,Jackson将引用的forward部分(您的Set
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 Jackson * issues on Keenformatics, my blog.
如果你想了解更多的信息,我在我的博客Keenformatics上写了一篇关于Json和Jackson *的文章。
EDIT:
编辑:
Another useful annotation you could check is @JsonIdentityInfo: using it, everytime Jackson 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:使用它,每当Jackson序列化对象时,它将向对象添加一个ID(或您选择的另一个属性),这样它就不会每次都“扫描”它。当您在更多相互关联的对象之间有一个链循环时(例如:Order -> OrderLine -> User -> Order和over),这将非常有用。
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.
在这种情况下,您必须非常小心,因为您可能需要多次读取您的对象的属性(例如,在产品列表中有更多的产品共享同一销售者),而这个注释阻止您这样做。我建议经常查看firebug日志,检查Json响应,看看代码中发生了什么。
Sources:
来源:
- Keenformatics - How To Solve JSON infinite recursion * (my blog)
- Keenformatics—如何解决JSON无限递归*(我的博客)
- Jackson References
- 杰克逊的引用
- Personal experience
- 个人经验
#3
66
The new annotation @JsonIgnoreProperties resolves many of the issues with the other options.
新的注释@JsonIgnoreProperties用其他选项解决了许多问题。
@Entity
public class Material{
...
@JsonIgnoreProperties("costMaterials")
private List<Supplier> costSuppliers = new ArrayList<>();
...
}
@Entity
public class Supplier{
...
@JsonIgnoreProperties("costSuppliers")
private List<Material> costMaterials = new ArrayList<>();
....
}
Check it out here. It works just like in the documentation:
http://springquay.blogspot.com/2016/01/new-approach-to-solve-json-recursive.html
检查出来。它的工作方式与文档类似:http://springquay.blogspot.com/2016/01/new-approach to solve-json-recursive.html
#4
41
Also, using Jackson 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:
此外,使用Jackson 2.0+还可以使用@JsonIdentityInfo。这对于我的hibernate类来说比@JsonBackReference和@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.
它应该工作。
#5
19
Also, Jackson 1.6 has support for handling bi-directional references... which seems like what you are looking for (this blog entry also mentions the feature)
此外,Jackson 1.6支持处理双向引用…这似乎是你在寻找的(这个博客条目也提到了这个功能)
And as of July 2011, there is also "jackson-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月开始,还有“jackson-module-hibernate”,它可能在处理Hibernate对象的某些方面有所帮助,尽管不一定是这个特定的对象(它需要注解)。
#6
11
Now Jackson supports avoiding cycles without ignoring the fields:
现在Jackson支持避免循环而不忽略字段:
Jackson - serialization of entities with birectional relationships (avoiding cycles)
Jackson -具有双向关系的实体的序列化(避免循环)
#7
7
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;
#8
6
There's now a Jackson module (for Jackson 2) specifically designed to handle Hibernate lazy initialization problems when serializing.
现在有一个专门为Jackson 2设计的Jackson模块,用于在序列化时处理Hibernate延迟初始化问题。
https://github.com/FasterXML/jackson-datatype-hibernate
https://github.com/FasterXML/jackson-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.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate4</artifactId>
<version>2.4.0</version>
</dependency>
and then register the module when intializing Jackson's ObjectMapper:
然后在初始化Jackson的ObjectMapper时注册模块:
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Hibernate4Module());
Documentation currently isn't great. See the Hibernate4Module code for available options.
目前文档不是很好。有关可用选项,请参见Hibernate4Module代码。
#9
4
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;
#10
4
For me the best solution is to use @JsonView
and create specific filters for each scenario. You could also use @JsonManagedReference
and @JsonBackReference
, however it is a hardcoded solution to only one situation, where the owner always references the owning side, and never the opposite. If you have another serialization scenario where you need to re-annotate the attribute differently, you will not be able to.
对我来说,最好的解决方案是使用@JsonView并为每个场景创建特定的过滤器。您也可以使用@JsonManagedReference和@JsonBackReference,但是它是针对只有一种情况的硬编码解决方案,在这种情况下,所有者总是引用所有者一方,而不是相反。如果您有另一个序列化场景,需要以不同的方式重新注释属性,您将无法这样做。
Problem
Lets use two classes, Company
and Employee
where you have a cyclic dependency between them:
让我们使用两个类,公司和员工,在它们之间存在循环依赖:
public class Company {
private Employee employee;
public Company(Employee employee) {
this.employee = employee;
}
public Employee getEmployee() {
return employee;
}
}
public class Employee {
private Company company;
public Company getCompany() {
return company;
}
public void setCompany(Company company) {
this.company = company;
}
}
And the test class that tries to serialize using ObjectMapper
(Spring Boot):
使用ObjectMapper (Spring Boot)进行序列化的测试类:
@SpringBootTest
@RunWith(SpringRunner.class)
@Transactional
public class CompanyTest {
@Autowired
public ObjectMapper mapper;
@Test
public void shouldSaveCompany() throws JsonProcessingException {
Employee employee = new Employee();
Company company = new Company(employee);
employee.setCompany(company);
String jsonCompany = mapper.writeValueAsString(company);
System.out.println(jsonCompany);
assertTrue(true);
}
}
If you run this code, you'll get the:
如果您运行此代码,您将得到:
org.codehaus.jackson.map.JsonMappingException: Infinite recursion (*Error)
Solution Using `@JsonView`
@JsonView
enables you to use filters and choose what fields should be included while serializing the objects. A filter is just a class reference used as a identifier. So let's first create the filters:
@JsonView使您能够使用筛选器并选择在序列化对象时应该包含哪些字段。过滤器只是作为标识符使用的类引用。让我们首先创建过滤器:
public class Filter {
public static interface EmployeeData {};
public static interface CompanyData extends EmployeeData {};
}
Remember, the filters are dummy classes, just used for specifying the fields with the @JsonView
annotation, so you can create as many as you want and need. Let's see it in action, but first we need to annotate our Company
class:
请记住,过滤器是伪类,仅用于使用@JsonView注释指定字段,因此可以创建任意数量的过滤器。让我们看看它的实际应用,但首先我们需要对我们的公司类进行注解:
public class Company {
@JsonView(Filter.CompanyData.class)
private Employee employee;
public Company(Employee employee) {
this.employee = employee;
}
public Employee getEmployee() {
return employee;
}
}
and change the Test in order for the serializer to use the View:
并更改测试,以便序列化器使用视图:
@SpringBootTest
@RunWith(SpringRunner.class)
@Transactional
public class CompanyTest {
@Autowired
public ObjectMapper mapper;
@Test
public void shouldSaveCompany() throws JsonProcessingException {
Employee employee = new Employee();
Company company = new Company(employee);
employee.setCompany(company);
ObjectWriter writter = mapper.writerWithView(Filter.CompanyData.class);
String jsonCompany = writter.writeValueAsString(company);
System.out.println(jsonCompany);
assertTrue(true);
}
}
Now if you run this code, the Infinite Recursion problem is solved, because you have explicitly said that you just want to serialize the attributes that were annotated with @JsonView(Filter.CompanyData.class)
.
现在,如果您运行这段代码,那么无限递归问题就解决了,因为您已经明确地说过,您只想序列化使用@JsonView(Filter.CompanyData.class)注释的属性。
When it reaches the back reference for company in the Employee
, it checks that it's not annotated and ignore the serialization. You also have a powerful and flexible solution to choose which data you want to send through your REST APIs.
当它到达雇员中的公司的后引用时,它检查它是否没有注释并忽略序列化。您还拥有一个功能强大且灵活的解决方案,可以选择通过REST api发送哪些数据。
With Spring you can annotate your REST Controllers methods with the desired @JsonView
filter and the serialization is applied transparently to the returning object.
使用Spring,您可以使用所需的@JsonView过滤器注释REST控制器方法,序列化透明地应用于返回的对象。
Here are the imports used in case you need to check:
以下是您需要检查的进口产品:
import static org.junit.Assert.assertTrue;
import javax.transaction.Transactional;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.annotation.JsonView;
#11
2
Be sure you use com.fasterxml.jackson everywhere. I spent much time to find it out.
一定要使用com.fasterxml。杰克逊无处不在。我花了很多时间才找到它。
<properties>
<fasterxml.jackson.version>2.9.2</fasterxml.jackson.version>
</properties>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${fasterxml.jackson.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${fasterxml.jackson.version}</version>
</dependency>
Then use @JsonManagedReference
and @JsonBackReference
.
然后使用@JsonManagedReference和@JsonBackReference。
Finally, you can serialize your model to JSON:
最后,可以将模型序列化为JSON:
import com.fasterxml.jackson.databind.ObjectMapper;
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(model);
#12
1
you can use DTO pattern create class TraineeDTO without any anotation hiberbnate and you can use jackson mapper to convert Trainee to TraineeDTO and bingo the error message disapeare :)
您可以使用DTO模式创建类TraineeDTO,而不使用任何阳极休眠,还可以使用jackson mapper将受训人员转换为TraineeDTO和bingo错误消息disapeare:)
#13
1
I also met the same problem. I used @JsonIdentityInfo
's ObjectIdGenerators.PropertyGenerator.class
generator type.
我也遇到了同样的问题。我用@JsonIdentityInfo ObjectIdGenerators.PropertyGenerator。类发电机类型。
That's my solution:
这是我的解决方案:
@Entity
@Table(name = "ta_trainee", uniqueConstraints = {@UniqueConstraint(columnNames = {"id"})})
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Trainee extends BusinessObject {
...
#14
1
You can use @JsonIgnore, but this will ignore the json data which can be accessed because of the Foreign Key relationship. Therefore if you reqiure the foreign key data (most of the time we require), then @JsonIgnore will not help you. In such situation please follow the below solution.
您可以使用@JsonIgnore,但这将忽略json数据,因为外键关系可以访问这些数据。因此,如果重新获取外键数据(大部分时间我们都需要),则@JsonIgnore不会帮助您。在这种情况下,请遵循下面的解决方案。
you are getting Infinite recursion, because of the BodyStat class again referring the Trainee object
你会得到无限递归,因为BodyStat类再次引用受训对象
BodyStat
BodyStat
@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinColumn(name="trainee_fk")
private Trainee trainee;
Trainee
实习
@OneToMany(mappedBy = "trainee", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@Column(nullable = true)
private Set<BodyStat> bodyStats;
Therefore, you have to comment/omit the above part in Trainee
因此,你必须在培训生中评论/省略上面的部分
#15
0
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或带有@ResponseBody的A发送到视图,它可能会导致一个无限循环。您可以在类中编写构造函数,并使用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 bs this 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.
但是,这个解决方案有一些限制,您可以看到,在构造函数中我没有引用清单bs,这是因为Hibernate不允许这样做,至少在3.6.10版本中是这样。最后,当我需要在视图中显示两个实体时,我将执行以下操作。
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.
此解决方案的另一个问题是,如果添加或删除属性,则必须更新构造函数和所有查询。
#16
0
In case you are using Spring Data Rest, issue can be resolved by creating Repositories for every Entity involved in cyclical references.
如果您使用的是Spring数据Rest,可以通过为涉及到循环引用的每个实体创建存储库来解决问题。
#17
0
@JsonIgnoreProperties is the answer.
@JsonIgnoreProperties就是答案。
Use something like this ::
使用如下内容:
@OneToMany(mappedBy = "course",fetch=FetchType.EAGER)
@JsonIgnoreProperties("course")
private Set<Student> students;
#18
0
Working fine for me Resolve Json Infinite Recursion problem when working with Jackson
在使用Jackson时,我很好地解决了Json无限递归问题
This is what I have done in oneToMany and ManyToOne Mapping
这是我在oneToMany和ManyToOne映射中所做的
@ManyToOne
@JoinColumn(name="Key")
@JsonBackReference
private LgcyIsp Key;
@OneToMany(mappedBy="LgcyIsp ")
@JsonManagedReference
private List<Safety> safety;
#1
#2
447
JsonIgnoreProperties [2017 Update]:
You can now use JsonIgnoreProperties to 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).
(感谢Zammel AlaaEddine指出这一点)。
JsonManagedReference and JsonBackReference
Since Jackson 1.6 you can use two annotations to solve the infinite recursion problem without ignoring the getters/setters during serialization: @JsonManagedReference
and @JsonBackReference
.
由于Jackson 1.6,您可以使用两个注释来解决无限递归问题,而不必在序列化期间忽略getters/setters: @JsonManagedReference和@JsonBackReference。
Explanation
解释
For Jackson 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 * error.
对于Jackson来说,关系的两个方面之一不应该被序列化,以避免导致*错误的infite循环。
So, Jackson 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 marshalling process. Then, Jackson 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.
因此,Jackson将引用的forward部分(您的Set
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 Jackson * issues on Keenformatics, my blog.
如果你想了解更多的信息,我在我的博客Keenformatics上写了一篇关于Json和Jackson *的文章。
EDIT:
编辑:
Another useful annotation you could check is @JsonIdentityInfo: using it, everytime Jackson 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:使用它,每当Jackson序列化对象时,它将向对象添加一个ID(或您选择的另一个属性),这样它就不会每次都“扫描”它。当您在更多相互关联的对象之间有一个链循环时(例如:Order -> OrderLine -> User -> Order和over),这将非常有用。
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.
在这种情况下,您必须非常小心,因为您可能需要多次读取您的对象的属性(例如,在产品列表中有更多的产品共享同一销售者),而这个注释阻止您这样做。我建议经常查看firebug日志,检查Json响应,看看代码中发生了什么。
Sources:
来源:
- Keenformatics - How To Solve JSON infinite recursion * (my blog)
- Keenformatics—如何解决JSON无限递归*(我的博客)
- Jackson References
- 杰克逊的引用
- Personal experience
- 个人经验
#3
66
The new annotation @JsonIgnoreProperties resolves many of the issues with the other options.
新的注释@JsonIgnoreProperties用其他选项解决了许多问题。
@Entity
public class Material{
...
@JsonIgnoreProperties("costMaterials")
private List<Supplier> costSuppliers = new ArrayList<>();
...
}
@Entity
public class Supplier{
...
@JsonIgnoreProperties("costSuppliers")
private List<Material> costMaterials = new ArrayList<>();
....
}
Check it out here. It works just like in the documentation:
http://springquay.blogspot.com/2016/01/new-approach-to-solve-json-recursive.html
检查出来。它的工作方式与文档类似:http://springquay.blogspot.com/2016/01/new-approach to solve-json-recursive.html
#4
41
Also, using Jackson 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:
此外,使用Jackson 2.0+还可以使用@JsonIdentityInfo。这对于我的hibernate类来说比@JsonBackReference和@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.
它应该工作。
#5
19
Also, Jackson 1.6 has support for handling bi-directional references... which seems like what you are looking for (this blog entry also mentions the feature)
此外,Jackson 1.6支持处理双向引用…这似乎是你在寻找的(这个博客条目也提到了这个功能)
And as of July 2011, there is also "jackson-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月开始,还有“jackson-module-hibernate”,它可能在处理Hibernate对象的某些方面有所帮助,尽管不一定是这个特定的对象(它需要注解)。
#6
11
Now Jackson supports avoiding cycles without ignoring the fields:
现在Jackson支持避免循环而不忽略字段:
Jackson - serialization of entities with birectional relationships (avoiding cycles)
Jackson -具有双向关系的实体的序列化(避免循环)
#7
7
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;
#8
6
There's now a Jackson module (for Jackson 2) specifically designed to handle Hibernate lazy initialization problems when serializing.
现在有一个专门为Jackson 2设计的Jackson模块,用于在序列化时处理Hibernate延迟初始化问题。
https://github.com/FasterXML/jackson-datatype-hibernate
https://github.com/FasterXML/jackson-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.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate4</artifactId>
<version>2.4.0</version>
</dependency>
and then register the module when intializing Jackson's ObjectMapper:
然后在初始化Jackson的ObjectMapper时注册模块:
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Hibernate4Module());
Documentation currently isn't great. See the Hibernate4Module code for available options.
目前文档不是很好。有关可用选项,请参见Hibernate4Module代码。
#9
4
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;
#10
4
For me the best solution is to use @JsonView
and create specific filters for each scenario. You could also use @JsonManagedReference
and @JsonBackReference
, however it is a hardcoded solution to only one situation, where the owner always references the owning side, and never the opposite. If you have another serialization scenario where you need to re-annotate the attribute differently, you will not be able to.
对我来说,最好的解决方案是使用@JsonView并为每个场景创建特定的过滤器。您也可以使用@JsonManagedReference和@JsonBackReference,但是它是针对只有一种情况的硬编码解决方案,在这种情况下,所有者总是引用所有者一方,而不是相反。如果您有另一个序列化场景,需要以不同的方式重新注释属性,您将无法这样做。
Problem
Lets use two classes, Company
and Employee
where you have a cyclic dependency between them:
让我们使用两个类,公司和员工,在它们之间存在循环依赖:
public class Company {
private Employee employee;
public Company(Employee employee) {
this.employee = employee;
}
public Employee getEmployee() {
return employee;
}
}
public class Employee {
private Company company;
public Company getCompany() {
return company;
}
public void setCompany(Company company) {
this.company = company;
}
}
And the test class that tries to serialize using ObjectMapper
(Spring Boot):
使用ObjectMapper (Spring Boot)进行序列化的测试类:
@SpringBootTest
@RunWith(SpringRunner.class)
@Transactional
public class CompanyTest {
@Autowired
public ObjectMapper mapper;
@Test
public void shouldSaveCompany() throws JsonProcessingException {
Employee employee = new Employee();
Company company = new Company(employee);
employee.setCompany(company);
String jsonCompany = mapper.writeValueAsString(company);
System.out.println(jsonCompany);
assertTrue(true);
}
}
If you run this code, you'll get the:
如果您运行此代码,您将得到:
org.codehaus.jackson.map.JsonMappingException: Infinite recursion (*Error)
Solution Using `@JsonView`
@JsonView
enables you to use filters and choose what fields should be included while serializing the objects. A filter is just a class reference used as a identifier. So let's first create the filters:
@JsonView使您能够使用筛选器并选择在序列化对象时应该包含哪些字段。过滤器只是作为标识符使用的类引用。让我们首先创建过滤器:
public class Filter {
public static interface EmployeeData {};
public static interface CompanyData extends EmployeeData {};
}
Remember, the filters are dummy classes, just used for specifying the fields with the @JsonView
annotation, so you can create as many as you want and need. Let's see it in action, but first we need to annotate our Company
class:
请记住,过滤器是伪类,仅用于使用@JsonView注释指定字段,因此可以创建任意数量的过滤器。让我们看看它的实际应用,但首先我们需要对我们的公司类进行注解:
public class Company {
@JsonView(Filter.CompanyData.class)
private Employee employee;
public Company(Employee employee) {
this.employee = employee;
}
public Employee getEmployee() {
return employee;
}
}
and change the Test in order for the serializer to use the View:
并更改测试,以便序列化器使用视图:
@SpringBootTest
@RunWith(SpringRunner.class)
@Transactional
public class CompanyTest {
@Autowired
public ObjectMapper mapper;
@Test
public void shouldSaveCompany() throws JsonProcessingException {
Employee employee = new Employee();
Company company = new Company(employee);
employee.setCompany(company);
ObjectWriter writter = mapper.writerWithView(Filter.CompanyData.class);
String jsonCompany = writter.writeValueAsString(company);
System.out.println(jsonCompany);
assertTrue(true);
}
}
Now if you run this code, the Infinite Recursion problem is solved, because you have explicitly said that you just want to serialize the attributes that were annotated with @JsonView(Filter.CompanyData.class)
.
现在,如果您运行这段代码,那么无限递归问题就解决了,因为您已经明确地说过,您只想序列化使用@JsonView(Filter.CompanyData.class)注释的属性。
When it reaches the back reference for company in the Employee
, it checks that it's not annotated and ignore the serialization. You also have a powerful and flexible solution to choose which data you want to send through your REST APIs.
当它到达雇员中的公司的后引用时,它检查它是否没有注释并忽略序列化。您还拥有一个功能强大且灵活的解决方案,可以选择通过REST api发送哪些数据。
With Spring you can annotate your REST Controllers methods with the desired @JsonView
filter and the serialization is applied transparently to the returning object.
使用Spring,您可以使用所需的@JsonView过滤器注释REST控制器方法,序列化透明地应用于返回的对象。
Here are the imports used in case you need to check:
以下是您需要检查的进口产品:
import static org.junit.Assert.assertTrue;
import javax.transaction.Transactional;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.annotation.JsonView;
#11
2
Be sure you use com.fasterxml.jackson everywhere. I spent much time to find it out.
一定要使用com.fasterxml。杰克逊无处不在。我花了很多时间才找到它。
<properties>
<fasterxml.jackson.version>2.9.2</fasterxml.jackson.version>
</properties>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${fasterxml.jackson.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${fasterxml.jackson.version}</version>
</dependency>
Then use @JsonManagedReference
and @JsonBackReference
.
然后使用@JsonManagedReference和@JsonBackReference。
Finally, you can serialize your model to JSON:
最后,可以将模型序列化为JSON:
import com.fasterxml.jackson.databind.ObjectMapper;
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(model);
#12
1
you can use DTO pattern create class TraineeDTO without any anotation hiberbnate and you can use jackson mapper to convert Trainee to TraineeDTO and bingo the error message disapeare :)
您可以使用DTO模式创建类TraineeDTO,而不使用任何阳极休眠,还可以使用jackson mapper将受训人员转换为TraineeDTO和bingo错误消息disapeare:)
#13
1
I also met the same problem. I used @JsonIdentityInfo
's ObjectIdGenerators.PropertyGenerator.class
generator type.
我也遇到了同样的问题。我用@JsonIdentityInfo ObjectIdGenerators.PropertyGenerator。类发电机类型。
That's my solution:
这是我的解决方案:
@Entity
@Table(name = "ta_trainee", uniqueConstraints = {@UniqueConstraint(columnNames = {"id"})})
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Trainee extends BusinessObject {
...
#14
1
You can use @JsonIgnore, but this will ignore the json data which can be accessed because of the Foreign Key relationship. Therefore if you reqiure the foreign key data (most of the time we require), then @JsonIgnore will not help you. In such situation please follow the below solution.
您可以使用@JsonIgnore,但这将忽略json数据,因为外键关系可以访问这些数据。因此,如果重新获取外键数据(大部分时间我们都需要),则@JsonIgnore不会帮助您。在这种情况下,请遵循下面的解决方案。
you are getting Infinite recursion, because of the BodyStat class again referring the Trainee object
你会得到无限递归,因为BodyStat类再次引用受训对象
BodyStat
BodyStat
@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinColumn(name="trainee_fk")
private Trainee trainee;
Trainee
实习
@OneToMany(mappedBy = "trainee", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@Column(nullable = true)
private Set<BodyStat> bodyStats;
Therefore, you have to comment/omit the above part in Trainee
因此,你必须在培训生中评论/省略上面的部分
#15
0
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或带有@ResponseBody的A发送到视图,它可能会导致一个无限循环。您可以在类中编写构造函数,并使用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 bs this 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.
但是,这个解决方案有一些限制,您可以看到,在构造函数中我没有引用清单bs,这是因为Hibernate不允许这样做,至少在3.6.10版本中是这样。最后,当我需要在视图中显示两个实体时,我将执行以下操作。
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.
此解决方案的另一个问题是,如果添加或删除属性,则必须更新构造函数和所有查询。
#16
0
In case you are using Spring Data Rest, issue can be resolved by creating Repositories for every Entity involved in cyclical references.
如果您使用的是Spring数据Rest,可以通过为涉及到循环引用的每个实体创建存储库来解决问题。
#17
0
@JsonIgnoreProperties is the answer.
@JsonIgnoreProperties就是答案。
Use something like this ::
使用如下内容:
@OneToMany(mappedBy = "course",fetch=FetchType.EAGER)
@JsonIgnoreProperties("course")
private Set<Student> students;
#18
0
Working fine for me Resolve Json Infinite Recursion problem when working with Jackson
在使用Jackson时,我很好地解决了Json无限递归问题
This is what I have done in oneToMany and ManyToOne Mapping
这是我在oneToMany和ManyToOne映射中所做的
@ManyToOne
@JoinColumn(name="Key")
@JsonBackReference
private LgcyIsp Key;
@OneToMany(mappedBy="LgcyIsp ")
@JsonManagedReference
private List<Safety> safety;