Rekursi Tak Terbatas dengan masalah Jackson JSON dan Hibernate JPA

412

Ketika mencoba untuk mengkonversi objek JPA yang memiliki asosiasi dua arah menjadi JSON, saya terus mendapatkannya

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

Yang saya temukan hanyalah utas ini yang pada dasarnya diakhiri dengan merekomendasikan untuk menghindari asosiasi dua arah. Apakah ada yang punya ide untuk solusi untuk bug musim semi ini?

------ EDIT 2010-07-24 16:26:22 -------

Codesnippets:

Objek Bisnis 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 ...

Objek Bisnis 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;

Pengendali:

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-implementasi DAO peserta pelatihan:

@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>
Ta Sas
sumber
Tambahkan @Transientke Trainee.bodyStats.
phil294
Pada 2017, @JsonIgnorePropertiesadalah solusi terbersih. Lihat jawaban Zammel AlaaEddine untuk lebih jelasnya.
Utku
Bagaimana kesalahan pegas ini ??
Nathan Hughes

Jawaban:

294

Anda dapat menggunakan @JsonIgnoreuntuk memutus siklus.

axtavt
sumber
1
@ Ben: Sebenarnya saya tidak tahu. Mungkin dukungannya tidak diaktifkan: wiki.fasterxml.com/JacksonJAXBAnnotations
axtavt
40
Karena Jackson 1.6 ada solusi yang lebih baik: Anda dapat menggunakan dua anotasi baru untuk menyelesaikan masalah rekursi tak terbatas tanpa mengabaikan getter / setter selama serialisasi. Lihat jawaban saya di bawah untuk detailnya.
Kurt Bourbaki
1
@axtavt Terima kasih atas jawaban sempurna. Btw, saya datang dengan solusi lain: Anda bisa menghindari membuat getter untuk nilai ini, sehingga Spring tidak akan dapat mengaksesnya saat membuat JSON (tapi saya pikir itu tidak cocok untuk setiap kasus, jadi jawaban Anda lebih baik)
Semyon Danilov
11
Semua solusi di atas tampaknya perlu mengubah objek domain dengan menambahkan anotasi. Jika saya membuat serialisasi kelas pihak ketiga yang tidak dapat saya modifikasi. Bagaimana saya bisa menghindari masalah ini?
Jianwu Chen
2
solusi ini tidak berfungsi dalam beberapa situasi. Dalam basis data relasional dengan jpa, jika Anda menempatkan @JsonIgnoreAnda akan null dalam "kunci asing" ketika Anda memperbarui entitas ...
slim
629

JsonIgnoreProperties [Pembaruan 2017]:

Anda sekarang dapat menggunakan JsonIgnoreProperties untuk menekan serialisasi properti (selama serialisasi), atau mengabaikan pemrosesan properti JSON yang dibaca (saat deserialisasi) . Jika ini bukan yang Anda cari, baca terus di bawah ini.

(Terima kasih kepada As Zammel AlaaEddine karena menunjukkan ini).


JsonManagedReference dan JsonBackReference

Sejak Jackson 1.6 Anda dapat menggunakan dua anotasi untuk menyelesaikan masalah rekursi tak terbatas tanpa mengabaikan getter / setter selama serialisasi: @JsonManagedReferencedan @JsonBackReference.

Penjelasan

Agar Jackson berfungsi dengan baik, salah satu dari dua sisi hubungan tidak boleh serial, untuk menghindari infite loop yang menyebabkan kesalahan stackoverflow Anda.

Jadi, Jackson mengambil bagian depan dari referensi ( Set<BodyStat> bodyStatskelas Trainee Anda), dan mengonversinya dalam format penyimpanan seperti json; inilah yang disebut proses marshalling . Kemudian, Jackson mencari bagian belakang referensi (yaitu Trainee traineedi kelas BodyStat) dan membiarkannya apa adanya, bukan membuat cerita bersambung. Bagian dari hubungan ini akan dibangun kembali selama deserialisasi ( unmarshalling ) dari referensi ke depan.

