Comment utilisez-vous JPA Spring Data en dehors d’un conteneur Spring?

J’essaie de connecter manuellement des objects JPA Spring Data afin de pouvoir générer des proxys DAO (aussi appelés repositorys) – sans utiliser de conteneur Spring Bean.

Inévitablement, on me demandera pourquoi je veux faire cela: c’est parce que notre projet utilise déjà Google Guice (et sur l’interface utilisateur utilisant Gin avec GWT), et que nous ne voulons pas conserver une autre configuration de conteneur IoC, ou toutes les dépendances résultantes. Je sais que nous pourrions utiliser SpringIntegration de Guice, mais ce serait un dernier recours.

Il semble que tout soit disponible pour relier les objects manuellement, mais comme ce n’est pas bien documenté, j’ai de la difficulté.

Selon le guide de l’utilisateur de Spring Data, il est possible d’utiliser des usines de référentiels en mode autonome . Malheureusement, l’exemple montre RepositoryFactorySupport qui est une classe abstraite. Après quelques recherches, j’ai réussi à trouver JpaRepositoryFactory

JpaRepositoryFactory fonctionne plutôt bien, sauf qu’il ne crée pas automatiquement de transactions. Les transactions doivent être gérées manuellement, sinon rien ne persistera dans la firebase database:

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

Le problème s’est avéré être que les annotations @Transactional ne sont pas utilisées automatiquement et nécessitent l’aide d’un TransactionInterceptor

Heureusement, JpaRepositoryFactory peut prendre un rappel pour append plus de conseils AOP au proxy de référentiel généré avant de retourner:

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

C’est là que les choses ne vont pas si bien. En parcourant le débogueur dans le code, le TransactionInterceptor crée en effet une transaction – mais sur le mauvais EntityManager . Spring gère le EntityManager actif en examinant le thread en cours d’exécution. Le TransactionInterceptor fait et voit qu’il n’y a pas de EntityManager actif lié au thread et décide d’en créer un nouveau.

Cependant, ce nouvel EntityManager n’est pas la même instance qui a été créée et transmise au constructeur JpaRepositoryFactory , qui nécessite un EntityManager . La question est de savoir comment faire en sorte que TransactionInterceptor et JpaRepositoryFactory utilisent le même EntityManager .

Mettre à jour:

En écrivant cela, j’ai découvert comment résoudre le problème mais ce n’est peut-être pas la solution idéale. Je posterai cette solution comme une réponse séparée. Je serais heureux d’entendre des suggestions sur une meilleure façon d’utiliser la solution autonome JPA de Spring Data que de la résoudre.

Le principe général de la conception de JpaRepositoryFactory et de l’intégration de Spring selon JpaRepositoryFactory bean JpaRepositoryFactory est le suivant:

Nous supposons que vous exécutez votre application dans un environnement d’exécution JPA géré , sans tenir compte de celui-ci.

C’est la raison pour laquelle nous nous appuyons sur EntityManager injecté plutôt que sur EntityManagerFactory . Par définition, EntityManager n’est pas thread-safe. Donc, si vous EntityManagerFactory directement un EntityManagerFactory nous devrions réécrire tout le code de gestion des ressources qu’un environnement d’exécution géré (comme Spring ou EJB) vous fournirait.

Pour s’intégrer à la gestion des transactions Spring, nous utilisons SharedEntityManagerCreator de Spring qui SharedEntityManagerCreator la magie de liaison des ressources de transaction que vous avez implémentée manuellement. Vous voulez probablement utiliser celui-ci pour créer des instances EntityManager partir de votre EntityManagerFactory . Si vous souhaitez activer directement la transactionalité sur le beans du référentiel (de sorte qu’un appel à, par exemple, repo.save(…) crée une transaction si aucune n’est déjà active), consultez l’implémentation TransactionalRepositoryProxyPostProcessor dans Spring Data Commons. Il active en fait les transactions lorsque les référentiels Spring Data sont utilisés directement (par exemple pour repo.save(…) ) et personnalise légèrement la recherche de configuration de transaction pour préférer les interfaces aux classes d’implémentation pour autoriser les interfaces de référentiel à remplacer la configuration de transaction définie dans SimpleJpaRepository .

J’ai résolu ce problème en liant manuellement EntityManager et EntityManagerFactory au thread d’exécution avant de créer des référentiels avec JpaRepositoryFactory . Ceci est accompli en utilisant la méthode 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 MatchAlwaysTransactionAtsortingbuteSource())); } }); // 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()); } 

Il doit y avoir un meilleur moyen cependant. Il semble étrange que RepositoryFactory ait été conçu pour utiliser EnitiyManager au lieu de EntityManagerFactory . Je m’attendrais à ce qu’il regarde d’abord pour voir si un EntityManger est lié au thread et ensuite créer un nouveau et le lier, ou utiliser un existant.

Fondamentalement, je voudrais injecter les proxys du référentiel, et j’attends à chaque appel qu’ils créent en interne un nouvel EntityManager , afin que les appels soient sécurisés pour les threads.