1. ホーム
  2. jpa

[解決済み] Spring Data JPAでgetOneとfindOneメソッドを使用する場合

2022-04-30 07:57:45

質問

以下のように呼び出すユースケースがあります。

@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
public UserControl getUserControlById(Integer id){
    return this.userControlRepository.getOne(id);
}

を観察してください。 @Transactional があります。 伝搬.REQUIRES_NEW を使用し、リポジトリは ゲットワン . アプリを実行すると、次のようなエラーメッセージが表示されます。

Exception in thread "main" org.hibernate.LazyInitializationException: 
could not initialize proxy - no Session
...

しかし、もし私が getOne(id)findOne(id) はすべて正常に動作します。

ちなみに、ユースケースで getUserControlById メソッドを呼び出した後、すでに insertUserControl メソッド

@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
public UserControl insertUserControl(UserControl userControl) {
    return this.userControlRepository.save(userControl);
}

どちらの方式も 伝搬.REQUIRES_NEW をしているので、単純な 監査 の制御を行います。

を使っています。 getOne メソッドで定義されているからです。 JpaRepository インターフェイスがあり、私のリポジトリインターフェイスはそこから拡張されています。

その JpaRepository インターフェースは CrudRepository(クラッドレポジトリ . また findOne(id) メソッドが定義されています。 CrudRepository .

私の質問です。

  1. なぜ getOne(id) メソッドを使用できますか?
  2. を使用する必要があります。 getOne(id) メソッドを使用できますか?

私は他のリポジトリと一緒に作業していますが、すべてのリポジトリで getOne(id) メソッドを使用すると、すべて正常に動作します。 プロパゲーション.REQUIRES_NEW は失敗します。

によると ゲットワン APIを使用します。

<ブロッククオート

指定された識別子を持つエンティティへの参照を返します。

によると FindOne APIを使用します。

<ブロッククオート

IDでエンティティを取得する。

  1. を使うべき時 findOne(id) メソッドを使用できますか?

  2. どのような方法で使用することが推奨されていますか?

解決方法は?

TL;DR

T findOne(ID id) (旧APIでの名称) / Optional<T> findById(ID id) (新しいAPIでの名前)に依存しています。 EntityManager.find() を実行するものです。 エンティティイーガーローディング .

T getOne(ID id) に依存しています。 EntityManager.getReference() を実行するものです。 エンティティ レイジー ロード . そのため、エンティティの効果的な読み込みを保証するために、そのエンティティに対するメソッドの呼び出しが必要です。

findOne()/findById() の方が、実に明快でシンプルに使える。 getOne() .

そのため、ほとんどの場合 findOne()/findById() オーバー getOne() .


API変更

少なくとも 2.0 バージョンに変更しました。 Spring-Data-Jpa 修正済み findOne() .

以前は CrudRepository インターフェイスは次のとおりです。

T findOne(ID primaryKey);

では、1つの findOne() メソッドにあります。 CrudRepository で定義されているものです。 QueryByExampleExecutor というインタフェースがあります。

<S extends T> Optional<S> findOne(Example<S> example);

それを最終的に実装しているのが SimpleJpaRepository のデフォルトの実装である CrudRepository インターフェイスを提供します。

このメソッドは例による検索であり、そのような置き換えはしないようにしましょう。

実は、新しいAPIでも同じ動作をするメソッドは存在しますが、メソッド名が変更されています。

から改名されました。 findOne() から findById() の中に CrudRepository インタフェースになります。

Optional<T> findById(ID id); 

今度は Optional . を防ぐには、それほど悪いことではありません。 NullPointerException .

ということで、実際の選択肢は Optional<T> findById(ID id)T getOne(ID id) .


2つの異なるJPA EntityManagerの検索メソッドに依存する2つの異なるメソッド

1) Optional<T> findById(ID id) ジャバドック は、それが:

idでエンティティを取得する。

