Spring Data JPA: Repositories for multiple database / Entitymanger configurations

simou picture simou · May 14, 2013 · Viewed 21.4k times · Source

I have two Entitymanager bean configurations. Each pointing to a separate database with a different schema (one is Oracle, the other one is an in-memory H2)

What could I do to solve the ambiguity of what Entitymanager should be used for each Repository? Right now I'm getting this error:

 No unique bean of type [javax.persistence.EntityManagerFactory] is defined:
 expected single bean but found 2

I guess I could provide a quick-fix simply by using something like

<jpa:repositories base-package="com.foo.repos.ora"
 entity-manager-factory-ref="entityManagerFactoryA">

<jpa:repositories base-package="com.foo.repos.m2"
 entity-manager-factory-ref="entityManagerFactoryB">

But hopefully there is a better solution.

EDIT:

I give you an idea of the current scenario:

Spring-Config: there're two EM

<jpa:repositories base-package="com.foo.repos.ora" entity-manager-factory-ref="entityManagerFactory"/>
<jpa:repositories base-package="com.foo.repos.m2" entity-manager-factory-ref="entityManagerFactory2"/>
<context:component-scan base-package="com.foo" />  ....

Everything from here on is in "package com.foo.repos.ora" Following the pattern of how to make a custom repository I get two interfaces 'ARepository', 'ARepositoryCustom' and its implementation 'ARepositoryImpl' like so

@Repository
public interface ARepository extends ARepositoryCustom, JpaRepository<myEntity, BigDecimal>, QueryDslPredicateExecutor {

}

public interface ARepositoryCustom {
    FooBar lookupFooBar()
}

public class ARepositoryImpl extends QueryDslRepositorySupport implements ARepositoryCustom {
    ARepositoryImpl(Class<?> domainClass) {
        super(domainClass.class)
    }

    ARepositoryImpl() {
        this(myEntity.class)
    }

    @Override
    FooBar lookupFooBar() {
        JPQLQuery query = ....
        ....
        return found
    }
}

resulting in the following error message:

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'aRepositoryImpl': Injection of persistence dependencies failed; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [javax.persistence.EntityManagerFactory] is defined: expected single bean but found 2

Which is of course correct, there are 2 EM beans, but since I restricted EM #1 aka 'entityManagerFactory' to package 'com.foo.repos.ora' only, I'm still not sure how to reference the exact EM bean.

Answer

simou picture simou · May 17, 2013

There is no magic under the hood.

<jpa:repositories base-package="com.foo.repos.ora" entity-manager-factory-ref="entityManagerFactory"/>

doesn't help you at all with your custom interface implementations. Best way I found is to treat your custom implementations as regular beans. So I defined a 'sharedEntitManager' bean in my spring configuration like so

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
       ...
</bean>
<bean id="sharedEntityManager" class="org.springframework.orm.jpa.support.SharedEntityManagerBean">
        <property name = "entityManagerFactory" ref="entityManagerFactory"/>
</bean>

After that, I simply injected the EntityManager into my implementation beans

<bean id="aRepositoryImpl" class="comm.foo.repos.ora.ARepositoryImpl">
    <property name="entityManager" ref="sharedEntityManager"/>
</bean>

The 'entity-manager-factory-ref' attribute discriminates between different entitymanager factories but only for straight Spring Data Repositories (i.e. only for interfaces). It doesn't however concern itself with any of your implementations.

To sum it up

1) if you simply rely on standard Spring Data repositories with no custom implementation, use the "entity-manager-factory-ref" attribute to differentiate databases.

2a) Additionally, if you use any custom implementation, inject the appropriate EntityManager directly into the implementing class. Wirering is done under control of your spring xml configuration. For some reason I wasn't able to use the @Autowire annotation with a @Qualifier to reference the correct EntityManager. EDIT I just learned about the @Resource annotation

@Resource(name =  "sharedEntityManagerA")
EntityManager entityManager


<bean id="sharedEntityManagerA" name="sharedEntityManagerA" class="org.springframework.orm.jpa.support.SharedEntityManagerBean">
        <property name = "entityManagerFactory" ref="entityManagerFactory"/>
</bean>

With this at hand selecting what EntityMAnger should be used becomes straightforward. No need of plumbing everything togehther in your context xml.

2b) As an alternative to Spring's xml configuration for hooking up your stuff you may also go with

@PersistenceContext( unitName = "nameOfPersistenceUnit" )

to inject the correct EntitymanagerFactory

While 'nameOfPersistenceUnit' referes to your persistence sitting in your standard JPA persistence.xml

However 2b) doesn't go well with 'QueryDslRepositorySupport', since it expects an EntityManager instance. But I found that 'QueryDslRepositorySupport' doesn't offer much support anyway, so I removed it.