web-dev-qa-db-ja.com

getOneメソッドおよびfindOneメソッドを使用する場合Spring Data JPA

私はそれが以下を呼び出すユースケースを持っています。

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

@TransactionalPropagation.REQUIRES_NEWを持ち、リポジトリがgetOneを使用していることを確認します。アプリを実行すると、次のエラーメッセージが表示されます。

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

どちらの方法もPropagation.REQUIRES_NEWです。単純な監査制御を行っているからです。

getOneメソッドは JpaRepository インターフェースで定義されており、私のRepositoryインターフェースはそこから拡張されているので、私はもちろんJPAで作業しています。

JpaRepositoryインターフェースは、 CrudRepository から拡張されています。 findOne(id)メソッドはCrudRepositoryで定義されています。

私の質問は:

  1. getOne(id)メソッドが失敗するのはなぜですか?
  2. いつgetOne(id)メソッドを使うべきですか?

私は他のリポジトリで作業していて、すべてgetOne(id)メソッドを使用していますが、Propagation.REQUIRES_NEWを使用した場合にのみ正常に機能します。

getOneAPIによると、

与えられた識別子を持つエンティティへの参照を返します。

findOneAPIによると、

エンティティをそのIDで取得します。

3)findOne(id)メソッドはいつ使うべきですか?

4)どの方法を使用することをお勧めしますか?

前もって感謝します。

125
Manuel Jordan

1。 getOne(id)メソッドが失敗するのはなぜですか?

このセクションを参照してください ドキュメント内 。すでに配置されているトランザクションを上書きすると、問題が発生する可能性があります。しかし、これ以上情報がないと答えられません。

2。 getOne(id)メソッドを使うべき時は?

Spring Data JPAの内部を掘り下げることなしに、違いは実体を検索するために使用されるメカニズムにあるようです。

の下のgetOne(ID)JavaDoc を見てください。関連項目

See Also:
EntityManager.getReference(Class, Object)

このメソッドは、JPAエンティティマネージャの実装に委譲するだけのようです。

しかし、findOne(ID)docs はこれについて言及していません。

その手がかりは、リポジトリの名前にもあります。 JpaRepositoryはJPAに固有のものであるため、必要に応じてエンティティマネージャに呼び出しを委任することができます。 CrudRepositoryは、使用されている永続化テクノロジを認識しません。 ここを見てください 。 JPAのような複数の永続化技術のためのマーカーインターフェースとして使用されています Neo4J など。

そのため、2つの方法に実際には「違い」はありません。findOne(ID)は、より特殊化されたgetOne(ID)よりも一般的なものです。どちらを使うかはあなたとあなたのプロジェクト次第ですが、私は個人的にはfindOne(ID)に固執するでしょう。

67
Donovan Muller

基本的な違いは、getOneは遅延ロードされ、findOneはロードされないことです。

次の例を見てください。

public static String NON_EXISTING_ID = -1;
...
MyEntity getEnt = myEntityRepository.getOne(NON_EXISTING_ID);
MyEntity findEnt = myEntityRepository.findOne(NON_EXISTING_ID);

if(findEnt != null) {
     findEnt.getText(); // findEnt is null - this code is not executed
}

if(getEnt != null) {
     getEnt.getText(); // Throws exception - no data found, BUT getEnt is not null!!!
}
108
Marek Halmo

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-JpafindOne()を修正しました。
以前は、CrudRepositoryインターフェイスで次のように定義されていました。

T findOne(ID primaryKey);

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

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

これは、SimpleJpaRepositoryインターフェースのデフォルト実装であるCrudRepositoryによって最終的に実装されます。
このメソッドは検索例によるクエリであり、代わりにする必要はありません。

実際、同じ動作を持つメソッドは新しいAPIにもまだ存在しますが、メソッド名は変更されています。
CrudRepository インターフェースでfindOne()からfindById()に名前が変更されました。

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) javadoc は次のように述べています。

