web-dev-qa-db-ja.com

Spring Container外でSpring Data JPAをどのように使用しますか?

Spring Beanコンテナーを使用せずにDAOプロキシー(別名リポジトリー)を生成できるように、Spring Data JPAオブジェクトを手動で接続しようとしています。

必然的に、なぜこれを実行するのかを尋ねられます。これは、プロジェクトが既にGoogle Guice(およびGWTでGinを使用しているUI)を使用しており、別のIoCコンテナー構成を維持したり、プルインしたりしないためです。結果のすべての依存関係。 GuiceのSpringIntegrationを使用できるかもしれませんが、これは最後の手段です。

オブジェクトを手動で接続するためにすべてが利用可能であるようですが、十分に文書化されていないため、私は困難な状況にあります。

Spring Dataユーザーズガイドによると、 リポジトリファクトリースタンドアロン の使用が可能です。残念ながら、この例は、抽象クラスであるRepositoryFactorySupportを示しています。いくつか検索した後、なんとか JpaRepositoryFactory が見つかりました

JpaRepositoryFactoryは、トランザクションを自動的に作成しないことを除いて、実際にはかなりうまく機能します。トランザクションは手動で管理する必要があります。そうしないと、データベースに何も永続化されません。

entityManager.getTransaction().begin();
repositoryInstance.save(someJpaObject);
entityManager.getTransaction().commit();

問題はそれであることが判明しました@Transactionalアノテーションは自動的には使用されず、TransactionInterceptorの助けが必要です

ありがたいことに、JpaRepositoryFactoryは、返される前に、生成されたリポジトリプロキシにAOPアドバイスを追加するためのコールバックを取ることができます。

final JpaTransactionManager xactManager = new JpaTransactionManager(emf);
final JpaRepositoryFactory factory = new JpaRepositoryFactory(emf.createEntityManager());

factory.addRepositoryProxyPostProcessor(new RepositoryProxyPostProcessor() {
    @Override
    public void postProcess(ProxyFactory factory) {
        factory.addAdvice(new TransactionInterceptor(xactManager, new AnnotationTransactionAttributeSource()));
    }
});

これは、物事がうまく機能していない場所です。コードでデバッガーをステップ実行すると、TransactionInterceptorは実際にトランザクションを作成していますが、間違ったEntityManagerを使用しています。 Springは、現在実行中のスレッドを調べることにより、アクティブなEntityManagerを管理します。 TransactionInterceptorはこれを行い、スレッドにバインドされているアクティブなEntityManagerがないことを確認し、新しいスレッドを作成することを決定します。

ただし、この新しいEntityManagerは、作成されてJpaRepositoryFactoryコンストラクターに渡されたインスタンスとは異なります。これには、EntityManagerが必要です。問題は、TransactionInterceptorJpaRepositoryFactoryで同じEntityManagerを使用するにはどうすればよいですか?

更新:

これを書いている間に問題を解決する方法を見つけましたが、それでもまだ理想的な解決策ではないかもしれません。このソリューションを別の回答として投稿します。 Spring Data JPAをスタンドアロンで使用するより良い方法について、私がそれを解決する方法よりも提案があれば喜んで教えてください。

46
codemaven

JpaRepositoryFactoryおよび対応するSpring統合JpaRepositoryFactory Beanの設計の背後にある一般的な原則は次のとおりです。

managed JPAランタイム環境内でアプリケーションを実行することを想定しています。

これが、EntityManagerではなく、挿入されたEntityManagerFactoryに依存している理由です。定義により、EntityManagerはスレッドセーフではありません。したがって、EntityManagerFactoryを直接処理する場合、マネージドランタイム環境(SpringまたはEJBと同様)が提供するすべてのリソース管理コードを書き換える必要があります。

Springトランザクション管理と統合するには、SpringのSharedEntityManagerCreatorを使用します。これは、手動で実装したトランザクションリソースバインディングマジックを実際に実行します。そのため、おそらくそのインスタンスを使用して、EntityManagerからEntityManagerFactoryインスタンスを作成する必要があります。リポジトリBeanでトランザクション性を直接アクティブ化する場合(たとえば、repo.save(…)を呼び出すと、トランザクションがまだアクティブでない場合はトランザクションが作成されます)、Spring Data CommonsのTransactionalRepositoryProxyPostProcessor実装を確認してください。これは、Spring Dataリポジトリが直接使用されたときにトランザクションを実際にアクティブ化し(repo.save(…)などの場合)、トランザクション構成ルックアップをわずかにカスタマイズして、リポジトリインターフェースがSimpleJpaRepositoryで定義されたトランザクション構成をオーバーライドできるように、実装クラスよりもインターフェースを優先します。

24
Oliver Drotbohm

EntityManagerでリポジトリを作成する前に、EntityManagerFactoryJpaRepositoryFactoryを実行中のスレッドに手動でバインドすることでこれを解決しました。これは、TransactionSynchronizationManager.bindResource 方法:

emf = Persistence.createEntityManagerFactory("com.foo.model", properties);
em = emf.createEntityManager();

// Create your transaction manager and RespositoryFactory
final JpaTransactionManager xactManager = new JpaTransactionManager(emf);
final JpaRepositoryFactory factory = new JpaRepositoryFactory(em);

// Make sure calls to the repository instance are intercepted for annotated transactions
factory.addRepositoryProxyPostProcessor(new RepositoryProxyPostProcessor() {
    @Override
    public void postProcess(ProxyFactory factory) {
        factory.addAdvice(new TransactionInterceptor(xactManager, new MatchAlwaysTransactionAttributeSource()));
    }
});

// Create your repository proxy instance
FooRepository repository = factory.getRepository(FooRepository.class);

// Bind the same EntityManger used to create the Repository to the thread
TransactionSynchronizationManager.bindResource(emf, new EntityManagerHolder(em));

try{
    repository.save(someInstance); // Done in a transaction using 1 EntityManger
} finally {
    // Make sure to unbind when done with the repository instance
    TransactionSynchronizationManager.unbindResource(getEntityManagerFactory());
}

しかし、もっと良い方法があるはずです。 RepositoryFactoryがEnitiyManagerではなくEntityManagerFactoryを使用するように設計されているのは奇妙に思われます。私は、EntityMangerがスレッドにバインドされているかどうかを最初に確認してから、新しいスレッドを作成してバインドするか、既存のスレッドを使用することを期待しています。

基本的に、私はリポジトリプロキシを挿入し、すべての呼び出しで内部的に新しいEntityManagerを作成して、呼び出しがスレッドセーフになることを期待します。

12
codemaven