การเรียกซ้ำแบบไม่สิ้นสุดกับปัญหา Jackson JSON และ Hibernate JPA


412

เมื่อพยายามแปลงวัตถุ JPA ที่มีความสัมพันธ์แบบสองทิศทางเป็น JSON ฉันจะได้รับต่อไป

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

ทั้งหมดที่ฉันพบคือหัวข้อนี้ซึ่งสรุปโดยทั่วไปพร้อมกับการแนะนำเพื่อหลีกเลี่ยงการเชื่อมโยงแบบสองทิศทาง ไม่มีใครมีความคิดในการแก้ปัญหาสำหรับข้อผิดพลาดในฤดูใบไม้ผลินี้หรือไม่?

------ แก้ไข 2010-07-24 16:26:22 -------

Codesnippets:

วัตถุธุรกิจ 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();
    }
}

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>

เพิ่มไป@Transient Trainee.bodyStats
phil294

ในปี 2560 @JsonIgnorePropertiesเป็นทางออกที่สะอาดที่สุด ตรวจสอบคำตอบของ Zammel AlaaEddineสำหรับรายละเอียดเพิ่มเติม
Utku

ความผิดของสปริงนี้เป็นอย่างไร?
Nathan Hughes

คำตอบ:


293

คุณอาจใช้@JsonIgnoreเพื่อทำลายวงจร


1
@Ben: จริง ๆ แล้วฉันไม่รู้ บางทีการสนับสนุนไม่ได้เปิดใช้งาน: wiki.fasterxml.com/JacksonJAXBA คำอธิบาย
axtavt

40
ตั้งแต่ Jackson 1.6 มีวิธีแก้ปัญหาที่ดีกว่า: คุณสามารถใช้คำอธิบายประกอบใหม่สองรายการเพื่อแก้ปัญหาการเรียกซ้ำแบบไม่มีที่สิ้นสุดโดยไม่สนใจตัวรับ / setters ระหว่างการทำให้เป็นอนุกรม ดูคำตอบของฉันด้านล่างสำหรับรายละเอียด
Kurt Bourbaki

1
@axtavt ขอบคุณสำหรับคำตอบที่สมบูรณ์แบบ Btw ฉันมาพร้อมกับโซลูชันอื่น: คุณสามารถหลีกเลี่ยงการสร้างทะเยอทะยานสำหรับค่านี้ดังนั้นสปริงจะไม่สามารถเข้าถึงได้ในขณะที่สร้าง JSON (แต่ฉันไม่คิดว่ามันเหมาะกับทุกกรณีดังนั้นคำตอบของคุณจะดีกว่า)
Semyon Danilov

11
โซลูชันทั้งหมดข้างต้นดูเหมือนจะต้องเปลี่ยนวัตถุโดเมนโดยการเพิ่มคำอธิบายประกอบ ถ้าฉันเป็นอันดับคลาสบุคคลที่สามซึ่งฉันไม่มีวิธีแก้ไข ฉันจะหลีกเลี่ยงปัญหานี้ได้อย่างไร
Jianwu Chen

2
วิธีนี้ไม่สามารถใช้งานได้ในบางสถานการณ์ ในฐานข้อมูลเชิงสัมพันธ์กับ jpa ถ้าคุณให้@JsonIgnoreคุณจะโมฆะใน "foreign key" เมื่อคุณอัปเดตเอนทิตี้ ...
slim

629

JsonIgnoreProperties [อัพเดท 2017]:

ตอนนี้คุณสามารถใช้JsonIgnorePropertiesเพื่ออนุกรมปราบของคุณสมบัติ (ระหว่างอนุกรม) หรือละเว้นการประมวลผลของ JSON คุณสมบัติอ่าน (ระหว่าง deserialization) หากนี่ไม่ใช่สิ่งที่คุณต้องการโปรดอ่านด้านล่าง

(ขอบคุณ As Zammel AlaaEddine สำหรับการชี้เรื่องนี้)


JsonManagedReference และ JsonBackReference

