Search code examples
javaspring-dataspring-data-jpaeclipselinkcdi

Spring Data JPA CDI integration with multiple persistence units


I am new to Spring and trying to integrate Spring Data, EclipseLink, and EJB on Weblogic 12c.

I want to use CDI to inject a Spring Data Repository into a stateless EJB so I followed the Spring Data CDI integration instruction and succeeded with single persistence unit.

https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpd.misc.cdi-integration

As the application requires two persistence units to connect two different databases, I configured two persistence units with a different name in persistence.xml.

Here comes the question: How can I create two Spring Data repository so that RepositoryA uses persistence Unit A and RepositoryB uses persistence Unit B?

persistence.xml

<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
             http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
             version="2.1">
    <persistence-unit name="PRIMARY_PU" transaction-type="JTA">
        <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
        <jta-data-source>jdbc/EMP_DS</jta-data-source>
        <class>com.smec.eis.example.springbooteval.model.Employee</class>
        <exclude-unlisted-classes>true</exclude-unlisted-classes>
        <shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
    </persistence-unit>
    <persistence-unit name="SECONDARY_PU" transaction-type="JTA">
        <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
        <jta-data-source>jdbc/HR_DS</jta-data-source>
        <class>com.smec.eis.example.springbooteval.model.Job</class>
        <exclude-unlisted-classes>true</exclude-unlisted-classes>
        <shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
    </persistence-unit>
</persistence>

Primary CDI Producer:

public class EntityManagerFactoryProducer {

    @Produces
    @ApplicationScoped
    public EntityManagerFactory createEntityManagerFactory() {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("PRIMARY_PU");
        return emf;
    }

    public void close(@Disposes EntityManagerFactory entityManagerFactory) {
        entityManagerFactory.close();
    }

    @Produces
    @Dependent
    public EntityManager createEntityManager(EntityManagerFactory entityManagerFactory) {
        return entityManagerFactory.createEntityManager();
    }

    public void close(@Disposes EntityManager entityManager) {
        entityManager.close();
    }
}

Solution

  • TL; DR;

    Use qualifiers to declare which repository should use which EntityManager.

    Explanation

    Spring Data JPA repositories are implemented by default on a single EntityManager. The CDI extension propagates any qualifiers from the repository interface to its EntityManager selection. Because the qualifiers are effectively empty (not counting in @Default and @Any), the extension uses the single EntityManager from your code above.

    Creating and adding own qualifier annotations will do the job for you:

    Qualifiers

    @Qualifier
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER })
    @interface MyFirstDatabase {
    
    }
    
    @Qualifier
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER })
    @interface MySecondDatabase {
    
    }
    

    Repository interfaces

    @MyFirstDatabase
    public interface SomeRepository extends CrudRepository<MyEntity, Long> { … }
    
    @MySecondDatabase
    public interface SomeOtherRepository extends CrudRepository<OtherEntity, Long> { … }
    

    Client-side usage interfaces

    public class MyComponent {
    
        @Inject
        @MyFirstDatabase 
        SomeRepository someRepo;
    
        @Inject    
        @MySecondDatabase 
        SomeOtherRepository someOtherRepo;
    }
    

    Your EntityManagerFactoryProducer:

    public class EntityManagerFactoryProducer {
    
        @Produces
        @ApplicationScoped
        @MyFirstDatabase
        public EntityManagerFactory createEntityManagerFactory() {
            EntityManagerFactory emf = Persistence.createEntityManagerFactory("PRIMARY_PU");
            return emf;
        }
    
        @Produces
        @ApplicationScoped
        @MySecondDatabase
        public EntityManagerFactory createEntityManagerFactory() {
            EntityManagerFactory emf = Persistence.createEntityManagerFactory("SECONDARY_PU");
            return emf;
        }
    
        public void close(@Disposes EntityManagerFactory entityManagerFactory) {
            entityManagerFactory.close();
        }
    
        @Produces
        @Dependent
        @MyFirstDatabase
        public EntityManager createEntityManager(@MyFirstDatabase EntityManagerFactory entityManagerFactory) {
            return entityManagerFactory.createEntityManager();
        }
    
        @Produces
        @Dependent
        @MySecondDatabase
        public EntityManager createEntityManager(@MySecondDatabase EntityManagerFactory entityManagerFactory) {
            return entityManagerFactory.createEntityManager();
        }
    
        public void close(@Disposes EntityManager entityManager) {
            entityManager.close();
        }
    }
    

    The code above assumes that you work with entity types that are not the same across your two data sources. If you need to use the same entity type, then you would create a base repository interface, annotate it with @NoRepositoryBean and two derived interfaces, similar to the code above.