首页 文章

Spring Boot,Spring Data JPA,带有多个DataSources

提问于
浏览
29

我正在尝试使用Spring Boot和Spring Data JPA将每个@Repositories连接到不同的DataSource . 我使用以下内容http://xantorohara.blogspot.com/2013/11/spring-boot-jdbc-with-multiple.html作为参考 . 以下是我试图使用Spring Data JPA实现类似解决方案时使用的代码 .

CustomerDbConfig.java (第一个数据源连接)

@Configuration
@EnableJpaRepositories(
        entityManagerFactoryRef = "orderEntityManager",
        transactionManagerRef = "orderTransactionManager",
        basePackages = {"com.mm.repository.customer"})
public class CustomerDbConfig {

    @Bean(name = "customerEntityManager")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(){
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource());
        em.setPackagesToScan(new String[] {"com.mm.domain.customer"});

        JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);
        em.setJpaProperties(additionalJpaProperties());
        em.setPersistenceUnitName("customerPersistence");
        em.setPackagesToScan("com.mm.domain.customer");

        return em;
    }

    Properties additionalJpaProperties(){
        Properties properties = new Properties();
        properties.setProperty("hibernate.hbm2ddl.auto", "create-drop");
        properties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
        properties.setProperty("hibernate.show_sql", "true");

        return properties;
    }

    @Bean
    public DataSource dataSource(){
        return DataSourceBuilder.create()
                .url("jdbc:h2:mem:customer:H2")
                .driverClassName("org.h2.Driver")
                .username("sa")
                .password("")
                .build();
    }   

    @Bean(name = "customerTransactionManager")
    public PlatformTransactionManager transactionManager(EntityManagerFactory emf){
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(emf);

        return transactionManager;
    }
}

CustomerDbConfig.java (第二个数据源)

@Configuration
@EnableJpaRepositories(
        entityManagerFactoryRef = "orderEntityManager",
        transactionManagerRef = "orderTransactionManager",
        basePackages = {"com.mm.repository.customer"})
public class CustomerDbConfig {

    @Bean(name = "customerEntityManager")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(){
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource());
        em.setPackagesToScan(new String[] {"com.mm.domain.customer"});

        JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);
        em.setJpaProperties(additionalJpaProperties());
        em.setPersistenceUnitName("customerPersistence");
        em.setPackagesToScan("com.mm.domain.customer");

        return em;
    }

    Properties additionalJpaProperties(){
        Properties properties = new Properties();
        properties.setProperty("hibernate.hbm2ddl.auto", "create-drop");
        properties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
        properties.setProperty("hibernate.show_sql", "true");

        return properties;
    }

    @Bean
    public DataSource dataSource(){
        return DataSourceBuilder.create()
                .url("jdbc:h2:mem:customer:H2")
                .driverClassName("org.h2.Driver")
                .username("sa")
                .password("")
                .build();
    }   

    @Bean(name = "customerTransactionManager")
    public PlatformTransactionManager transactionManager(EntityManagerFactory emf){
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(emf);

        return transactionManager;
    }
}

Customer.java (型号)

@Entity
@Table(name = "customer")
@Data
@EqualsAndHashCode(exclude = {"id"})
public class Customer {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    @Column(name = "name", nullable = false)
    private String name;

    @Column(name = "age", nullable = false)
    private Integer age;

....

Order.java (型号)

@Entity
@Table(name = "order")
@Data
@EqualsAndHashCode(exclude = {"id"})
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    @Column(name = "code", nullable = false)
    private Integer code;

    @Column(name = "quality", nullable = false)
    private Integer quality;

...

CustomerRepository.java

public interface CustomerRepository extends JpaRepository<Customer, Integer>{

}

OrderRepository.java

public interface OrderRepository extends JpaRepository<Order, Integer> {

}

最后, Application.java

@Configuration
@ComponentScan
@EnableAutoConfiguration
public class Application extends SpringApplication{

       public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }

        @Bean
        public ServletRegistrationBean h2Console() {
            ServletRegistrationBean reg = new ServletRegistrationBean(new WebServlet(), "/console/*");
            reg.setLoadOnStartup(1);
            return reg;
        }
}

在启动期间,抛出以下 exceptions