Anda dapat mengubah kode Anda seperti ini (saya melewatkan bagian yang tidak berguna):

Objek Bisnis 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;

Objek Bisnis 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;

Sekarang semuanya harus bekerja dengan baik.

Jika Anda ingin informasi lebih lanjut, saya menulis artikel tentang masalah Json dan Jackson Stackoverflow di Keenformatics , blog saya.

EDIT:

Anotasi bermanfaat lain yang dapat Anda periksa adalah @JsonIdentityInfo : menggunakannya, setiap kali Jackson membuat serial objek Anda, itu akan menambahkan ID (atau atribut lain yang Anda pilih) ke dalamnya, sehingga itu tidak akan sepenuhnya "memindai" lagi setiap kali. Ini bisa berguna ketika Anda memiliki loop rantai di antara objek yang lebih saling terkait (misalnya: Order -> OrderLine -> Pengguna -> Order dan lagi).

Dalam hal ini Anda harus berhati-hati, karena Anda mungkin perlu membaca atribut objek Anda lebih dari sekali (misalnya dalam daftar produk dengan lebih banyak produk yang berbagi penjual yang sama), dan anotasi ini mencegah Anda untuk melakukannya. Saya sarankan untuk selalu melihat log pembakar untuk memeriksa respons Json dan melihat apa yang terjadi dalam kode Anda.

Sumber:

Kurt Bourbaki
sumber
29
Terima kasih atas jawaban yang jelas. Ini adalah solusi yang lebih nyaman daripada memakai @JsonIgnorereferensi kembali.
Utku Özdemir
3
Ini jelas cara yang tepat untuk melakukannya. Jika Anda melakukannya seperti ini di sisi server karena Anda menggunakan Jackson di sana, tidak masalah json mapper apa yang Anda gunakan di sisi klien dan Anda tidak perlu mengatur manual tautan anak ke orangtua. Itu hanya bekerja. Terima kasih Kurt
flosk8
1
Bagus, penjelasan terperinci dan jelas pendekatan yang lebih baik dan lebih deskriptif daripada @JsonIgnore.
Piotr Nowicki
2
Terima kasih! @JsonIdentityInfo bekerja untuk referensi siklus yang melibatkan banyak entitas dalam banyak loop yang tumpang tindih.
n00b
1
Saya tidak bisa mendapatkan ini bekerja untuk kehidupan saya. Saya pikir saya memiliki setup yang sangat mirip, tapi saya jelas punya sesuatu yang salah karena saya tidak bisa mendapatkan apa-apa selain kesalahan rekurisi tak terbatas:
swv
103

Anotasi baru @JsonIgnoreProperties menyelesaikan banyak masalah dengan opsi lain.

@Entity

public class Material{
   ...    
   @JsonIgnoreProperties("costMaterials")
   private List<Supplier> costSuppliers = new ArrayList<>();
   ...
}

@Entity
public class Supplier{
   ...
   @JsonIgnoreProperties("costSuppliers")
   private List<Material> costMaterials = new ArrayList<>();
   ....
}

Lihat disini. Ini berfungsi seperti dalam dokumentasi:
http://springquay.blogspot.com/2016/01/new-approach-to-solve-json-recursive.html

tero17
sumber
@tero - Dengan pendekatan ini juga kami tidak mendapatkan data yang terkait dengan entitas.
Pra_A
@ PAA HEY PAA saya pikir itu terkait dengan entitas! mengapa kamu mengatakan itu?
tero17
1
@ tero17 bagaimana Anda mengelola rekursi tak terbatas ketika Anda memiliki lebih dari 2 kelas? Misalnya: Kelas A -> Kelas B -> Kelas C -> Kelas A. Saya mencoba dengan JsonIgnoreProperties tanpa hasil
Villat
@Villat ini adalah masalah lain untuk dipecahkan, saya sarankan untuk membuka permintaan baru untuk itu.
tero17
+1 untuk contoh kode, sebagai pemula Jackson penggunaan @JsonIgnoreProperties tidak sepenuhnya jelas dengan membaca
JavaDoc
47

