web-dev-qa-db-ja.com

Spring Boot Data JPA-更新クエリの変更-永続コンテキストの更新

Spring Boot 1.3.0.M4とMySQLデータベースを使用しています。

変更クエリを使用すると問題が発生します。クエリが実行された後、EntityManagerに古いエンティティが含まれています。

元のJPAリポジトリ:

_public interface EmailRepository extends JpaRepository<Email, Long> {

    @Transactional
    @Modifying
    @Query("update Email e set e.active = false where e.active = true and e.expire <= NOW()")
    Integer deactivateByExpired();

}
_

DBにEmail [id = 1、active = true、expire = 2015/01/01]があるとします。

実行後:

_emailRepository.save(email);
emailRepository.deactivateByExpired();
System.out.println(emailRepository.findOne(1L).isActive()); // prints true!! it should print false
_

問題を解決する最初のアプローチ:add clearAutomatically = true

_public interface EmailRepository extends JpaRepository<Email, Long> {

    @Transactional
    @Modifying(clearAutomatically = true)
    @Query("update Email e set e.active = false where e.active = true and e.expire <= NOW()")
    Integer deactivateByExpired();

}
_

このアプローチでは、永続コンテキストをクリアして古い値を持たないようにしますが、EntityManagerで保留中のフラッシュされていないすべての変更を削除します。私はsave()メソッドのみを使用し、saveAndFlush()他のエンティティの一部の変更は失われます:(


問題を解決する2番目のアプローチ:リポジトリのカスタム実装

_public interface EmailRepository extends JpaRepository<Email, Long>, EmailRepositoryCustom {

}

public interface EmailRepositoryCustom {

    Integer deactivateByExpired();

}

public class EmailRepositoryImpl implements EmailRepositoryCustom {

    @PersistenceContext
    private EntityManager entityManager;

    @Transactional
    @Override
    public Integer deactivateByExpired() {
        String hsql = "update Email e set e.active = false where e.active = true and e.expire <= NOW()";
        Query query = entityManager.createQuery(hsql);
        entityManager.flush();
        Integer result = query.executeUpdate();
        entityManager.clear();
        return result;
    }

}
_

このアプローチは@Modifying(clearAutomatically = true)と同様に機能しますが、更新を実行する前にEntityManagerにすべての変更を強制的にDBにフラッシュさせ、次に永続コンテキストをクリアします。これにより、古いエンティティがなくなり、すべての変更がDBに保存されます。


古いエンティティの問題やDBへの手動フラッシュを行わずに、JPAでupdateステートメントを実行するより良い方法があるかどうかを知りたいです。おそらく2次キャッシュを無効にしますか? Spring Bootでどうすればいいですか?


2018年に更新

Spring Data JPAは私のPRを承認しました。@Modifying()にはflushAutomaticallyオプションがあります。

_@Modifying(flushAutomatically = true, clearAutomatically = true)
_
47
ciri-cuervo

これはあなたの質問に対する直接的な答えではないことを知っています。あなたはすでに修正を作成し、Githubでプルリクエストを開始しているからです。有難うございます!

しかし、私はあなたが行くことができるJPAの方法を説明したいと思います。そのため、特定の基準に一致するすべてのエンティティを変更し、それぞれの値を更新する必要があります。通常のアプローチは、必要なすべてのエンティティをロードすることです。

_@Query("SELECT * FROM Email e where e.active = true and e.expire <= NOW()")
List<Email> findExpired();
_

次に、それらを反復処理し、値を更新します。

_for (Email email : findExpired()) {
  email.setActive(false);
}
_

これでhibernateはすべての変更を認識し、トランザクションが完了した場合、またはEntityManager.flush()を手動で呼び出した場合、それらをデータベースに書き込みます。すべてのエンティティをメモリにロードするため、大量のデータエントリがある場合、これはうまく機能しないことがわかっています。ただし、これは、Hibernateエンティティキャッシュ、2次レベルキャッシュ、およびデータベースの同期を維持するための最良の方法です。

この回答は、「@ Modifying」アノテーションは役に立たないと言っていますか?いや!変更したエンティティがローカルキャッシュにないことを確認した場合、たとえば書き込み専用アプリケーションの場合、このアプローチはそのままです。

記録のためだけに、リポジトリメソッドに_@Transactional_は必要ありません。

レコードv2の場合のみ、active列はexpireに直接依存しているように見えます。それでは、すべてのクエリでactiveを完全に削除し、expireだけを見てみませんか?

8
Johannes Leimer

Klaus-groenbaekが言ったように、EntityManagerを注入し、そのrefreshメソッドを使用できます。

@Inject
EntityManager entityManager;

...

emailRepository.save(email);
emailRepository.deactivateByExpired();
Email email2 = emailRepository.findOne(1L);
entityManager.refresh(email2);
System.out.println(email2.isActive()); // prints false
1
Eric Bonnot