-10-10 15:45:24.757 ERROR 1549 --- [           main] o.s.boot.SpringApplication               : Application startup failed

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'customerTransactionManager' defined in class path resource [com/mm/boot/multidb/CustomerConfig.class]: Unsatisfied dependency expressed through constructor argument with index 0 of type [javax.persistence.EntityManagerFactory]: : No qualifying bean of type [javax.persistence.EntityManagerFactory] is defined: expected single matching bean but found 2: customerEntityManager,orderEntityManager; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [javax.persistence.EntityManagerFactory] is defined: expected single matching bean but found 2: customerEntityManager,orderEntityManager
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:747)
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:462)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1095)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:990)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:302)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:298)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:706)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:762)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482)
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:109)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:691)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:320)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:952)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:941)
    at com.mm.boot.multidb.Application.main(Application.java:17)
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [javax.persistence.EntityManagerFactory] is defined: expected single matching bean but found 2: customerEntityManager,orderEntityManager
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:974)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:862)
    at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:811)
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:739)
    ... 18 common frames omitted

Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'customerTransactionManager' defined in class path resource [com/mm/boot/multidb/CustomerConfig.class]: Unsatisfied dependency expressed through constructor argument with index 0 of type [javax.persistence.EntityManagerFactory]: : No qualifying bean of type [javax.persistence.EntityManagerFactory] is defined: expected single matching bean but found 2: customerEntityManager,orderEntityManager; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [javax.persistence.EntityManagerFactory] is defined: expected single matching bean but found 2: customerEntityManager,orderEntityManager
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:747)
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:462)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1095)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:990)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:302)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:298)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:706)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:762)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482)
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:109)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:691)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:320)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:952)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:941)
    at com.mm.boot.multidb.Application.main(Application.java:17)
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [javax.persistence.EntityManagerFactory] is defined: expected single matching bean but found 2: customerEntityManager,orderEntityManager
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:974)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:862)
    at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:811)
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:739)
    ... 18 more