Juga, menggunakan Jackson 2.0+ yang dapat Anda gunakan @JsonIdentityInfo. Ini bekerja jauh lebih baik untuk kelas hibernasi saya daripada @JsonBackReferencedan @JsonManagedReference, yang memiliki masalah bagi saya dan tidak menyelesaikan masalah. Cukup tambahkan sesuatu seperti:

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

dan itu harus bekerja.

Marcus
sumber
Bisakah Anda jelaskan "Ini bekerja lebih baik"? Apakah ada masalah dengan referensi yang dikelola?
Utku Özdemir
@ UtkuÖzdemir Saya menambahkan detail tentang @JsonIdentityInfojawaban saya di atas.
Kurt Bourbaki
1
ini adalah solusi terbaik yang kami temukan sejauh ini, karena ketika kami menggunakan "@JsonManagedReference" metode get berhasil mengembalikan nilai-nilai tanpa kesalahan stackoverflow. Tetapi, ketika kami mencoba untuk menyimpan data menggunakan pos, ia mengembalikan kesalahan 415 (kesalahan media tidak didukung)
cuser
1
Saya telah menambahkan @JsonIdentityInfoanotasi ke entitas saya tetapi tidak menyelesaikan masalah rekursi. Hanya @JsonBackReferencedan @JsonManagedReferencemenyelesaikan, tetapi mereka menghapus properti yang dipetakan dari JSON.
Oleg Abrazhaev
19

Juga, Jackson 1.6 memiliki dukungan untuk menangani referensi dua arah ... yang sepertinya Anda cari ( entri blog ini juga menyebutkan fitur)

Dan pada Juli 2011, ada juga " jackson-module-hibernate " yang mungkin membantu dalam beberapa aspek berurusan dengan objek Hibernate, meskipun tidak harus yang khusus ini (yang memang membutuhkan anotasi).

StaxMan
sumber
Tautan sudah mati, apakah Anda keberatan memperbarui atau mengedit jawaban Anda.
GetMeARemoteJob
9

Ini bekerja dengan sangat baik untuk saya. Tambahkan penjelasan @JsonIgnore pada kelas anak di mana Anda menyebutkan referensi ke kelas induk.

@ManyToOne
@JoinColumn(name = "ID", nullable = false, updatable = false)
@JsonIgnore
private Member member;
Manjunath BR
sumber
2
Saya pikir @JsonIgnoremengabaikan atribut ini agar tidak diambil ke sisi klien. Bagaimana jika saya memerlukan atribut ini dengan anaknya (jika memiliki anak)?
Khasan 24-7
6

Sekarang ada modul Jackson (untuk Jackson 2) yang dirancang khusus untuk menangani masalah inisialisasi malas Hibernate ketika membuat serial.

https://github.com/FasterXML/jackson-datatype-hibernate

Cukup tambahkan dependensi (perhatikan ada dependensi berbeda untuk Hibernate 3 dan Hibernate 4):

<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-hibernate4</artifactId>
  <version>2.4.0</version>
</dependency>

dan kemudian daftarkan modul ketika menginternalisasi ObjectMapper Jackson:

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

Dokumentasi saat ini tidak bagus. Lihat kode Hibernate4Module untuk opsi yang tersedia.

Shane
sumber
Masalahnya adalah menyelesaikannya, karena itu terlihat menarik. Saya memiliki masalah yang sama dengan OP dan semua trik termasuk di atas tidak berhasil.
user1567291
6

Bekerja dengan baik untuk saya Selesaikan masalah Rekursi Json Infinite ketika bekerja dengan Jackson