ตั้งแต่แจ็คสัน 1.6 คุณสามารถใช้สองคำอธิบายประกอบในการแก้ปัญหาการเรียกซ้ำอนันต์โดยไม่ละเลย getters / setters ระหว่างอนุกรม: และ@JsonManagedReference@JsonBackReference

คำอธิบาย

เพื่อให้แจ็กสันทำงานได้ดีความสัมพันธ์ด้านใดด้านหนึ่งของทั้งสองไม่ควรจัดลำดับเพื่อหลีกเลี่ยงการวนซ้ำ infite ที่ทำให้เกิดข้อผิดพลาดการซ้อนทับของคุณ

ดังนั้นแจ็กสันใช้ส่วนหน้าของการอ้างอิง ( Set<BodyStat> bodyStatsในคลาส Trainee ของคุณ) และแปลงเป็นรูปแบบการจัดเก็บข้อมูลแบบ json นี่คือสิ่งที่เรียกว่าจอมพลกระบวนการ จากนั้นแจ็คสันมองหาส่วนหลังของการอ้างอิง (เช่น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;

ตอนนี้ทุกอย่างควรทำงานอย่างถูกต้อง

หากคุณต้องการข้อมูลเพิ่มเติมฉันเขียนบทความเกี่ยวกับปัญหาJson และ Jackson Stackoverflow เกี่ยวกับ Keenformaticsบล็อกของฉัน

แก้ไข:

คำอธิบายประกอบที่เป็นประโยชน์อื่นที่คุณสามารถตรวจสอบได้คือ@JsonIdentityInfo : เมื่อใช้งานทุกครั้ง Jackson จะทำให้วัตถุของคุณเป็นอนุกรมแล้วจะเพิ่ม ID (หรือคุณลักษณะอื่น ๆ ที่คุณเลือก) ให้กับมันเพื่อไม่ให้ "สแกน" อีกครั้ง สิ่งนี้จะมีประโยชน์เมื่อคุณมีห่วงโซ่ระหว่างวัตถุที่สัมพันธ์กันมากขึ้น (เช่น: สั่งซื้อ -> สั่งซื้อ -> ผู้ใช้ -> สั่งซื้อและอีกครั้ง)

ในกรณีนี้คุณต้องระวังเนื่องจากคุณอาจจำเป็นต้องอ่านคุณสมบัติของวัตถุมากกว่าหนึ่งครั้ง (ตัวอย่างเช่นในรายการผลิตภัณฑ์ที่มีผลิตภัณฑ์เพิ่มเติมที่มีผู้ขายรายเดียวกัน) และหมายเหตุประกอบนี้ทำให้คุณไม่สามารถทำได้ ฉันแนะนำให้ดูบันทึก firebug เสมอเพื่อตรวจสอบการตอบสนองของ Json และดูว่าเกิดอะไรขึ้นในรหัสของคุณ

แหล่งที่มา:


29
ขอบคุณสำหรับคำตอบที่ชัดเจน นี่เป็นวิธีที่สะดวกกว่าการ@JsonIgnoreอ้างอิงย้อนกลับ
Utku Özdemir

3
นี่เป็นวิธีที่ถูกต้องอย่างแน่นอน ถ้าคุณทำเช่นนี้ทางฝั่งเซิร์ฟเวอร์เพราะคุณใช้ Jackson ที่นั่นมันไม่สำคัญว่า json mapper ที่คุณใช้ในฝั่งไคลเอ็นต์และคุณไม่ต้องตั้งค่าลูกให้เป็นคู่มือการเชื่อมโยงผู้ปกครอง มันใช้งานได้ ขอบคุณ Kurt
flosk8

1
นีซ, @JsonIgnoreคำอธิบายรายละเอียดและแน่นอนที่ดีขึ้นและอธิบายเพิ่มเติมวิธีกว่า
Piotr Nowicki

2
ขอบคุณ! @JsonIdentityInfo ทำงานสำหรับการอ้างอิงตามวัฏจักรที่เกี่ยวข้องกับหลายเอนทิตีในหลายลูปที่ทับซ้อนกัน
n00b

1
ฉันไม่สามารถทำสิ่งนี้เพื่อชีวิตของฉัน ฉันคิดว่าฉันมีการตั้งค่าที่คล้ายกัน แต่เห็นได้ชัดว่าฉันมีบางอย่างผิดปกติเนื่องจากฉันไม่สามารถรับอะไรได้นอกจากข้อผิดพลาดในการเรียกซ้ำที่ไม่สิ้นสุด:
swv

103

คำอธิบายประกอบใหม่ @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


@tero - ด้วยวิธีนี้เราไม่ได้รับข้อมูลที่เกี่ยวข้องกับเอนทิตี
Pra_A

@PAA HEY PAA ฉันคิดว่ามีความเกี่ยวข้องกับกิจการ! ทำไมคุณพูดเช่นนั้น ?
tero17

1
@ tero17 คุณจัดการการเรียกซ้ำแบบไม่สิ้นสุดได้อย่างไรเมื่อคุณมีคลาสมากกว่า 2 คลาส ตัวอย่างเช่น: Class A -> Class B -> Class C -> Class A ฉันลองกับ JsonIgnoreProperties โดยไม่ต้องโชค
Villat

@Villat นี่เป็นอีกปัญหาที่แก้ได้ฉันขอแนะนำให้เปิดความต้องการใหม่สำหรับสิ่งนั้น
tero17

+1 สำหรับตัวอย่างโค้ดเนื่องจากแจ็กสันเป็นมือใหม่การใช้ @JsonIgnoreProperties ไม่ชัดเจนนักโดยการอ่าน JavaDoc
Wecherowski

47

นอกจากนี้การใช้แจ็คสัน 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 {

และมันควรจะทำงาน


คุณกรุณาอธิบายว่า "สิ่งนี้ทำงานได้ดีขึ้นมาก" มีปัญหากับการอ้างอิงที่มีการจัดการหรือไม่
Utku Özdemir

@ UtkuÖzdemirฉันเพิ่มรายละเอียดเกี่ยวกับ@JsonIdentityInfoคำตอบของฉันด้านบน
Kurt Bourbaki

1
นี่เป็นทางออกที่ดีที่สุดที่เราพบเพราะเมื่อเราใช้ "@JsonManagedReference" วิธีการรับคืนค่าที่ประสบความสำเร็จโดยไม่มีข้อผิดพลาด stackoverflow แต่เมื่อเราพยายามที่จะบันทึกข้อมูลโดยใช้โพสต์มันกลับข้อผิดพลาดของ 415 (ข้อผิดพลาดของสื่อที่ไม่สนับสนุน)
cuser

1
ฉันได้เพิ่ม@JsonIdentityInfoคำอธิบายประกอบลงในเอนทิตีของฉันแล้ว แต่ก็ไม่ได้แก้ปัญหาการเรียกซ้ำ เท่านั้น@JsonBackReferenceและ@JsonManagedReferenceแก้ไขได้ แต่จะลบคุณสมบัติที่แมปออกจาก JSON
Oleg Abrazhaev

19

นอกจากนี้ Jackson 1.6 ยังรองรับการจัดการอ้างอิงสองทิศทาง ... ซึ่งดูเหมือนว่าคุณกำลังมองหา ( รายการบล็อกนี้ยังกล่าวถึงคุณสมบัติ)

และเมื่อเดือนกรกฎาคม 2011 ยังมี " jackson-module-hibernate " ซึ่งอาจช่วยในบางแง่มุมของการจัดการกับวัตถุ Hibernate แม้ว่าจะไม่จำเป็นต้องมีสิ่งนี้ (ซึ่งต้องมีคำอธิบายประกอบ)


ลิงค์ตายแล้วคุณคิดจะอัพเดตหรือแก้ไขคำตอบของคุณ
GetMeARemoteJob


9

มันใช้งานได้ดีอย่างสมบูรณ์แบบสำหรับฉัน เพิ่มคำอธิบายประกอบ @JsonIgnore บนคลาสลูกที่คุณพูดถึงการอ้างอิงถึงระดับผู้ปกครอง

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

2
ฉันคิดว่า@JsonIgnoreไม่สนใจคุณลักษณะนี้จากการถูกดึงไปยังฝั่งไคลเอ็นต์ ถ้าฉันต้องการคุณลักษณะนี้กับลูกของมัน (ถ้ามีลูก)?
Khasan 24-7

6

ตอนนี้มีโมดูลแจ็คสัน (สำหรับแจ็คสัน 2) ออกแบบมาเป็นพิเศษเพื่อจัดการกับปัญหาการเริ่มต้นขี้เกียจของไฮเบอร์เนตเมื่อทำการซีเรียลไลซ์

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>

จากนั้นลงทะเบียนโมดูลเมื่อทำการจับคู่ ObjectMapper ของแจ็คสัน:

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Hibernate4Module());

เอกสารในปัจจุบันยังไม่ดี ดูรหัส Hibernate4Moduleสำหรับตัวเลือกที่มี


ปัญหาอะไรคือการแก้แล้วเพราะมันดูน่าสนใจ ฉันมีปัญหาเช่นเดียวกับ OP และเทคนิคทั้งหมดรวมถึงข้างต้นไม่ได้ทำงาน
user1567291

6

ทำงานได้ดีสำหรับฉัน แก้ไขปัญหาการเรียกซ้ำ Json Infinite เมื่อทำงานกับ Jackson

นี่คือสิ่งที่ฉันได้ทำใน oneToMany และ ManyToOne Mapping

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


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

ฉันได้ใช้การไฮเบอร์เนตการทำแผนที่ในแอปพลิเคชันการบูทสปริง
Prabu M

สวัสดีผู้เขียนขอขอบคุณสำหรับบทเรียนที่ดีและโพสต์ที่ยอดเยี่ยม แต่ฉันพบว่า@JsonManagedReference, @JsonBackReferenceไม่ได้ช่วยให้คุณมีข้อมูลที่เกี่ยวข้องกับ@OneToManyและ@ManyToOneสถานการณ์ยังเมื่อใช้@JsonIgnorePropertiesไม่ข้ามข้อมูลนิติบุคคลที่เกี่ยวข้อง วิธีแก้ปัญหานี้
Pra_A

5

สำหรับฉันทางออกที่ดีที่สุดคือการใช้@JsonViewและสร้างตัวกรองเฉพาะสำหรับแต่ละสถานการณ์ คุณสามารถใช้@JsonManagedReferenceและ@JsonBackReferenceอย่างไรก็ตามมันเป็นโซลูชันฮาร์ดโค้ดที่มีเพียงสถานการณ์เดียวเท่านั้นที่เจ้าของอ้างถึงด้านที่เป็นเจ้าของเสมอและไม่เคยตรงกันข้าม หากคุณมีสถานการณ์การทำให้เป็นอนุกรมอื่นซึ่งคุณจำเป็นต้องใส่คำอธิบายประกอบให้กับแอตทริบิวต์อีกครั้งคุณจะไม่สามารถทำได้

ปัญหา

ให้ใช้สองคลาสCompanyและEmployeeที่ที่คุณมีวงจรอ้างอิงระหว่างกัน:

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

และเปลี่ยนการทดสอบตามลำดับเพื่อให้ serializer ใช้มุมมอง:

@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 คุณสามารถใส่หมายเหตุวิธี REST Controllers ด้วยวิธีที่ต้องการ @JsonViewตัวกรอง

นี่คือการนำเข้าที่ใช้ในกรณีที่คุณต้องการตรวจสอบ:

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;

1
นี่เป็นบทความที่ดีที่อธิบายวิธีแก้ปัญหาทางเลือกมากมายเพื่อแก้ปัญหาการเรียกซ้ำ: baeldung.com/…
Hugo Baés

5

@JsonIgnorePropertiesคือคำตอบ

ใช้สิ่งนี้ ::

@OneToMany(mappedBy = "course",fetch=FetchType.EAGER)
@JsonIgnoreProperties("course")
private Set<Student> students;

ใช้สิ่งนี้อย่างมั่นใจตามที่ฉันเห็น Jhipster ใช้สิ่งนี้ในโค้ดที่สร้างขึ้น
ifelse.codes

ขอบคุณสำหรับคำตอบ. แต่ฉันพบว่า@JsonManagedReference, @JsonBackReferenceไม่ได้ช่วยให้คุณมีข้อมูลที่เกี่ยวข้องกับ@OneToManyและ@ManyToOneสถานการณ์ยังเมื่อใช้@JsonIgnorePropertiesไม่ข้ามข้อมูลนิติบุคคลที่เกี่ยวข้อง วิธีแก้ปัญหานี้
Pra_A

4

ในกรณีของฉันมันก็เพียงพอที่จะเปลี่ยนความสัมพันธ์จาก:

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

ถึง:

@OneToMany
private List<Town> towns;

ความสัมพันธ์อื่นยังคงเหมือนเดิม:

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

2
ฉันคิดว่ามันดีกว่าที่จะใช้โซลูชันของ Kurt เนื่องจากโซลูชัน JoinColumn สามารถสิ้นสุดในเนื้อความของข้อมูลที่ไม่มีการอ้างอิง
flosk8

1
นี่เป็นสิ่งเดียวที่ช่วยฉันได้ ไม่มีวิธีการแก้ปัญหาอื่น ๆ จากการทำงานด้านบน ฉันยังคงไม่แน่ใจว่าทำไม ...
Deniss M.

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

4

คุณสามารถใช้@JsonIgnoreได้ แต่สิ่งนี้จะไม่สนใจข้อมูล json ที่สามารถเข้าถึงได้เนื่องจากความสัมพันธ์กับ Foreign Key ดังนั้นหากคุณทำการปิดข้อมูลคีย์ต่างประเทศ (ใช้เวลาส่วนใหญ่) @JsonIgnoreจะไม่ช่วยคุณ ในสถานการณ์เช่นนี้โปรดปฏิบัติตามวิธีการแก้ปัญหาด้านล่าง

คุณได้รับการเรียกซ้ำแบบไม่สิ้นสุดเนื่องจากคลาสBodyStatอ้างถึงวัตถุTraineeอีกครั้ง

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;

ดังนั้นคุณต้องแสดงความคิดเห็น / ละเว้นส่วนด้านบนใน Trainee


1
ในกรณีของฉันมันไม่ทำงาน คุณช่วยดูหน่อยได้ไหม: github.com/JavaHelper/issue-jackson-boot ?
Pra_A

3

ฉันก็เจอปัญหาเดียวกัน ผมใช้@JsonIdentityInfoเป็นObjectIdGenerators.PropertyGenerator.classประเภทเครื่องกำเนิดไฟฟ้า

นั่นคือทางออกของฉัน:

@Entity
@Table(name = "ta_trainee", uniqueConstraints = {@UniqueConstraint(columnNames = {"id"})})
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Trainee extends BusinessObject {
...

2
คำตอบที่ดี
LAGRIDA

1
ฉันจะตอบในสิ่งเดียวกัน !!! Nice;)
Felipe Desiderati

3

คุณควรใช้ @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;

หากฉันใส่คำอธิบายประกอบ @ jsonIgnore ไว้ในเด็ก ฉันไม่สามารถรับวัตถุแม่จากเด็กเมื่อฉันพยายามที่จะพาเด็ก เหตุใดวัตถุหลักจึงไม่มาจึงถูกละเว้นโดย @ jsonignore บอกวิธีที่จะได้รับจากเด็กไปผู้ปกครองและผู้ปกครองเด็ก
Kumaresan Perumal

ไม่จำเป็นต้องใช้ @JsonIgnore เพียงใช้คำอธิบายประกอบด้านบนและเพื่อรับ objets ของผู้ปกครองและเด็กโดยใช้ Getters และ setters และ Jsonignore ก็ทำเช่นเดียวกัน แต่มันจะสร้างการเรียกซ้ำแบบไม่สิ้นสุด หากคุณแบ่งปันรหัสของคุณฉันสามารถตรวจสอบสาเหตุที่คุณไม่ได้รับวัตถุ เพราะสำหรับฉันทั้งคู่กำลังมา
Shubham

ฉันหมายถึงว่า. เมื่อพาผู้ปกครอง ผู้ปกครองควรมาพร้อมกับวัตถุลูก เมื่อนำวัตถุลูก เด็กควรมาพร้อมกับผู้ปกครอง มันไม่ทำงานในสถานการณ์นี้ คุณจะกรุณาช่วยฉันหน่อยได้ไหม?
Kumaresan Perumal

1

คุณสามารถใช้รูปแบบ DTO สร้างคลาส TraineeDTO โดยไม่ต้องใช้คำอธิบายประกอบ hiberbnate และคุณสามารถใช้ jackson mapper ในการแปลง Trainee เป็น TraineeDTO และ bingo ข้อความแสดงข้อผิดพลาด disapeare :)


