I'm trying to wire up Spring Data JPA objects manually so that I can generate DAO proxies (aka Repositories) - without using a Spring bean container.
Inevitably, I will be asked why I want to do this: it is because our project is already using Google Guice (and on the UI using Gin with GWT), and we don't want to maintain another IoC container configuration, or pull in all the resulting dependencies. I know we might be able to use Guice's SpringIntegration
, but this would be a last resort.
It seems that everything is available to wire the objects up manually, but since it's not well documented, I'm having a difficult time.
According to the Spring Data user's guide, using repository factories standalone is possible. Unfortunately, the example shows RepositoryFactorySupport
which is an abstract class. After some searching I managed to find JpaRepositoryFactory
JpaRepositoryFactory
actually works fairly well, except it does not automatically create transactions. Transactions must be managed manually, or nothing will get persisted to the database:
entityManager.getTransaction().begin();
repositoryInstance.save(someJpaObject);
entityManager.getTransaction().commit();
The problem turned out to be that @Transactional
annotations are not used automatically, and need the help of a TransactionInterceptor
Thankfully, the JpaRepositoryFactory
can take a callback to add more AOP advice to the generated Repository proxy before returning:
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()));
}
});
This is where things are not working out so well. Stepping through the debugger in the code, the TransactionInterceptor
is indeed creating a transaction - but on the wrong EntityManager
. Spring manages the active EntityManager
by looking at the currently executing thread. The TransactionInterceptor
does this and sees there is no active EntityManager
bound to the thread, and decides to create a new one.
However, this new EntityManager
is not the same instance that was created and passed into the JpaRepositoryFactory
constructor, which requires an EntityManager
. The question is, how do I make the TransactionInterceptor
and the JpaRepositoryFactory
use the same EntityManager
?
Update:
While writing this up, I found out how to solve the problem but it still may not be the ideal solution. I will post this solution as a separate answer. I would be happy to hear any suggestions on a better way to use Spring Data JPA standalone than how I've solve it.
I solved this by manually binding the
EntityManager
andEntityManagerFactory
to the executing thread, before creating repositories with theJpaRepositoryFactory
. This is accomplished using theTransactionSynchronizationManager.bindResource
method:There must be be a better way though. It seems strange that the RepositoryFactory was designed to use
EnitiyManager
instead of anEntityManagerFactory
. I would expect, that it would first look to see if anEntityManger
is bound to the thread and then either create a new one and bind it, or use an existing one.Basically, I would want to inject the repository proxies, and expect on every call they internally create a new
EntityManager
, so that calls are thread safe.The general principle behind the design of
JpaRepositoryFactory
and the according Spring integrationJpaRepositoryFactory
bean is the following:That's the reason we rely on injected
EntityManager
rather than anEntityManagerFactory
. By definition theEntityManager
is not thread safe. So if dealt with anEntityManagerFactory
directly we would have to rewrite all the resource managing code a managed runtime environment (just like Spring or EJB) would provide you.To integrate with the Spring transaction management we use Spring's
SharedEntityManagerCreator
that actually does the transaction resource binding magic you've implemented manually. So you probably want to use that one to createEntityManager
instances from yourEntityManagerFactory
. If you want to activate the transactionality at the repository beans directly (so that a call to e.g.repo.save(…)
creates a transaction if none is already active) have a look at theTransactionalRepositoryProxyPostProcessor
implementation in Spring Data Commons. It actually activates transactions when Spring Data repositories are used directly (e.g. forrepo.save(…)
) and slightly customizes the transaction configuration lookup to prefer interfaces over implementation classes to allow repository interfaces to override transaction configuration defined inSimpleJpaRepository
.