実装を見ていくと、この実装は EntityManager.find() を使用して検索を行います。

public Optional<T> findById(ID id) {

    Assert.notNull(id, ID_MUST_NOT_BE_NULL);

    Class<T> domainType = getDomainClass();

    if (metadata == null) {
        return Optional.ofNullable(em.find(domainType, id));
    }

    LockModeType type = metadata.getLockModeType();

    Map<String, Object> hints = getQueryHints().withFetchGraphs(em).asMap();

    return Optional.ofNullable(type == null ? em.find(domainType, id, hints) : em.find(domainType, id, type, hints));
}

そしてこちら em.find() EntityManager として宣言されたメソッドです。

public <T> T find(Class<T> entityClass, Object primaryKey,
                  Map<String, Object> properties);

そのjavadocには、次のように書かれています。

<ブロッククオート

指定されたプロパティを用いて、主キーで検索する

つまり、ロードされたエンティティを取得することは期待できそうです。

2)一方 T getOne(ID id) ジャバドック の状態です(強調は私)。

<ブロッククオート

を返します。 参照 を、指定された識別子を持つエンティティに変換します。

実際には 参照 という用語がありますが、JPA APIはこの用語を一切使用していません。 getOne() メソッドを使用します。

ですから、Springのラッパーが何を行っているかを理解するために最も良いことは、実装を調べることです。

@Override
public T getOne(ID id) {
    Assert.notNull(id, ID_MUST_NOT_BE_NULL);
    return em.getReference(getDomainClass(), id);
}

ここで em.getReference() EntityManager として宣言されたメソッドです。

public <T> T getReference(Class<T> entityClass,
                              Object primaryKey);

そして幸いなことに、この EntityManager javadocはその意図をより良く定義しています(強調は私です).

<ブロッククオート

インスタンスを取得し、その状態を遅延フェッチすることができます。 . 要求された インスタンスがデータベース内に存在しない場合、EntityNotFoundException が投げられます。 インスタンス状態に最初にアクセスしたとき . (永続化 プロバイダのランタイムはEntityNotFoundExceptionを投げることが許されています。 が呼び出されたとき、getReference が呼び出されます)。 アプリケーションは インスタンス状態が切り離し時に利用可能になること ただし エンティティ・マネージャが開いている間、アプリケーションによってアクセスされる。

そのため getOne() は遅延フェッチされたエンティティを返すかもしれません。

ここでは、遅延フェッチはエンティティのリレーションシップではなく、エンティティそのものを参照しています。

これは、もし私たちが getOne() で、Persistenceコンテキストが閉じられると、エンティティはロードされないかもしれないので、結果は本当に予測不可能です。

例えば、プロキシオブジェクトがシリアライズされている場合、そのプロキシオブジェクトに null の参照をシリアライズした結果、またはプロキシオブジェクトに対してメソッドを呼び出した場合、次のような例外が発生します。 LazyInitializationException がスローされます。

ですから、このような状況では、スローの EntityNotFoundException を使用する主な理由である getOne() は、データベースに存在しないインスタンスを処理するために、エンティティが存在しない間はエラー状況が実行されない可能性があります。

いずれにせよ、セッションを開いている間に、エンティティの読み込みを確実にするために、エンティティを操作する必要があります。これは、エンティティに対して任意のメソッドを呼び出すことで可能です。

または、より良い代替手段を使用する findById(ID id) の代わりに


なぜこんなに不明確なAPIなのか?

最後に、Spring-Data-JPAの開発者に2つの質問をします。

  • について、なぜもっと明確なドキュメントがないのでしょうか? getOne() ? エンティティのレイジーローディングは、本当に細かいことではありません。

  • を導入する必要があるのでしょうか? getOne() を包むために EM.getReference() ?

    なぜ、単純にラップ法にこだわらないのか : getReference() ? この EM メソッドは非常に特殊であり、一方 getOne() は、とてもシンプルな処理を伝えています。