时间:2010-07-24 14:00:35

标签: java json orm spring-mvc jackson

28 个答案:

答案 0 :(得分:537)

JsonIgnoreProperties [2017年更新]:

您现在可以使用JsonIgnoreProperties禁止属性的序列化(在序列化期间),或忽略处理读取的JSON属性(在反序列化期间)。如果这不是您想要的,请继续阅读下面的内容。

(感谢As Zammel AlaaEddine指出这一点)。


JsonManagedReference和JsonBackReference

从Jackson 1.6开始,您可以使用两个注释来解决无限递归问题,而不会在序列化期间忽略getter / setter:@JsonManagedReference@JsonBackReference

<强>解释

为了让Jackson工作得很好,关系的两个方面之一不应该被序列化,以避免导致你的stackoverflow错误的infite循环。

因此,杰克逊采用了引用的前向部分(你在训练班中的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;

现在一切都应该正常运作。

如果你想了解更多信息,我写了一篇关于Json and Jackson Stackoverflow issues on Keenformatics的文章,我的博客。

编辑:

你可以检查的另一个有用的注释是@JsonIdentityInfo:使用它,每当Jackson序列化你的对象时,它会向它添加一个ID(或你选择的另一个属性),这样它就不会完全“扫描” “每次都这样。当你在更多相互关联的对象之间有一个链循环时(例如:Order - &gt; OrderLine - &gt; User - &gt; Order and over again),这可能很有用。

在这种情况下,您必须要小心,因为您可能需要多次读取对象的属性(例如,在产品列表中包含更多共享同一卖家的产品),并且此注释会阻止您执行此操作所以。我建议总是看看firebug日志来检查Json响应,看看你的代码中发生了什么。

<强>来源:

答案 1 :(得分:243)

答案 2 :(得分:83)

新注释@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

答案 3 :(得分:44)

此外,使用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 {

它应该有用。

答案 4 :(得分:19)

此外,杰克逊1.6支持handling bi-directional references ...似乎 你在寻找什么(this blog entry也提到了这个特征)

截至2011年7月,还有“jackson-module-hibernate”在处理Hibernate对象的某些方面可能有所帮助,尽管不一定是特定的(需要注释)。

答案 5 :(得分:11)

答案 6 :(得分:8)

这对我来说非常好。 在子类中添加注释@JsonIgnore,其中提到对父类的引用。

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

答案 7 :(得分:6)

现在有一个Jackson模块(用于Jackson 2)专门设计用于在序列化时处理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 code

答案 8 :(得分:5)

对我来说,最好的解决方案是使用@JsonView并为每个方案创建特定的过滤器。您也可以使用@JsonManagedReference@JsonBackReference,但它只是一种硬编码解决方案,只有一种情况,所有者总是引用拥有方,而不是相反。如果您有另一个序列化方案需要以不同方式重新标注属性,则无法进行。

问题

让我们使用两个类CompanyEmployee,它们之间存在循环依赖关系:

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

尝试使用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);
    }
}

如果您运行此代码,您将获得:

org.codehaus.jackson.map.JsonMappingException: Infinite recursion (StackOverflowError)

使用`@JsonView`

的解决方案

@JsonView使您可以使用过滤器并选择在序列化对象时应包含哪些字段。过滤器只是用作标识符的类引用。所以我们首先创建过滤器:

public class Filter {

    public static interface EmployeeData {};

    public static interface CompanyData extends EmployeeData {};

} 

请记住,过滤器是虚拟类,仅用于指定带有@JsonView注释的字段,因此您可以根据需要创建任意数量。让我们看看它的实际效果,但首先我们需要注释我们的Company类:

public class Company {

    @JsonView(Filter.CompanyData.class)
    private Employee employee;

    public Company(Employee employee) {
        this.employee = employee;
    }

    public Employee getEmployee() {
        return employee;
    }
}

并更改测试以使序列化程序使用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);
    }
}