1

หากคุณไม่สามารถเพิกเฉยต่อคุณสมบัตินี้ได้ให้ลองปรับเปลี่ยนการมองเห็นของฟิลด์ ในกรณีของเราเรามีรหัสเก่าที่ยังคงส่งเอนทิตีที่มีความสัมพันธ์ดังนั้นในกรณีของฉันนี่คือการแก้ไข:

    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    private Trainee trainee;

หากฉันใส่คำอธิบายประกอบ @ jsonIgnore ไว้ในเด็ก ฉันไม่สามารถรับวัตถุแม่จากเด็กเมื่อฉันพยายามที่จะพาเด็ก เหตุใดวัตถุหลักจึงไม่มาจึงถูกละเว้นโดย @ jsonignore บอกวิธีที่จะได้รับจากเด็กไปผู้ปกครองและผู้ปกครองเด็ก
Kumaresan Perumal

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

}

อย่างไรก็ตามมีข้อ จำกัด บางอย่างเกี่ยวกับวิธีแก้ปัญหานี้อย่างที่คุณเห็นใน Constructor ฉันไม่ได้ทำการอ้างอิงไปยังList bsนี่เป็นเพราะ Hibernate ไม่อนุญาตอย่างน้อยในรุ่น 3.6.10 สุดท้ายดังนั้นเมื่อฉันต้องการ เพื่อแสดงเอนทิตีทั้งสองในมุมมองฉันทำดังต่อไปนี้

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

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

