无限递归的问题:Jackson JSON和Hibernate JPA的问题。
无限递归的问题:Jackson JSON和Hibernate JPA的问题。
当尝试将具有双向关联的JPA对象转换为JSON时,我一直收到:
org.codehaus.jackson.map.JsonMappingException: Infinite recursion (StackOverflowError)
我找到的仅有的东西是这个线程,基本上得出了建议,以避免双向关联。有人有解决这个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 bodyStats; @OneToMany(mappedBy = "trainee", fetch = FetchType.EAGER, cascade = CascadeType.ALL) @Column(nullable = true) private Set trainings; @OneToMany(mappedBy = "trainee", fetch = FetchType.EAGER, cascade = CascadeType.ALL) @Column(nullable = true) private Set 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
false
您可以使用@JsonIgnore
打破循环(参考)。
您需要导入org.codehaus.jackson.annotate.JsonIgnore
(旧版本)或com.fasterxml.jackson.annotation.JsonIgnore
(当前版本)。
JsonIgnoreProperties [2017更新]:
现在可以使用JsonIgnoreProperties来抑制属性的序列化(在序列化期间),或忽略JSON属性的处理(在反序列化期间)。如果这不是你要寻找的,那么请继续往下阅读。
(感谢As Zammel AlaaEddine指出)。
JsonManagedReference和JsonBackReference
从Jackson 1.6开始,可以使用两个注释来解决无限递归问题,而不会在序列化过程中忽略getter / setter: @JsonManagedReference
和@JsonBackReference
。
说明
为了使Jackson正常工作,关系的两个方面应该有一个不序列化,从而避免因无限循环而导致堆栈溢出错误。
因此,Jackson将引用的前置部分(在Trainee类中的 Set
)转换成json格式存储;这是所谓的marshalling过程。然后,Jackson查找引用的后部分(即BodyStat类中的Trainee trainee
),并将其保持不变,不进行序列化。在向前引用的反序列化(unmarshalling)期间,此关系的这一部分将被重新构建。
你可以像这样更改你的代码(跳过无用的部分):
业务对象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 SetbodyStats;
业务对象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;
现在应该都可以正常工作了。
如果你想要更多的信息,我在我的博客Keenformatics上写了一篇关于Json和Jackson Stackoverflow问题的文章。
编辑:
你可以检查另一个有用的注解@JsonIdentityInfo:使用它,每次Jackson序列化你的对象时,它将为其添加一个ID(或你选择的另一个属性),这样它就不会每次完全“扫描”它。当你有多个相互关联的对象之间的循环链时(例如:订单->订单行->用户->订单等等),这非常有用。
在这种情况下,你必须小心,因为你可能需要多次读取你的对象的属性(例如在具有共享同一卖家的多个产品的产品列表中),而这个注解会阻止你这样做。我建议始终查看firebug日志以检查Json响应,以了解你的代码中发生了什么。
来源:
- Keenformatics - 如何解决JSON无限递归Stackoverflow(我的博客)
- 个人经验