现在,如果您运行此代码,则无限递归问题已解决,因为您已明确表示您只想序列化使用@JsonView(Filter.CompanyData.class)注释的属性。

当它到达Employee中的公司的后向引用时,它会检查它是否未注释并忽略序列化。您还可以使用功能强大且灵活的解决方案来选择要通过REST API发送的数据。

使用Spring,您可以使用所需的@JsonView过滤器注释REST控制器方法,并且序列化将透明地应用于返回的对象。

以下是您需要检查时使用的导入:

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;

答案 9 :(得分:4)

就我而言,改变之间的关系就足够了:

@OneToMany(mappedBy = "county")
private List<Town> towns;

为:

@OneToMany
private List<Town> towns;

另一种关系保持不变:

@ManyToOne
@JoinColumn(name = "county_id")
private County county;

答案 10 :(得分:4)

对我来说很好 Resolve Json Infinite Recursion problem when working with Jackson

这是我在oneToMany和ManyToOne Mapping

中所做的
@ManyToOne
@JoinColumn(name="Key")
@JsonBackReference
private LgcyIsp Key;


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

答案 11 :(得分:4)

@JsonIgnoreProperties 就是答案。

使用类似这样的东西::

{{1}}

答案 12 :(得分:4)

请务必在任何地方使用 com.fasterxml.jackson 。我花了很多时间才找到它。

<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>

然后使用@JsonManagedReference@JsonBackReference

最后,您可以将模型序列化为JSON:

import com.fasterxml.jackson.databind.ObjectMapper;

ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(model);

答案 13 :(得分:2)

您应将@JsonBackReference与@ManyToOne实体一起使用,并将@JsonManagedReference与@onetomany包含实体类一起使用。

@OneToMany(
            mappedBy = "queue_group",fetch = FetchType.LAZY,
            cascade = CascadeType.ALL
        )
    @JsonManagedReference
    private Set<Queue> queues;



@ManyToOne(cascade=CascadeType.ALL)
        @JoinColumn(name = "qid")
       // @JsonIgnore
        @JsonBackReference
        private Queue_group queue_group;

答案 14 :(得分:2)

您可以使用 @JsonIgnore ,但这会忽略因外键关系而可以访问的json数据。因此,如果您重新检查外键数据(我们需要的大部分时间),那么 @JsonIgnore 将无法帮助您。在这种情况下,请遵循以下解决方案。

您正在获得无限递归,因为 BodyStat 类再次引用实习生对象

<强> BodyStat

@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinColumn(name="trainee_fk")
private Trainee trainee;

<强>生

@OneToMany(mappedBy = "trainee", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@Column(nullable = true)
private Set<BodyStat> bodyStats;

因此,您必须在实习生

中评论/省略上述部分

答案 15 :(得分:2)

如果您使用 @JsonManagedReference@JsonBackReference@JsonIgnore 注释,它会忽略某些字段并使用 Jackson JSON 解决无限递归。

但是如果您使用 @JsonIdentityInfo 也避免了无限递归并且您可以获得所有字段值,那么我建议您使用 @JsonIdentityInfo 注释。

@JsonIdentityInfo(generator= ObjectIdGenerators.UUIDGenerator.class, property="@id")

请参阅这篇文章 https://www.toptal.com/javascript/bidirectional-relationship-in-json 以更好地了解 @JsonIdentityInfo 注释。

答案 16 :(得分:1)

出于某种原因,就我而言,它不适用于Set。我不得不将其更改为List并使用@JsonIgnore和@ ToString.Exclude使其正常工作。

用列表替换集:

//before
@OneToMany(mappedBy="client")
private Set<address> addressess;

//after
@OneToMany(mappedBy="client")
private List<address> addressess;

并添加@JsonIgnore和@ ToString.Exclude注释:

@ManyToOne
@JoinColumn(name="client_id", nullable = false)
@JsonIgnore
@ToString.Exclude
private Client client;

答案 17 :(得分:1)

非常重要::如果您使用的是LOMBOK,请确保排除集合的属性,例如Set,List等...

赞:

@EqualsAndHashCode(exclude = {"attributeOfTypeList", "attributeOfTypeSet"})

答案 18 :(得分:1)

我也遇到了同样的问题。我使用了@JsonIdentityInfo的{​​{1}}生成器类型。

这是我的解决方案:

ObjectIdGenerators.PropertyGenerator.class

答案 19 :(得分:1)

你可以使用DTO模式 在没有任何anotation hiberbnate的情况下创建类TraineeDTO,你可以使用jackson mapper将Trainee转换为TraineeDTO和bingo错误信息disapeare:)

