java无限递归与jackson json和hibernate jpa问题

jhdbpxl9  于 2021-07-13  发布在  Java
关注(0)|答案(23)|浏览(587)

当尝试将具有双向关联的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<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;
    }    
}

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();
    }
}

持久性.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>
jyztefdp

jyztefdp16#

现在有一个jackson模块(针对jackson2)专门设计用来处理序列化时的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代码。

r1zk6ea1

r1zk6ea117#

对我来说工作很好解决json无限递归问题时与Jackson的工作
这就是我在一个多域和多个Map中所做的

@ManyToOne
@JoinColumn(name="Key")
@JsonBackReference
private LgcyIsp Key;

@OneToMany(mappedBy="LgcyIsp ")
@JsonManagedReference
private List<Safety> safety;
s6fujrry

s6fujrry18#

这对我来说非常好。在提到父类引用的子类上添加注解@jsonignore。

@ManyToOne
@JoinColumn(name = "ID", nullable = false, updatable = false)
@JsonIgnore
private Member member;
hrysbysz

hrysbysz19#

现在jackson支持避免循环而不忽略字段:
jackson-具有双方向关系的实体序列化(避免循环)

wvmv3b1j

wvmv3b1j20#

另外,jackson1.6支持处理双向引用。。。这似乎是你正在寻找的(这篇博客文章也提到了这个功能)
截至2011年7月,还有“jackson module hibernate”,它可能在处理hibernate对象的某些方面有所帮助,尽管不一定是这个特定的模块(它确实需要注解)。

62o28rlo

62o28rlo21#

另外,使用jackson2.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 {

它应该有用。

yk9xbfzb

yk9xbfzb22#

新的注解@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<>();
   ....
}

看看这里。它的工作原理与文档中的一样:
http://springquay.blogspot.com/2016/01/new-approach-to-solve-json-recursive.html

s2j5cfk0

s2j5cfk023#

jsonignoreproperties[2017年更新]:

现在可以使用jsonignoreproperties抑制属性的序列化(在序列化过程中),或者忽略json属性读取的处理(在反序列化过程中)。如果这不是你想要的,请继续阅读下面的内容。
(感谢as zammel alaaeddine指出这一点)。

jsonmanagedreference和jsonbackreference

由于jackson 1.6,您可以使用两个注解来解决无限递归问题,而不必在序列化期间忽略getter/setter: @JsonManagedReference 以及 @JsonBackReference .
解释
为了让jackson正常工作,不应该序列化关系的两个方面之一,以避免导致stackoverflow错误的infite循环。
因此,jackson将引用的前面部分(您的 Set<BodyStat> bodyStats ,并将其转换为类似json的存储格式;这就是所谓的编组过程。然后,jackson查找引用的后面部分(即。 Trainee trainee 在bodystat类中),并保持原样,而不是序列化它。这部分关系将在前向引用的反序列化(解组)过程中重新构建。
您可以这样更改代码(我跳过无用的部分):
业务对象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和over)之间有一个链循环时,这会很有用。
在这种情况下,您必须小心,因为您可能需要多次读取对象的属性(例如,在具有多个共享同一卖家的产品的产品列表中),并且此注解阻止您这样做。我建议经常查看firebug日志来检查json响应,看看代码中发生了什么。
资料来源:
如何解决json无限递归堆栈溢出(我的博客)
Jackson推荐信
个人经历

相关问题