可以在GitHub上找到该示例的完整代码(https://github.com/tonym2105/samples/tree/master/boot-multidb-sample

提前感谢您的帮助 .

5 回答

  • 6

    通过使用@EnableAutoConfiguration和application.properties,还有另一种方法可以拥有多个dataSource .

    基本上在application.properties上放置多个dataSource配置信息,并通过@EnableAutoConfiguration自动为第一个dataSource生成默认设置(dataSource和entityManagerFactory) . 但对于下一个dataSource,请通过属性文件中的信息手动创建dataSource,entityManagerFactory和transactionManager .

    下面是我设置两个dataSource的示例 . 第一个dataSource由@EnableAutoConfiguration设置,只能为一个配置而不是多个配置 . 这将由 DataSourceTransactionManager 生成'transactionManager',它看起来是由注释生成的默认transactionManager . 但是,我已经看到事务没有从调度线程池中的线程开始问题仅针对默认的DataSourceTransactionManager以及有多个事务管理器时 . 所以我也通过 JpaTransactionManager 为第一个dataSource手动创建了事务管理器,并分配了'transactionManager' bean名称和默认的entityManagerFactory . 第一个dataSource的JpaTransactionManager肯定会解决来自ScheduledThreadPool的线程上的奇怪事务问题 .

    Update for Spring Boot 1.3.0.RELEASE

    我发现我之前使用@EnableAutoConfiguration配置的默认dataSource在查找带有Spring Boot 1.3版本的entityManagerFactory时遇到了问题 . 在我介绍自己的transactionManager之后,可能默认的entityManagerFactory不是由@EnableAutoConfiguration生成的 . 所以现在我自己创建entityManagerFactory . 所以我不需要使用@EntityScan . 因此看起来我越来越多地通过@EnableAutoConfiguration进行设置了 .

    第二个dataSource在没有@EnableAutoConfiguration的情况下设置,并通过手动方式创建“anotherTransactionManager” .

    由于从PlatformTransactionManager扩展了多个transactionManager,我们应该指定在每个@Transactional注释上使用哪个transactionManager

    Default Repository Config

    @Configuration
    @EnableTransactionManagement
    @EnableAutoConfiguration
    @EnableJpaRepositories(
            entityManagerFactoryRef = "entityManagerFactory",
            transactionManagerRef = "transactionManager",
            basePackages = {"com.mysource.repository"})
    public class RepositoryConfig {
        @Autowired
        JpaVendorAdapter jpaVendorAdapter;
    
        @Autowired
        DataSource dataSource;
    
        @Bean(name = "entityManager")
        public EntityManager entityManager() {
            return entityManagerFactory().createEntityManager();
        }
    
        @Primary
        @Bean(name = "entityManagerFactory")
        public EntityManagerFactory entityManagerFactory() {
            LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
            emf.setDataSource(dataSource);
            emf.setJpaVendorAdapter(jpaVendorAdapter);
            emf.setPackagesToScan("com.mysource.model");
            emf.setPersistenceUnitName("default");   // <- giving 'default' as name
            emf.afterPropertiesSet();
            return emf.getObject();
        }
    
        @Bean(name = "transactionManager")
        public PlatformTransactionManager transactionManager() {
            JpaTransactionManager tm = new JpaTransactionManager();
            tm.setEntityManagerFactory(entityManagerFactory());
            return tm;
        }
    }
    

    Another Repository Config

    @Configuration
    @EnableTransactionManagement
    @EnableJpaRepositories(
            entityManagerFactoryRef = "anotherEntityManagerFactory",
            transactionManagerRef = "anotherTransactionManager",
            basePackages = {"com.mysource.anothersource.repository"})
    public class AnotherRepositoryConfig {
        @Autowired
        JpaVendorAdapter jpaVendorAdapter;
    
        @Value("${another.datasource.url}")
        private String databaseUrl;
    
        @Value("${another.datasource.username}")
        private String username;
    
        @Value("${another.datasource.password}")
        private String password;
    
        @Value("${another.dataource.driverClassName}")
        private String driverClassName;
    
        @Value("${another.datasource.hibernate.dialect}")
        private String dialect;
    
        public DataSource dataSource() {
            DriverManagerDataSource dataSource = new DriverManagerDataSource(databaseUrl, username, password);
            dataSource.setDriverClassName(driverClassName);
            return dataSource;
        }
    
        @Bean(name = "anotherEntityManager")
        public EntityManager entityManager() {
            return entityManagerFactory().createEntityManager();
        }
    
        @Bean(name = "anotherEntityManagerFactory")
        public EntityManagerFactory entityManagerFactory() {
            Properties properties = new Properties();
            properties.setProperty("hibernate.dialect", dialect);
    
            LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
            emf.setDataSource(dataSource());
            emf.setJpaVendorAdapter(jpaVendorAdapter);
            emf.setPackagesToScan("com.mysource.anothersource.model");   // <- package for entities
            emf.setPersistenceUnitName("anotherPersistenceUnit");
            emf.setJpaProperties(properties);
            emf.afterPropertiesSet();
            return emf.getObject();
        }
    
        @Bean(name = "anotherTransactionManager")
        public PlatformTransactionManager transactionManager() {
            return new JpaTransactionManager(entityManagerFactory());
        }
    }
    

    application.properties

    # database configuration
    spring.datasource.url=jdbc:h2:file:~/main-source;AUTO_SERVER=TRUE
    spring.datasource.username=sa
    spring.datasource.password=
    spring.datasource.driver-class-name=org.h2.Driver
    spring.datasource.continueOnError=true
    spring.datasource.initialize=false
    
    # another database configuration
    another.datasource.url=jdbc:sqlserver://localhost:1433;DatabaseName=another;
    another.datasource.username=username
    another.datasource.password=
    another.datasource.hibernate.dialect=org.hibernate.dialect.SQLServer2008Dialect 
    another.datasource.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
    

    为@Transactional注释选择适当的transactionManager

    Service for first datasource

    @Service("mainService")
    @Transactional("transactionManager")
    public class DefaultDataSourceServiceImpl implements DefaultDataSourceService       
    {
    
       //
    
    }
    

    Service for another datasource

    @Service("anotherService")
    @Transactional("anotherTransactionManager")
    public class AnotherDataSourceServiceImpl implements AnotherDataSourceService 
    {
    
       //
    
    }
    
  • 35

    我检查了你在GitHub上提供的源代码 . 配置中存在多个错误/拼写错误 .

    在CustomerDbConfig / OrderDbConfig中,您应该引用customerEntityManager,并且包应该指向现有的包:

    @Configuration
    @EnableJpaRepositories(
        entityManagerFactoryRef = "customerEntityManager",
        transactionManagerRef = "customerTransactionManager",
        basePackages = {"com.mm.boot.multidb.repository.customer"})
    public class CustomerDbConfig {
    

    要在customerEntityManager和orderEntityManager中扫描的包都没有指向正确的包:

    em.setPackagesToScan("com.mm.boot.multidb.model.customer");
    

    注入适当的EntityManagerFactory也不起作用 . 它应该是:

    @Bean(name = "customerTransactionManager")
    public PlatformTransactionManager transactionManager(EntityManagerFactory customerEntityManager){
    
    }
    

    以上是导致问题和例外 . 在@Bean方法中提供名称时,您确定可以注入适当的EMF .

    我做的最后一件事是禁用自动配置JpaRepositories:

    @EnableAutoConfiguration(exclude = JpaRepositoriesAutoConfiguration.class)
    

    通过所有修复程序,应用程序就像您预期的那样启动!

  • 1

    感谢Steve Park和Rafal Borowiec的答案我得到了我的代码,但是,我有一个问题:DriverManagerDataSource是一个"simple"实现并且 NOT give you a ConnectionPool (检查http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/jdbc/datasource/DriverManagerDataSource.html) .

    因此,我替换了为 secondDB 返回 DataSource 的函数 .

    public DataSource <secondaryDB>DataSource() {
        // use DataSourceBuilder and NOT DriverManagerDataSource 
        // as this would NOT give you ConnectionPool
        DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
        dataSourceBuilder.url(databaseUrl);
        dataSourceBuilder.username(username);
        dataSourceBuilder.password(password);
        dataSourceBuilder.driverClassName(driverClassName);
        return dataSourceBuilder.build();
    }
    

    此外,如果您不需要 EntityManager ,则可以同时删除 entityManager()@Bean 注释 .

    另外,您可能希望删除配置类的basePackages注释:使用 factoryBean.setPackagesToScan() 调用来维护它是足够的 .

  • -2

    不知道为什么,但它的确有效 . 两个配置相同,只需将xxx更改为您的名字即可 .

    @Configuration
    @EnableTransactionManagement
    @EnableJpaRepositories(
        entityManagerFactoryRef = "xxxEntityManager",
        transactionManagerRef = "xxxTransactionManager",
        basePackages = {"aaa.xxx"})
     public class RepositoryConfig {
    @Autowired
    private Environment env;
    
    @Bean
    @Primary
    @ConfigurationProperties(prefix="datasource.xxx")
    public DataSource xxxDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    @Bean
    public LocalContainerEntityManagerFactoryBean xxxEntityManager() {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(xxxDataSource());
        em.setPackagesToScan(new String[] {"aaa.xxx"});
    
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);
        HashMap<String, Object> properties = new HashMap<String, Object>();
        properties.put("hibernate.show_sql", env.getProperty("hibernate.show_sql"));
        properties.put("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
        properties.put("hibernate.dialect", env.getProperty("hibernate.dialect"));
        em.setJpaPropertyMap(properties);
    
        return em;
    }
    
    @Bean(name = "xxxTransactionManager")
    public PlatformTransactionManager xxxTransactionManager() {
        JpaTransactionManager tm = new JpaTransactionManager();
        tm.setEntityManagerFactory(xxxEntityManager().getObject());
        return tm;
    }
    

    }

  • 2

    这是我的解决方案 . 基于spring-boot.1.2.5.RELEASE .

    application.properties

    first.datasource.driver-class-name=com.mysql.jdbc.Driver
    first.datasource.url=jdbc:mysql://127.0.0.1:3306/test
    first.datasource.username=
    first.datasource.password=
    first.datasource.validation-query=select 1
    
    second.datasource.driver-class-name=com.mysql.jdbc.Driver
    second.datasource.url=jdbc:mysql://127.0.0.1:3306/test2
    second.datasource.username=
    second.datasource.password=
    second.datasource.validation-query=select 1
    

    DataSourceConfig.java

    @Configuration
    public class DataSourceConfig {
        @Bean
        @Primary
        @ConfigurationProperties(prefix="first.datasource")
        public DataSource firstDataSource() {
            DataSource ds =  DataSourceBuilder.create().build();
            return ds;
        }
    
        @Bean
        @ConfigurationProperties(prefix="second.datasource")
        public DataSource secondDataSource() {
            DataSource ds =  DataSourceBuilder.create().build();
            return ds;
        }
    }
    

相关问题