当尝试将具有双向关联的JPA对象转换为JSON时,我不断得到
org.codehaus.jackson.map.JsonMappingException: Infinite recursion (StackOverflowError)
我所发现的是this thread,其基本结论是建议避免双向关联。有人有解决这个spring bug的方法吗?
------编辑2010-07-24 16:26:22-------
代码片段:
业务对象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 ...
}
业务对象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;
}
控制器:
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;
}
}
学员DAO的JPA实施:
@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>
28条答案
按热度按时间6jygbczu1#
请确保使用**com.fasterxml。Jackson无处不在。我花了很多时间去寻找它。
然后使用
@JsonManagedReference
和@JsonBackReference
。最后,您可以将模型序列化为JSON:
2sbarzqh2#
我遇到了同样的问题,添加了jsonbackref和jsonmanagedref,请确保@override equals和hashCode方法,这肯定会解决这个问题。
nzkunb0c3#
要点是将**@JsonIgnore**放入setter方法中,如下所示。就我而言。
Township.java
Village.java
uplii1fm4#
我是一个迟到的人,这已经是一条很长的线索了。但我也花了几个小时试图弄清楚这一点,我想再举一个例子。
我尝试了JsonIgnore、JSONignoReperties和反向引用解决方案,但奇怪的是,它们好像没有被选中。
我使用了Lombok,并认为它可能会干扰,因为它会创建构造函数并覆盖toString(在StackOverflowerError堆栈中看到toString)。
最后,这不是Lombok的错-我使用了从数据库表自动生成JPA实体的NetBeans,没有考虑太多-好吧,添加到生成类中的注解之一是@XmlRootElement。一旦我把它取下来,一切都开始工作了。嗯。
vi4fp9gy5#
在使用SpringDataREST的情况下,可以通过为循环引用中涉及的每个实体创建存储库来解决这个问题。
ycl3bljg6#
我有这个问题,但我不想在我的实体中使用注解,所以我通过为我的类创建一个构造函数来解决这个问题,这个构造函数不能引用回引用这个实体的实体。让我们假设这个场景。
如果尝试向视图发送带有
@ResponseBody
的类B
或A
,则可能会导致无限循环。您可以在类中编写构造函数,并使用entityManager
创建查询,如下所示。这是具有构造函数的类。
但是,这个解决方案有一些限制,如您所见,在构造函数中,我没有引用列表bs。这是因为Hibernate不允许这样做,至少在3.6.10.Final版本中是这样,所以当我需要在视图中显示两个实体时,我会执行以下操作。
此解决方案的另一个问题是,如果添加或删除属性,则必须更新构造函数和所有查询。
zwghvu4y7#
这篇文章:https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion有一个完整的解释。
如果您使用的是旧版本的Jackson,可以尝试@jsonmanagedreference+@jsonbackreference。如果您的Jackson高于2(据我所知,1.9也不起作用),请尝试@JsonIdentityInfo。
weylhg0b8#
如果使用
@JsonManagedReference
、@JsonBackReference
或@JsonIgnore
注解,它会忽略一些字段,并使用Jackson JSON解决无限递归。但是如果您使用
@JsonIdentityInfo
,这也避免了无限递归,您可以获得所有字段值,因此我建议您使用@JsonIdentityInfo
注解。请参阅本文https://www.toptal.com/javascript/bidirectional-relationship-in-json,以更好地了解
@JsonIdentityInfo
注解。owfi6suc9#
出于某种原因,在我的情况下,它不适用于Set。我不得不将其更改为List并使用@JsonIgnore和@ToString。排除以使其工作。
用列表替换集合:
并添加@JsonIgnore和@ToString。排除注解:
lstz6jyr10#
如果无法忽略属性,请尝试修改字段的可见性。在我们的例子中,旧代码仍然提交具有关系的实体,因此在我的例子中这是修复:
enyaitl311#
您可以使用DTO模式创建类TraineeDTO,而不使用任何选项hiberbnate,您可以使用jacksonMap器将受训者转换为TraineeDTO,然后返回错误消息disapeare:)
oxf4rvwz12#
我也有同样的问题,在做了更多的分析之后,我才知道,我们也可以通过将**@JsonBackReference**保留在OneToMany注解中来获得Map实体
ukxgm1gy13#
**非常重要:*如果您使用LOMBOK,请使用舒尔排除集合属性,如集合、列表等。。。
这样地:
djp7away14#
您可以使用**@JsonIgnore**,但这将忽略由于外键关系而可以访问的json数据。因此,如果您需要外键数据(大多数情况下我们需要),那么**@JsonIgnore将无法帮助您。在这种情况下,请遵循以下解决方案。
由于BodyStat类再次引用Trainee**对象,您将得到无限递归
身体统计
受训者
因此,您必须在受训人员中注解/省略上述部分
of1yzvn415#
JSONIGNOREPORTIES〔2017年更新〕:
现在可以使用JsonIgnoreProperties抑制属性的序列化(在序列化期间),或忽略JSON属性读取的处理(在反序列化期间)。如果这不是您想要的,请继续阅读下面的内容。
(感谢As Zammel AlaaEddine指出这一点)。
JsonManagedReference和JsonBackReference
自Jackson 1.6以来,您可以使用两个注解来解决无限递归问题,而不会在序列化期间忽略getter/setter:
@JsonManagedReference
和@JsonBackReference
。解释
为了让Jackson正常工作,不应该序列化关系的两个方面之一,以避免导致stackoverflow错误的内联循环。
因此,Jackson获取引用的前向部分(学员类中的
Set<BodyStat> bodyStats
),并将其转换为类似json的存储格式;这就是所谓的编组过程。然后,Jackson查找引用的后面部分(即BodyStat类中的Trainee trainee
),并保持原样,而不是序列化它。这部分关系将在前向引用的反序列化(解组)过程中重新构建。您可以这样更改代码(我跳过了无用的部分):
业务对象1:
业务对象2:
现在一切都应该正常工作了。
如果您想了解更多信息,我在我的博客上写了一篇关于Json and Jackson Stackoverflow issues on Keenformatics的文章。
编辑:
您可以检查的另一个有用的注解是@JsonIdentityInfo:使用它,每次Jackson序列化对象时,它都会向其添加一个ID(或您选择的其他属性),这样它就不会每次都完全“扫描”它。当您在多个相互关联的对象之间有一个链循环时,这可能很有用(例如:订单->订单行->用户->订单,然后重复)。
在这种情况下,您必须小心,因为您可能需要多次读取对象的属性(例如,在产品列表中,多个产品共享同一卖家),并且此注解阻止您这样做。我建议始终查看firebug日志,以检查Json响应,并查看代码中发生了什么。
资料来源: