web-dev-qa-db-ja.com

JSONを書き込めませんでした:ロールのコレクションの遅延初期化に失敗しました

サーバーを実現していますREST with Java-hibernate-jsonを返すspring

多対多の関係をマップしています。

私はよりよく説明します、私は成分のリストを持っている供給者を持っています、そして、各成分は供給者のリストを持っています。

テーブルを作成しました:

CREATE TABLE supplier_ingredient (
supplier_id BIGINT,
ingredient_id BIGINT
)


ALTER TABLE supplier_ingredient ADD CONSTRAINT supplier_ingredient_pkey 
PRIMARY KEY(supplier_id, ingredient_id);

ALTER TABLE supplier_ingredient ADD CONSTRAINT 
fk_supplier_ingredient_ingredient_id FOREIGN KEY (ingredient_id) 
REFERENCES ingredient(id);

ALTER TABLE supplier_ingredient ADD CONSTRAINT 
fk_supplier_ingredient_supplier_id FOREIGN KEY (supplier_id) REFERENCES 
supplier(id);

次に、Ingredientモデルがあります:

.....
.....
@ManyToMany(mappedBy = "ingredients")
@OrderBy("created DESC")
@BatchSize(size = 1000)
private List<Supplier> suppliers = new ArrayList<>();
....
....

次に、Supplierモデルがあります:

....
@ManyToMany
@JoinTable( name = "supplier_ingredient ", 
        joinColumns = @JoinColumn(name = "supplier_id", referencedColumnName = "id"), 
        inverseJoinColumns = @JoinColumn(name = "ingredient_id", referencedColumnName = "id"), 
        foreignKey = @ForeignKey(name = "fk_supplier_ingredient_supplier_id"))
@OrderBy("created DESC")
@Cascade(CascadeType.SAVE_UPDATE)
@BatchSize(size = 1000)
private List<Ingredient> ingredients = new ArrayList<>();
....

エンドポイント

@RequestMapping(value = "/{supplierId:[0-9]+}", method = RequestMethod.GET)
@ResponseStatus(value = HttpStatus.OK)
@ResponseBody
public SupplierObject get(@PathVariable Long supplierId) {

    Supplier supplier = supplierService.get(supplierId);

    SupplierObject supplierObject = new SupplierObject (supplier);

    return SupplierObject;

}

サービス

....
public Supplier get(Long supplierId) {

    Supplier supplier = supplierDao.getById(supplierId); (it does entityManager.find(entityClass, id))

    if (supplier == null) throw new ResourceNotFound("supplier", supplierId);

    return supplier;
}
....

SupplierObject

    @JsonIgnoreProperties(ignoreUnknown = true)
    public class SupplierObject extends IdAbstractObject {

    public String email;

    public String phoneNumber;

    public String address;

    public String responsible;

    public String companyName;

    public String vat;

    public List<Ingredient> ingredients = new ArrayList<>();

    public SupplierObject () {

    }

    public SupplierObject (Supplier supplier) {

        id = supplier.getId();
        email = supplier.getEmail();
        responsible = supplier.getResponsible();
        companyName = supplier.getCompanyName();
        phoneNumber = supplier.getPhone_number();
        ingredients = supplier.getIngredients();
        vat = supplier.getVat();
        address = supplier.getAddress();


    }
}

そしてIdAbstractObject

public abstract class IdAbstractObject{

    public Long id;

}

私の問題は、エンドポイントを呼び出すときです:

http://localhost:8080/supplier/1

エラーが発生しました:

「JSONを書き込めませんでした:ロールのコレクションの遅延初期化に失敗しました:myPackage.ingredient.Ingredient.suppliers、プロキシを初期化できませんでした-セッションなし、ネストされた例外はcom.fasterxml.jackson.databind.JsonMappingExceptionです:遅延コレクションの初期化に失敗しましたロールの:myPackage.ingredient.Ingredient.suppliers、プロキシを初期化できませんでした-セッションなし(参照チェーン:myPackage.supplier.SupplierObject [\ "ingredients \"]-> org.hibernate.collection.internal.PersistentBag [0]- > myPackage.ingredient.Ingredient [\ "suppliers \"]) "

私はこれに従いました:

フェッチされていない遅延オブジェクトでのジャクソンのシリアル化を避ける

今はエラーはありませんが、jsonで返された成分フィールドはnullです:

{
"id": 1,
"email": "[email protected]",
"phoneNumber": null,
"address": null,
"responsible": null,
"companyName": "Company name",
"vat": "vat number",
"ingredients": null
}

しかし、デバッグでは成分を見ることができます....

enter image description here

5
Gjord83

これは、HibernateおよびJackson Marshallerの通常の動作です。基本的に、次のものが必要です。すべてのサプライヤーオブジェクトの詳細を含むJSON ...成分が含まれています。

この場合、JSON自体を作成しようとすると循環参照ができるため、 JsonIgnore アノテーションも使用する必要があるため、非常に注意する必要があることに注意してください。

最初に行う必要があるのは、サプライヤーとそのすべての詳細(成分を含む)をロードすることです。

どうすればできますか?いくつかの戦略を使用して... Hibernate.initialize を使用しましょう。このは、DAO(またはリポジトリ)実装(基本的には休止状態セッションを使用する場所)にある休止状態セッションを閉じる前に使用する必要があります。

そのため、この場合(Hibernateを使用することを想定しています)、リポジトリクラスで次のように記述します。

public Supplier findByKey(Long id)
{
    Supplier result = (Supplier) getSession().find(Supplier.class, id);
    Hibernate.initialize(result.getIngredients());
    return result;
}

これで、Supplierオブジェクトにすべての詳細(Ingredientsも)が追加されました。これで、サービスで次のことができます。

@RequestMapping(value = "/{supplierId:[0-9]+}", method = RequestMethod.GET)
@ResponseStatus(value = HttpStatus.OK)
@ResponseBody
public SupplierObject get(@PathVariable Long supplierId) 
{
    Supplier supplier = supplierService.get(supplierId);
    SupplierObject supplierObject = new SupplierObject (supplier);
    return SupplierObject;
}

この方法で、JacksonはJSON butを記述できます。Ingredientオブジェクトを見てみましょう。次のプロパティがあります。

@ManyToMany(mappedBy = "ingredients")
@OrderBy("created DESC")
@BatchSize(size = 1000)
private List<Supplier> suppliers = new ArrayList<>();

JacksonがJSONを作成しようとするとどうなりますか? List<Ingredient>内の各要素にアクセスし、これもJSONを作成しようとします。サプライヤリストについても、これは循環参照です。 JsonIgnoreアノテーションを使用して回避できます。たとえば、次のようにIngredientエンティティクラスを記述できます。

@JsonIgnoreProperties(value= {"suppliers"})
public class Ingredient implements Serializable
{
......
}

この方法で:

  • サプライヤオブジェクトにすべての関連成分をロードします
  • jSON自体を作成しようとすると、循環参照を避けてください

いずれにしても、JSONのマーシャリングとアンマーシャリングに使用する特定のDTO(またはVO)オブジェクトを作成することをお勧めします

これが役に立つことを願っています

アンジェロ

8

私のプロジェクトでは、あなたと同じ問題に遭遇しました。問題は、データを「1対多」で読み取るまでに、セッションがすでに閉じられていることです。すべてのデータを取得するには、トランザクションを明示的に初期化または使用する必要があります。明示的な初期化を使用しました。 DAOに行を追加する必要があります。

Hibernate.initialize(supplier.getIngredients());

その後、Hibernateはデータベースからすべてのデータをロードします。 JSONにシリアル化するときに例外が生成されないように、1対多のモデルフィールドに@JsonIgnore注釈を追加します。

これが私のコードの例です:

1.モデル

@OneToMany(mappedBy = "commandByEv", fetch = FetchType.LAZY)
@JsonIgnore
private Set<Evaluation> evaluations;

2. DAO

public Command getCommand(long id) {
Session session = sessionFactory.getCurrentSession();
Evaluation evaluation = session.get(Evaluation.class, id);
Hibernate.initialize(evaluation.getCommand());
return evaluation.getCommand();
}
2
conacry

モデルクラスの@JsonIgnoreの後に@oneToManyを追加するだけです。

0
Majid

これは、レイジー初期化が開始される前に休止状態のセッションが閉じられたためです。

解決策は、以下のこの回答で詳しく説明されています。 フェッチされていない遅延オブジェクトでのジャクソンのシリアル化を避ける

0
Nikhil Yekhe

この問題を解決するいくつかの解決策があります。

  1. @ManyToMany(fetch = FetchType.LAZY)を使用できます

しかし、EAGERフェッチはパフォーマンスの観点から非常に悪いです。さらに、EAGERアソシエーションを取得すると、それをLAZYにする方法はありません。

  1. @ManyToMany @Fetch(FetchMode.JOIN)を使用できます

詳細: https://docs.jboss.org/hibernate/orm/3.2/api/org/hibernate/FetchMode.html

編集:youtに次の行があるときに発生する可能性がありますapplication.propertiesファイル:

spring.jpa.open-in-view = false
0
veben