エンティティをその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) javadoc と書かれています(強調は私のものです):

与えられた識別子を持つエンティティへの参照を返します。

実際、参照という用語は実際には専門用語であり、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がスローされます。 (getReferenceが呼び出されると、持続性プロバイダランタイムはEntityNotFoundExceptionをスローすることが許可されます。)アプリケーションは、インスタンス状態がデタッチ時に使用可能になることを期待するべきではありません、エンティティマネージャが開いている間にアプリケーションからアクセスされた場合を除きます。

そのため、getOne()を呼び出すと、遅延フェッチされたエンティティが返される可能性があります。
ここで、遅延フェッチとは、エンティティの関係ではなく、エンティティ自体のことです。

つまり、getOne()を呼び出してからPersistenceコンテキストが閉じられると、エンティティがロードされない可能性があるため、結果は実際には予測不可能です。
たとえば、プロキシオブジェクトがシリアル化されている場合は、シリアル化された結果としてnull参照を取得したり、プロキシオブジェクトに対してメソッドが呼び出された場合は、LazyInitializationExceptionなどの例外がスローされます。
このような状況では、エラー状況としてデータベースに存在しないインスタンスを処理するためにgetOne()を使用する主な理由であるEntityNotFoundExceptionのスローは、エンティティが存在しない間は決して実行されない可能性があります。

いずれにせよ、その読み込みを確実にするためには、セッションが開かれている間にエンティティを操作する必要があります。エンティティ上の任意のメソッドを呼び出すことによってそれを行うことができます。
または、より良い代替方法として、代わりにfindById(ID id)を使用してください。


なぜそれほど不明瞭なAPIなのか

最後に、Spring-Data-JPA開発者のための2つの質問

  • なぜgetOne()のためのより明確なドキュメントを持っていないのですか?エンティティの遅延ロードは実際には詳細ではありません。

  • なぜgetOne()をラップするためにEM.getReference()を導入する必要があるのですか?
    ラップされたメソッドに固執しないのはなぜですか?getReference()getOne()がとても単純な処理を伝えるのに対して、このEMメソッドはとても特別です。

87
davidxxx

getOneメソッドは、DBからの参照のみを返します(遅延ロード)。そのため、基本的にあなたはトランザクションの外側にいて(サービスクラスで宣言したTransactionalは考慮されません)、エラーが発生します。

15
Bogdan Mata

私は上記の答えから非常に難しいと思います。デバッグの観点からは、私は愚かな間違いを知るために8時間を費やしました。

私は春+休止状態+ブルドーザー+ MySQLプロジェクトをテストしています。はっきりさせるために。

ユーザーエンティティ、ブックエンティティがあります。あなたはマッピングの計算をします。

複数の書籍が1人のユーザーに関連付けられていました。しかしUserServiceImplでは、私はgetOne(userId)でそれを見つけようとしていました。

public UserDTO getById(int userId) throws Exception {

    final User user = userDao.getOne(userId);

    if (user == null) {
        throw new ServiceException("User not found", HttpStatus.NOT_FOUND);
    }
    userDto = mapEntityToDto.transformBO(user, UserDTO.class);

    return userDto;
}

その他の結果は

{
"collection": {
    "version": "1.0",
    "data": {
        "id": 1,
        "name": "TEST_ME",
        "bookList": null
    },
    "error": null,
    "statusCode": 200
},
"booleanStatus": null

}

上記のコードでは、ユーザーが読んだ本を取得しませんでした。

GetList(ID)のため、bookListは常にnullでした。 findOne(ID)に変更した後結果は

{
"collection": {
    "version": "1.0",
    "data": {
        "id": 0,
        "name": "Annama",
        "bookList": [
            {
                "id": 2,
                "book_no": "The karma of searching",
            }
        ]
    },
    "error": null,
    "statusCode": 200
},
"booleanStatus": null

}

1
Tot