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>
@Transient
keTrainee.bodyStats
.@JsonIgnoreProperties
adalah solusi terbersih. Lihat jawaban Zammel AlaaEddine untuk lebih jelasnya.Jawaban:
Anda dapat menggunakan
@JsonIgnore
untuk memutus siklus.sumber
@JsonIgnore
Anda akan null dalam "kunci asing" ketika Anda memperbarui entitas ...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:
@JsonManagedReference
dan@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> bodyStats
kelas Trainee Anda), dan mengonversinya dalam format penyimpanan seperti json; inilah yang disebut proses marshalling . Kemudian, Jackson mencari bagian belakang referensi (yaituTrainee trainee
di 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:
Objek Bisnis 2:
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:
sumber
@JsonIgnore
referensi kembali.@JsonIgnore
.Anotasi baru @JsonIgnoreProperties menyelesaikan banyak masalah dengan opsi lain.
Lihat disini. Ini berfungsi seperti dalam dokumentasi:
http://springquay.blogspot.com/2016/01/new-approach-to-solve-json-recursive.html
sumber
Juga, menggunakan Jackson 2.0+ yang dapat Anda gunakan
@JsonIdentityInfo
. Ini bekerja jauh lebih baik untuk kelas hibernasi saya daripada@JsonBackReference
dan@JsonManagedReference
, yang memiliki masalah bagi saya dan tidak menyelesaikan masalah. Cukup tambahkan sesuatu seperti:dan itu harus bekerja.
sumber
@JsonIdentityInfo
jawaban saya di atas.@JsonIdentityInfo
anotasi ke entitas saya tetapi tidak menyelesaikan masalah rekursi. Hanya@JsonBackReference
dan@JsonManagedReference
menyelesaikan, tetapi mereka menghapus properti yang dipetakan dari JSON.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).
sumber
Sekarang Jackson mendukung menghindari siklus tanpa mengabaikan bidang:
Jackson - serialisasi entitas dengan hubungan birectional (menghindari siklus)
sumber
Ini bekerja dengan sangat baik untuk saya. Tambahkan penjelasan @JsonIgnore pada kelas anak di mana Anda menyebutkan referensi ke kelas induk.
sumber
@JsonIgnore
mengabaikan atribut ini agar tidak diambil ke sisi klien. Bagaimana jika saya memerlukan atribut ini dengan anaknya (jika memiliki anak)?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):
dan kemudian daftarkan modul ketika menginternalisasi ObjectMapper Jackson:
Dokumentasi saat ini tidak bagus. Lihat kode Hibernate4Module untuk opsi yang tersedia.
sumber
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
sumber
@JsonManagedReference
,@JsonBackReference
tidak memberi Anda data yang terkait dengan@OneToMany
dan@ManyToOne
skenario, juga ketika menggunakan@JsonIgnoreProperties
tidak melewati data entitas terkait. Bagaimana cara mengatasinya?Bagi saya solusi terbaik adalah menggunakan
@JsonView
dan membuat filter khusus untuk setiap skenario. Anda juga bisa menggunakan@JsonManagedReference
dan@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,
Company
dan diEmployee
mana Anda memiliki ketergantungan siklik di antara mereka:Dan kelas uji yang mencoba membuat serial menggunakan
ObjectMapper
( Spring Boot ):Jika Anda menjalankan kode ini, Anda akan mendapatkan:
Solusi Menggunakan `@ JsonView`
@JsonView
memungkinkan 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:Ingat, filter adalah kelas dummy, hanya digunakan untuk menentukan bidang dengan
@JsonView
anotasi, sehingga Anda dapat membuat sebanyak yang Anda inginkan dan butuhkan. Mari kita lihat dalam aksi, tapi pertama-tama kita perlu memberi penjelasan padaCompany
kelas kita :dan ubah Tes agar serializer menggunakan Tampilan:
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
@JsonView
filter yang diinginkan dan serialisasi diterapkan secara transparan ke objek yang kembali.Berikut adalah impor yang digunakan jika Anda perlu memeriksa:
sumber
@JsonIgnoreProperties adalah jawabannya.
Gunakan sesuatu seperti ini ::
sumber
@JsonManagedReference
,@JsonBackReference
tidak memberi Anda data yang terkait dengan@OneToMany
dan@ManyToOne
skenario, juga ketika menggunakan@JsonIgnoreProperties
tidak melewati data entitas terkait. Bagaimana cara mengatasinya?Dalam kasus saya, cukup mengubah hubungan dari:
untuk:
hubungan lain tetap seperti itu:
sumber
Pastikan Anda menggunakan com.fasterxml.jackson di mana-mana. Saya menghabiskan banyak waktu untuk mengetahuinya.
Kemudian gunakan
@JsonManagedReference
dan@JsonBackReference
.Akhirnya, Anda dapat membuat cerita bersambung model Anda ke JSON:
sumber
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
Trainee
Karena itu, Anda harus mengomentari / menghilangkan bagian di atas dalam Trainee
sumber
Saya juga menemui masalah yang sama. Saya menggunakan
@JsonIdentityInfo
'sObjectIdGenerators.PropertyGenerator.class
jenis pembangkit.Itu solusi saya:
sumber
Anda harus menggunakan @JsonBackReference dengan entitas @ManyToOne dan @JsonManagedReference dengan @onetomany yang berisi kelas entitas.
sumber
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 :)
sumber
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:
sumber
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.
Jika Anda mencoba mengirim ke tampilan kelas
B
atauA
dengan@ResponseBody
hal itu dapat menyebabkan loop tak terbatas. Anda dapat menulis konstruktor di kelas Anda dan membuat kueri denganentityManager
seperti ini.Ini adalah kelas dengan konstruktor.
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.
Masalah lain dengan solusi ini, adalah bahwa jika Anda menambah atau menghapus properti, Anda harus memperbarui konstruktor dan semua pertanyaan Anda.
sumber
Jika Anda menggunakan Spring Data Rest, masalah dapat diatasi dengan membuat Gudang untuk setiap Entitas yang terlibat dalam referensi siklus.
sumber
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.
sumber
Intinya adalah untuk menempatkan @JsonIgnore dalam metode setter sebagai berikut. dalam hal ini.
Township.java
Village.java
sumber