当尝试将具备双向关联的JPA对象转换为JSON时,我不断 html
org.codehaus.jackson.map.JsonMappingException: Infinite recursion (StackOverflowError)
我所发现的只是该线程 ,基本上以建议避免双向关联为结尾。 有谁知道这个春季错误的解决方法? java
------编辑2010-07-24 16:26:22 ------- git
代码段: github
业务对象1: web
@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: spring
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;
控制器: json
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实施学员DAO: app
@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 fetch
<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>
如今,杰克逊支持在不忽略字段的状况下避免循环: ui
如今,您可使用JsonIgnoreProperties 抑制属性的序列化(在序列化期间),或者忽略对JSON属性读取的处理(在反序列化期间) 。 若是这不是您想要的,请继续阅读如下内容。
(感谢As Zammel AlaaEddine指出了这一点)。
从Jackson 1.6开始,您可使用两个批注来解决无限递归问题,而没必要在序列化过程当中忽略getter / setter: @JsonManagedReference
和@JsonBackReference
。
说明
为了使Jackson正常工做,不该将关系的两个方面之一进行序列化,以免引发您stackoverflow错误的infite循环。
所以,Jackson接受了引用的前一部分(Trainee类中的Set<BodyStat> bodyStats
),并将其转换为相似json的存储格式; 这就是所谓的编组过程。 而后,Jackson寻找参考的后半部分(即BodyStat类中的Trainee trainee
),并保持原样,而不对其进行序列化。 关系的这一部分将在前向引用的反序列化( 反编组 )期间从新构建。
您能够这样更改代码(我跳过了无用的部分):
业务对象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;
业务对象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(或您选择的另外一个属性),这样就不会每次都彻底“扫描”它。 当您在更多相互关联的对象之间造成链循环时(例如:Order-> OrderLine-> User-> Order and over over),这颇有用。
在这种状况下,您必需要当心,由于您可能须要屡次读取对象的属性(例如,在一个产品列表中有多个共享同一卖方的产品),而且此注释阻止您这样作。 我建议始终查看Firebug日志,以检查Json响应,并查看代码中发生了什么。
资料来源:
另外,使用Jackson 2.0+,您可使用@JsonIdentityInfo
。 对于个人休眠类,这比@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 {
它应该工做。
就我而言,将关系从如下位置更改就足够了:
@OneToMany(mappedBy = "county") private List<Town> towns;
至:
@OneToMany private List<Town> towns;
另外一个关系保持不变:
@ManyToOne @JoinColumn(name = "county_id") private County county;
如今有一个专为Jackson 2设计的Jackson模块,用于处理序列化时的Hibernate延迟初始化问题。
https://github.com/FasterXML/jackson-datatype-hibernate
只需添加依赖项(请注意,Hibernate 3和Hibernate 4有不一样的依赖项):
<dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-hibernate4</artifactId> <version>2.4.0</version> </dependency>
而后在初始化Jackson的ObjectMapper时注册该模块:
ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new Hibernate4Module());
文档目前不是很好。 请参阅Hibernate4Module代码以获取可用选项。