Ini adalah apa yang telah saya lakukan di Pemetaan oneToMany dan ManyToOne

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


@OneToMany(mappedBy="LgcyIsp ")
@JsonManagedReference
private List<Safety> safety;
Prabu M
sumber
Saya telah menggunakan pemetaan hibernate dalam aplikasi booting musim semi
Prabu M
Hai Penulis, Terima kasih atas tutorialnya yang bagus dan posting yang bagus. Namun saya menemukan bahwa @JsonManagedReference, @JsonBackReferencetidak memberi Anda data yang terkait dengan @OneToManydan @ManyToOneskenario, juga ketika menggunakan @JsonIgnorePropertiestidak melewati data entitas terkait. Bagaimana cara mengatasinya?
Pra_A
5

Bagi saya solusi terbaik adalah menggunakan @JsonViewdan membuat filter khusus untuk setiap skenario. Anda juga bisa menggunakan @JsonManagedReferencedan@JsonBackReference , bagaimanapun, itu adalah solusi yang sulit untuk hanya satu situasi, di mana pemilik selalu merujuk sisi pemilik, dan tidak pernah sebaliknya. Jika Anda memiliki skenario serialisasi lain di mana Anda perlu menganotasi ulang atribut secara berbeda, Anda tidak akan bisa.

Masalah

Mari kita gunakan dua kelas, Companydan di Employeemana Anda memiliki ketergantungan siklik di antara mereka:

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

Dan kelas uji yang mencoba membuat serial menggunakan 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);
    }
}

Jika Anda menjalankan kode ini, Anda akan mendapatkan:

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

Solusi Menggunakan `@ JsonView`

@JsonViewmemungkinkan Anda untuk menggunakan filter dan memilih bidang apa yang harus dimasukkan saat membuat serial objek. Filter hanyalah referensi kelas yang digunakan sebagai pengidentifikasi. Jadi pertama mari kita buat filter:

public class Filter {

    public static interface EmployeeData {};

    public static interface CompanyData extends EmployeeData {};

} 

Ingat, filter adalah kelas dummy, hanya digunakan untuk menentukan bidang dengan @JsonViewanotasi, sehingga Anda dapat membuat sebanyak yang Anda inginkan dan butuhkan. Mari kita lihat dalam aksi, tapi pertama-tama kita perlu memberi penjelasan pada Companykelas kita :

public class Company {

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

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

    public Employee getEmployee() {
        return employee;
    }
}

dan ubah Tes agar serializer menggunakan Tampilan:

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

Sekarang jika Anda menjalankan kode ini, masalah Rekursi Tak Terbatas diselesaikan, karena Anda telah secara eksplisit mengatakan bahwa Anda hanya ingin membuat cerita bersambung atribut yang dijelaskan dengan @JsonView(Filter.CompanyData.class) .

Ketika mencapai referensi belakang untuk perusahaan di Employee, itu memeriksa bahwa itu tidak dijelaskan dan mengabaikan serialisasi. Anda juga memiliki solusi yang kuat dan fleksibel untuk memilih data mana yang ingin Anda kirim melalui API REST Anda.

Dengan Spring Anda dapat membuat anotasi metode Pengendali REST Anda dengan @JsonViewfilter yang diinginkan dan serialisasi diterapkan secara transparan ke objek yang kembali.

Berikut adalah impor yang digunakan jika Anda perlu memeriksa:

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;
fabioresner
sumber
1
Ini adalah artikel yang bagus menjelaskan banyak solusi alternatif untuk menyelesaikan rekursi: baeldung.com/...
Hugo Baés
5

@JsonIgnoreProperties adalah jawabannya.

Gunakan sesuatu seperti ini ::