答案 20 :(得分:0)

如果您不能忽略该属性,请尝试修改该字段的可见性。在我们的案例中,我们仍然使用旧代码提交具有该关系的实体,因此在我的案例中,这就是解决方法:

scss

答案 21 :(得分:0)

我来晚了,已经很长了。但是我也花了几个小时试图弄清楚这一点,并且想举另一个例子。

我尝试了JsonIgnore,JsonIgnoreProperties和BackReference解决方案,但奇怪的是,好像它们没有被使用。

我使用Lombok并认为它可能会干扰,因为它创建了构造函数并覆盖了toString(在stackoverflowerror堆栈中看到了toString)。

最后,这不是Lombok的错-我使用了从数据库表自动生成NetBeans的JPA实体的方式,没有多加考虑-很好,添加到生成的类中的注释之一是@XmlRootElement。一旦删除它,一切便开始工作。哦,很好。

答案 22 :(得分:0)

重点是将 @JsonIgnore 放置在setter方法中,如下所示。就我而言。

Township.java

@Access(AccessType.PROPERTY)
@OneToMany(fetch = FetchType.LAZY)
@JoinColumn(name="townshipId", nullable=false ,insertable=false, updatable=false)
public List<Village> getVillages() {
    return villages;
}

@JsonIgnore
@Access(AccessType.PROPERTY)
public void setVillages(List<Village> villages) {
    this.villages = villages;
}

Village.java

@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "townshipId", insertable=false, updatable=false)
Township township;

@Column(name = "townshipId", nullable=false)
Long townshipId;

答案 23 :(得分:0)

如果您使用的是Spring Data Rest,可以通过为周期性引用中涉及的每个实体创建存储库来解决问题。

答案 24 :(得分:0)

经过更多分析后,我遇到了同样的问题,我知道,我们也可以通过将 @JsonBackReference 保留在OneToMany批注中来获取映射实体

@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)
@JsonBackReference
private Set<BodyStat> bodyStats;

答案 25 :(得分:0)

我有这个问题,但我不想在我的实体中使用注释,所以我通过为我的类创建一个构造函数来解决,这个构造函数必须没有引用回引用这个实体的实体。我们来说这个场景吧。

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

如果您尝试使用B向视图发送课程A@ResponseBody,则可能会导致无限循环。您可以在班级中编写构造函数,并使用entityManager创建一个查询。

"select new A(id, code, name) from A"

这是带有构造函数的类。

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

}

然而,有一些关于这个解决方案的限制,正如你所看到的,在构造函数中我没有引用 List bs 这是因为Hibernate不允许它,至少在< strong>版本3.6.10.Final ,所以当我需要在视图中显示两个实体时,我会执行以下操作。

public A getAById(int id); //THE A id

public List<B> getBsByAId(int idA); //the A id.

此解决方案的另一个问题是,如果添加或删除属性,则必须更新构造函数和所有查询。

答案 26 :(得分:0)

我也遇到过同样的问题,添加jsonbackref和jsonmanagedref,请确保@override等于和hashCode方法,这肯定可以解决此问题。

答案 27 :(得分:0)

这篇文章:https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion 有完整的解释。

如果你使用老版本的 Jackson,你可以试试@jsonmanagedreference + @jsonbackreference。如果您的 Jackson 高于 2(据我所知,1.9 也不起作用),请尝试使用 @JsonIdentityInfo。