Search code examples
spring-bootjpaspring-data-jpaentitymanager

NoUniqueBeanDefinitionException when setting up multiple JPA datasources


I've followed many tutorials and reviewed many answers (in SO and other sites), and I'm quite confident I didn't miss anything, but I can't get two datasources/EntityManagers to work in my environment.

This is what I have

DbADataSourceConfig.java

package com.myorg.rest.config.dba;

import ...

@Configuration
// @EnableJpaRepositories(
//     basePackages = "com.myorg.rest.dao.dbA",
//     entityManagerFactoryRef = "dbAEntityManager",
//     transactionManagerRef = "dbATransactionManager"
//     )
@EnableTransactionManagement
public class DbADataSourceConfig {

    @Autowired
    private EnvProperties settings;

    @Bean
    @Primary
    public DataSource prjDataSource() {
        DataSourceProperties ds = settings.getdbADatasource();
        return ds.initializeDataSourceBuilder().type(BasicDataSource.class).build();
    }

    @Bean(name = "dbAEntityManager")
    @Primary
    public LocalContainerEntityManagerFactoryBean dbAEntityManager() {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(prjDataSource());
        em.setPackagesToScan(new String[] { "com.myorg.model.entities.dba" });
        em.setPersistenceUnitName("dbAUnit");

        JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);
        em.setJpaProperties(additionalProperties());
        // em.afterPropertiesSet();

        return em;
    }

    @Bean(name = "dbATransactionManager")
    @Primary
    public PlatformTransactionManager dbATransactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(dbAEntityManager().getObject());

        return transactionManager;
    }

    Properties additionalProperties() {
        Properties properties = new Properties();
        properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
        return properties;
    }
}

DbBDataSourceConfig.java

package com.myorg.rest.config.dbb;

import ...

@Configuration
// @EnableJpaRepositories(
//     basePackages = "com.myorg.rest.dao.dbB",
//     entityManagerFactoryRef = "dbBEntityManager",
//     transactionManagerRef = "dbBTransactionManager"
//     )
@EnableTransactionManagement
public class DbBDataSourceConfig {

    @Autowired
    private EnvProperties settings;

    @Bean
    @Primary
    public DataSource prjDataSource() {
        DataSourceProperties ds = settings.getdbBDatasource();
        return ds.initializeDataSourceBuilder().type(BasicDataSource.class).build();
    }

    @Bean(name = "dbBEntityManager")
    @Primary
    public LocalContainerEntityManagerFactoryBean dbBEntityManager() {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(prjDataSource());
        em.setPackagesToScan(new String[] { "com.myorg.model.entities.dbb" });
        em.setPersistenceUnitName("dbBUnit");

        JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);
        em.setJpaProperties(additionalProperties());
        // em.afterPropertiesSet();

        return em;
    }

    @Bean(name = "dbBTransactionManager")
    @Primary
    public PlatformTransactionManager dbBTransactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(dbBEntityManager().getObject());

        return transactionManager;
    }

    Properties additionalProperties() {
        Properties properties = new Properties();
        properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
        return properties;
    }
}

AbstractDBADAO.java

package com.myorg.rest.dao.dba.base;

public abstract class AbstractDBADAO<T extends Serializable> implements IAbstractJpaDAO<T> {

    private Class<T> clazz;

    @PersistenceContext(unitName = "dbAUnit")
    private EntityManager entityManager;

    public AbstractDBADAO(Class<T> clazzToSet) {
        this.clazz = clazzToSet;
    }

    public T findOne(String id) {
        return entityManager.find(clazz, id);
    }

    public List<T> findAll() {
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery<T> entityQuery = criteriaBuilder.createQuery(clazz);
        entityQuery.from(clazz);
        return entityManager.createQuery(entityQuery).getResultList();
    }

    public void create(T entity) {
        entityManager.persist(entity);
    }

    public T update(T entity) {
        return entityManager.merge(entity);
    }

    public void delete(T entity) {
        entityManager.remove(entity);
    }

    public void deleteById(String entityId) {
        T entity = findOne(entityId);
        delete(entity);
    }
}

and last IAbstractJpaDAO.java

package com.myorg.rest.dao.dba.base;

import ...

public interface IAbstractJpaDAO<T extends Serializable> {

    T findOne(String id);

    List<T> findAll();

    void create(T entity);

    T update(T entity);

    void delete(T entity);

    void deleteById(String entityId);

}

Of course, there's also a bunch of @Repositorys extending IAbstractDAO interface and a corresponding bunch of @Services extending AbstractDAO<T>, but those are just skeletons like

EntityARepo.java

package com.myorg.rest.dao.dba.repositories;

import ...

@Repository
public interface EntityARepo extends IAbstractJpaDAO<EntityA> {

}

EntityAService.java

package com.myorg.rest.dao.dba.services;

import ...

@Service
public class EntityAService extends AbstractDBADAO<EntityA> implements EntityARepo {

    public EntityAService() {
        super(EntityA.class);
    }
}

Similarly, there's a whole bunch of clases set up for the second database (dbB).

The problem

No matter what I do, I get the following problem (or something very similar):

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'javax.persistence.EntityManager' available: expected single matching bean but found 2: org.springframework.orm.jpa.SharedEntityManagerCreator#0,org.springframework.orm.jpa.SharedEntityManagerCreator#1
            at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:220)
            at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1285)
            at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1227)

From what I understand, the @PersistenceContext can not resolve to the correct LocalContainerEntityManagerFactoryBean. They both have setPersistenceUnitName('dbXUnit'), so they should be able to get resolved correctly, right?.

I do not have any persistence.xml file, I'm doing everything programmatically.

What I've tried so far

  1. Obviously, the code above.
  2. Also, you will notice there's a commented section for @EnableJpaRepositories; that doesn't work either.
  3. I've also tried to build a PersistenceUnitManager for each of the DataSource config classes to no success.
  4. I've tried @Autowired, with and without the @PersistenceContext, no luck either.
  5. I've tried @PersistenceContext on a setter and using @Qualifier on the input EntityManager, no luck either.
  6. I've tried with and without the @Primary decorators.

Please keep in mind any inconsistencies between the files above posted are surely a copy/paste error, I've had to edit names in order to keep confidentiality. Also note that the code I have compiles, runs, and doesn't output any other errors except for the one listed above. Also, with only one of those Database Config classes set up, the project runs as expected, and I can execute the tests on that connection.

The question

  • Can anyone spot why the @PersistenceContext is not wiring the correct dependency and complains of a duplicate? or
  • Should I do things in a completely different manner?

Solution

  • You are going the correct way so far.

    You need 2 of each:

    1. EntityManager/factory
    2. DataSource
    3. TransactionManager
    4. JPARepository-Configs

    The key to success is IMO the correct and unique naming of them all (2x4=8 different names). @Primary and @Secondary do not help here.