@OneToMany(mappedBy = "course",fetch=FetchType.EAGER)
@JsonIgnoreProperties("course")
private Set<Student> students;
kode ifelse
sumber
Gunakan ini dengan percaya diri seperti yang saya lihat Jhipster menggunakan ini dalam kode yang dihasilkan
ifelse.codes
Terima kasih atas jawabannya. Namun saya menemukan bahwa @JsonManagedReference, @JsonBackReferencetidak memberi Anda data yang terkait dengan @OneToManydan @ManyToOneskenario, juga ketika menggunakan @JsonIgnorePropertiestidak melewati data entitas terkait. Bagaimana cara mengatasinya?
Pra_A
4

Dalam kasus saya, cukup mengubah hubungan dari:

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

untuk:

@OneToMany
private List<Town> towns;

hubungan lain tetap seperti itu:

@ManyToOne
@JoinColumn(name = "county_id")
private County county;
Klapsa2503
sumber
2
Saya pikir lebih baik menggunakan solusi Kurt. Karena solusi JoinColumn dapat berakhir dengan mayat data yang tidak direferensikan.
flosk8
1
Ini sebenarnya satu-satunya hal yang membantu saya. Tidak ada solusi lain dari atas yang berhasil. Saya masih tidak yakin mengapa ...
Deniss M.
4

Pastikan Anda menggunakan com.fasterxml.jackson di mana-mana. Saya menghabiskan banyak waktu untuk mengetahuinya.

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

Kemudian gunakan @JsonManagedReferencedan @JsonBackReference.

Akhirnya, Anda dapat membuat cerita bersambung model Anda ke JSON:

import com.fasterxml.jackson.databind.ObjectMapper;

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

Anda dapat menggunakan @JsonIgnore , tetapi ini akan mengabaikan data json yang dapat diakses karena hubungan Foreign Key. Karena itu jika Anda mengatur ulang data kunci asing (sebagian besar waktu yang kami butuhkan), maka @JsonIgnore tidak akan membantu Anda. Dalam situasi seperti itu silakan ikuti solusi di bawah ini.

Anda mendapatkan rekursi Infinite, karena kelas BodyStat lagi merujuk objek Trainee

BodyStat

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

Trainee

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

Karena itu, Anda harus mengomentari / menghilangkan bagian di atas dalam Trainee

RAJ KASHWAN
sumber
1
Dalam kasus saya, ini tidak berfungsi. Bisakah Anda melihatnya: github.com/JavaHelper/issue-jackson-boot ?
Pra_A
3

Saya juga menemui masalah yang sama. Saya menggunakan @JsonIdentityInfo's ObjectIdGenerators.PropertyGenerator.classjenis pembangkit.

Itu solusi saya:

@Entity
@Table(name = "ta_trainee", uniqueConstraints = {@UniqueConstraint(columnNames = {"id"})})
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Trainee extends BusinessObject {
...
Arif Acar
sumber
2
Jawaban yang bagus
LAGRIDA
1
Saya akan menjawab hal yang sama !!! Nice;)
Felipe Desiderati
3

Anda harus menggunakan @JsonBackReference dengan entitas @ManyToOne dan @JsonManagedReference dengan @onetomany yang berisi kelas entitas.

@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;
Shubham
sumber
Jika saya memasukkan @ jsonIgnore anotasi pada anak. Saya tidak bisa mendapatkan objek orangtua dari anak. Ketika saya mencoba mengambil anak itu. mengapa objek induk tidak datang, diabaikan oleh @ jsonignore. katakan padaku cara untuk berpindah dari anak ke orang tua dan orang tua ke anak.
Kumaresan Perumal
Tidak perlu Menggunakan @JsonIgnore cukup gunakan penjelasan di atas dan Untuk mendapatkan keberatan dari orang tua dan anak Dengan menggunakan Getters dan setter. dan Jsonignore juga melakukan hal yang sama tetapi itu akan menciptakan rekursi tak terbatas. Jika Anda membagikan kode Anda maka saya dapat memeriksa mengapa Anda tidak mendapatkan objek. Karena bagi saya keduanya akan datang.
Shubham
Saya bermaksud mengatakan. saat mengambil orang tua. Orang tua harus datang dengan objek anak. saat mengambil objek anak. Anak itu harus datang dengan orang tua. Ini tidak berfungsi dalam skenario ini. Bisakah bantu saya?
Kumaresan Perumal
1