ปัญหาอื่น ๆ ของการแก้ปัญหานี้คือถ้าคุณเพิ่มหรือลบคุณสมบัติคุณต้องอัพเดต Constructor และแบบสอบถามทั้งหมดของคุณ


0

ในกรณีที่คุณใช้ Spring Data Rest ปัญหาสามารถแก้ไขได้ด้วยการสร้าง Repositories สำหรับทุก Entity ที่เกี่ยวข้องในการอ้างอิงตามวัฏจักร


0

ฉันเป็นผู้มาสายและมันก็เป็นเธรดที่ยาวมากแล้ว แต่ฉันใช้เวลาสองสามชั่วโมงในการพยายามคิดออกเช่นนี้และต้องการให้กรณีของฉันเป็นอีกตัวอย่างหนึ่ง

ฉันลองทั้ง JsonIgnore, JsonIgnoreProperties และ BackReference แต่แปลกพอมันเหมือนว่าพวกเขาไม่ได้หยิบขึ้นมา

ฉันใช้ลอมบอกและคิดว่ามันอาจรบกวนเนื่องจากมันสร้างตัวสร้างและแทนที่ toString (เห็น toString ใน stackoverflow stackor stack)

ในที่สุดมันก็ไม่ใช่ความผิดของลอมบอก - ฉันใช้ NetBeans เอนทิตี JPA อัตโนมัติจากตารางฐานข้อมูลโดยไม่คิดอะไรมาก - ดีและหนึ่งในคำอธิบายประกอบที่เพิ่มไปยังคลาสที่สร้างคือ @XmlRootElement เมื่อฉันลบมันทุกอย่างเริ่มทำงาน โอ้ดี


0

จุดคือการวาง@JsonIgnoreในวิธีการตั้งค่าดังต่อไปนี้ ในกรณีของฉัน

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;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.