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


154

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

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

観察し@TransactionalているPropagation.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インターフェースから延びCrudRepositoryfindOne(id)方法は、で定義されていますCrudRepository

私の質問は:

  1. なぜgetOne(id)メソッドが失敗するのですか?
  2. いつgetOne(id)メソッドを使用する必要がありますか?

私は他のリポジトリを使用していますが、すべてこのgetOne(id)方法を使用しており、すべて正常に機能します。Propagation.REQUIRES_NEWを使用した場合にのみ失敗します。

getOne APIによると:

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

findOne APIによると:

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

3)いつfindOne(id)メソッドを使用する必要がありますか?

4)どの方法を使用することが推奨されますか?

前もって感謝します。


getOneでは常にオブジェクト!= nullを取得しますが、findOneではnullを提供するため、データベース内のオブジェクトの存在をテストするために特にgetOne()を使用しないでください。
Uwe Allner 2014

回答:


137

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

さて、findOne()あなたが見つける単一のメソッドは、次のようCrudRepositoryQueryByExampleExecutorインターフェースで定義されたものです:

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

これはSimpleJpaRepositoryCrudRepositoryインターフェースのデフォルト実装であるによって最終的に実装されます。
このメソッドは検索例によるクエリであり、代わりに使用したくない。

実際、同じ動作のメソッドはまだ新しい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)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);

そして幸いにも、EntityManagerjavadocはその意図をより明確に定義しています(強調は私のものです):

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

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

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

いずれの場合でも、その読み込みを保証するには、セッションが開いている間にエンティティを操作する必要があります。エンティティの任意のメソッドを呼び出すことで実行できます。
または、findById(ID id)代わりのより良い代替使用法。


なぜそれほどあいまいなAPIですか?

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

  • より明確なドキュメントがないのはなぜgetOne()ですか?エンティティの遅延読み込みは実際には詳細ではありません。

  • なぜgetOne()ラップを導入する必要があるのEM.getReference()ですか?
    なぜラップされたメソッドに固執しないのgetReference()ですか?このEM手法は非常に特殊でありgetOne() 、処理は非常に単純です。


3
getOne()がEntityNotFoundExceptionをスローしないのはなぜか混乱しましたが、「インスタンスの状態に最初にアクセスしたときにEntityNotFoundExceptionがスローされる」という概念で説明しました。ありがとう
TheCoder 2018年

この回答の概要:getOne()遅延読み込みを使用しEntityNotFoundException、アイテムが見つからない場合はをスローします。findById()すぐにロードし、見つからない場合はnullを返します。getOne()には予測できない状況がいくつかあるため、代わりにfindById()を使用することをお勧めします。
Janac Meena

124

基本的な違いは、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!!!
}

1
遅延読み込みは、エンティティが使用されるときにのみ読み込まれることを意味しませんか?だから私はgetEntがnullであり、2番目のコードが実行されない場合は実行されないことを期待します。ありがとう!
Doug

CompletableFuture <> Webサービス内にラップされている場合、遅延実装のため、findOne()とgetOne()を使用する必要があることがわかりました。
Fratt

76

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

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

2. getOne(id)メソッドをいつ使用する必要がありますか?

Spring Data JPAの内部を掘り下げることなく、エンティティを取得するために使用されるメカニズムに違いがあるようです。

あなたは見ればJavaDocのためgetOne(ID)の下で関連項目

See Also:
EntityManager.getReference(Class, Object)

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

ただし、のドキュメントfindOne(ID)はこれについて言及していません。

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

したがって、ユースケースの2つの方法に実際の「違い」はありません。findOne(ID)それは、より特殊化されたものよりも一般的であるということだけですgetOne(ID)。どちらを使用するかはあなたとあなたのプロジェクト次第ですfindOne(ID)が、コードを実装固有にする必要が少なくなり、将来、リファクタリングをあまり行わずにMongoDBなどに移動するための扉が開かれるので、私は個人的にに固執します。


ドノバン、ありがとうございます。
マヌエルジョーダン

20
there's not really a 'difference' in the two methodsエンティティの取得方法と、メソッドが返すことを期待する必要があるものには実際に大きな違いがあるため、ここでそれを言うのは非常に誤解を招くと思います。@davidxxxでさらに下にある答えは、これを非常によく強調しており、Spring Data JPAを使用するすべての人がこれに注意する必要があると思います。そうでなければ、それはかなりの頭痛を引き起こす可能性があります。
fridberg

16

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


EntityManager.getReference(Class、Object)は、新しいTransactionスコープにいるため、「nothing」を返しているようです。
マヌエルジョーダン

2

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

spring + hibernate + dozer + 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

}

上記のコードはユーザーが言う本をフェッチしませんでした。

getOne(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

spring.jpa.open-in-viewはtrueでしたが、getOneには何の問題もありませんでしたが、falseに設定した後、LazyInitializationExceptionが発生しました。その後、findByIdに置き換えることで問題が解決しました。
getOneメソッドを置き換えない別の解決策がありますが、それは、repository.getOne(id)を呼び出すメソッドに@Transactionalに配置されます。このようにして、トランザクションは存在し、セッションはメソッドで閉じられません。エンティティを使用している間は、LazyInitializationExceptionは発生しません。


-2

JpaRespository.getOne(id)が機能せずエラーをスローする理由を理解する同様の問題がありました。

私は行ってJpaRespository.findById(id)に変更します。これはオプションを返す必要があります。

これはおそらくStackOverflowに関する私の最初のコメントです。


残念ながら、これは質問を提供および回答するものではなく、既存の回答を改善するものでもありません。
JSTL 2018

わかりました、問題ありません。
akshaymittal143 2018
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.