Anda dapat menggunakan pola DTO membuat kelas TraineeDTO tanpa hiberbnate anotasi dan Anda dapat menggunakan jackson mapper untuk mengonversi Trainee ke TraineeDTO dan bingo pesan kesalahan disapeare :)

Dahar Youssef
sumber
1

Jika Anda tidak dapat mengabaikan properti, cobalah memodifikasi visibilitas bidang. Dalam kasus kami, kami memiliki kode lama yang masih mengirimkan entitas dengan hubungan, jadi dalam kasus saya, ini adalah perbaikannya:

    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    private Trainee trainee;
Scott Langeberg
sumber
Jika saya memasukkan @ jsonIgnore anotasi pada anak. Saya tidak bisa mendapatkan objek orangtua dari anak. Ketika saya mencoba mengambil anak itu. mengapa objek induk tidak datang, diabaikan oleh @ jsonignore. katakan padaku cara untuk berpindah dari anak ke orang tua dan orang tua ke anak.
Kumaresan Perumal
0

Saya memiliki masalah ini, tetapi saya tidak ingin menggunakan anotasi di entitas saya, jadi saya menyelesaikannya dengan membuat konstruktor untuk kelas saya, konstruktor ini tidak boleh memiliki referensi kembali ke entitas yang mereferensikan entitas ini. Katakanlah skenario ini.

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

Jika Anda mencoba mengirim ke tampilan kelas Batau Adengan @ResponseBodyhal itu dapat menyebabkan loop tak terbatas. Anda dapat menulis konstruktor di kelas Anda dan membuat kueri dengan entityManagerseperti ini.

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

Ini adalah kelas dengan konstruktor.

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

}

Namun, ada beberapa penyempitan tentang solusi ini, seperti yang Anda lihat, di konstruktor saya tidak membuat referensi ke Daftar bs ini karena Hibernate tidak mengizinkannya, setidaknya dalam versi 3.6.10 . Terakhir , jadi ketika saya membutuhkan untuk menunjukkan kedua entitas dalam tampilan, saya melakukan hal berikut.

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

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

Masalah lain dengan solusi ini, adalah bahwa jika Anda menambah atau menghapus properti, Anda harus memperbarui konstruktor dan semua pertanyaan Anda.

OJVM
sumber
0

Jika Anda menggunakan Spring Data Rest, masalah dapat diatasi dengan membuat Gudang untuk setiap Entitas yang terlibat dalam referensi siklus.

Morik
sumber
0

Saya terlambat datang dan ini sudah sangat panjang. Tetapi saya menghabiskan beberapa jam mencoba untuk mencari tahu hal ini juga, dan ingin memberikan kasus saya sebagai contoh lain.

Saya mencoba solusi JsonIgnore, JsonIgnoreProperties, dan BackReference, tetapi anehnya mereka tidak dijemput.

Saya menggunakan Lombok dan berpikir bahwa itu mungkin mengganggu, karena ia menciptakan konstruktor dan menimpa ke String (lihat toString di stackoverflowerror stack).

Akhirnya itu bukan kesalahan Lombok - saya menggunakan generasi NetBeans otomatis entitas JPA dari tabel database, tanpa terlalu memikirkannya - well, dan salah satu penjelasan yang ditambahkan ke kelas yang dihasilkan adalah @XmlRootElement. Setelah saya hapus semuanya mulai bekerja. Baiklah.

hello_earth
sumber
0

Intinya adalah untuk menempatkan @JsonIgnore dalam metode setter sebagai berikut. dalam hal ini.

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;
zawhtut
sumber