498

双方向の関連付けがあるJPAオブジェクトをJSONに変換しようとすると、次のようになります。

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

私が見つけたのはこのスレッドだけで、基本的に双方向の関連付けを避けることをお勧めします。この春のバグの回避策について誰かが考えていますか?

------編集2010-07-2416: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();
    }
}

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

28 に答える 28

345

@JsonIgnoreサイクルを壊すために使用できます (参照)。

org.codehaus.jackson.annotate.JsonIgnore(レガシー バージョン) またはcom.fasterxml.jackson.annotation.JsonIgnore(現在のバージョン)をインポートする必要があります。

于 2010-07-25T14:53:42.990 に答える
52

また、Jackson 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 {

そしてそれはうまくいくはずです。

于 2013-12-02T21:55:15.363 に答える
19

また、Jackson 1.6 は双方向参照の処理をサポートしています...これはあなたが探しているもののようです (このブログエントリでもこの機能について言及しています)

また、2011 年 7 月の時点で、" jackson-module-hibernate " もあり、Hibernate オブジェクトを処理するいくつかの側面で役立つ可能性がありますが、必ずしもこの特定のもの (注釈が必要) ではありません)。

于 2010-11-08T18:35:58.367 に答える
12

現在、Jackson はフィールドを無視せずにサイクルを回避することをサポートしています。

Jackson - 二項関係を持つエンティティのシリアル化 (サイクルの回避)

于 2012-04-10T03:46:55.663 に答える
7

シリアライズ時に Hibernate の遅延初期化の問題を処理するために特別に設計された Jackson モジュール (Jackson 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>

次に、Jackson の ObjectMapper を初期化するときにモジュールを登録します。

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

現在、ドキュメンテーションは素晴らしいものではありません。利用可能なオプションについては、 Hibernate4Module コードを参照してください。

于 2014-12-14T04:50:52.553 に答える
5

私の場合、関係を次のように変更するだけで十分でした:

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

に:

@OneToMany
private List<Town> towns;

別の関係はそのままでした:

@ManyToOne
@JoinColumn(name = "county_id")
private County county;
于 2014-08-29T17:31:20.450 に答える
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);
于 2016-09-01T11:41:33.717 に答える
3

さらに分析を行った後、同じ問題を抱えていることがわかりました。 @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;
于 2020-07-23T02:24:01.667 に答える
3

非常に重要: LOMBOK を使用している場合は、Set、List などのコレクションの属性を除外するようにしてください...

このような:

@EqualsAndHashCode(exclude = {"attributeOfTypeList", "attributeOfTypeSet"})
于 2020-08-14T16:02:53.773 に答える
2

DTO パターン create class TraineeDTO を注釈 hiberbnate なしで使用でき、jackson マッパーを使用して Trainee を TraineeDTO に変換し、ビンゴでエラー メッセージを消すことができます :)

于 2015-08-20T15:00:47.663 に答える
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への参照を作成しませんでした。これは、少なくともバージョン 3.6.10.Finalでは Hibernate が許可していないためです。ビューに両方のエンティティを表示するには、次のようにします。

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

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

このソリューションのもう 1 つの問題は、プロパティを追加または削除する場合、コンストラクターとすべてのクエリを更新する必要があることです。

于 2015-11-30T20:20:50.313 に答える
0

Spring Data Rest を使用している場合、循環参照に関与するすべてのエンティティのリポジトリを作成することで問題を解決できます。

于 2016-04-20T20:54:28.123 に答える
0

この投稿: https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion に完全な説明があります。

以前のバージョンで Jackson を使用している場合は、@jsonmanagedreference + @jsonbackreference を試すことができます。Jackson が 2 を超えている場合 (私の知る限り、1.9 も機能しません)、代わりに @JsonIdentityInfo を試してください。

于 2021-07-25T18:17:23.700 に答える