Spring Boot, Spring Data JPA con múltiples DataSources

Estoy intentando conectar cada @Repositories a diferentes DataSource (s) con Spring Boot y Spring Data JPA. Utilicé lo siguiente, http://xantorohara.blogspot.com/2013/11/spring-boot-jdbc-with-multiple.html , como referencia. Aquí está el código que estoy usando para tratar de implementar una solución similar usando Spring Data JPA.

CustomerDbConfig.java (Primera conexión de origen de datos)

@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 (Segunda fuente de datos)

 @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 (modelo)

 @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 (modelo)

 @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{ } 

OrderRepository.java

 public interface OrderRepository extends JpaRepository { } 

Finalmente, 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; } } 

Durante el inicio se lanzan las siguientes excepciones :

 -10-10 15:45:24.757 ERROR 1549 --- [ main] osboot.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 

El código completo para la muestra se puede encontrar en GitHub ( https://github.com/tonym2105/samples/tree/master/boot-multidb-sample )

Gracias por adelantado por la ayuda.

Hay otra manera de tener múltiples DataSources usando @EnableAutoConfiguration y application.properties.

Básicamente coloque múltiples información de configuración de DataSource en application.properties y genere la configuración predeterminada (dataSource y entityManagerFactory) automáticamente para first dataSource por @EnableAutoConfiguration. Pero para el próximo dataSource, cree dataSource, entityManagerFactory y transactionManager todo de forma manual mediante la información del archivo de propiedades.

A continuación está mi ejemplo para configurar dos fonts de datos. First dataSource es configurado por @EnableAutoConfiguration que se puede asignar solo para una configuración, no para varias. Y eso generará ‘transactionManager’ por DataSourceTransactionManager , que se ve por defecto transactionManager generado por la anotación. Sin embargo, he visto que la transacción no comienza a emitirse en el hilo del grupo de subprocesos progtwigdos solo para el DataSourceTransactionManager predeterminado y también cuando hay varios gestores de transacciones. Así que creo transactionManager manualmente por JpaTransactionManager también para el primer dataSource con la asignación de nombre de bean ‘transactionManager’ y por defecto entityManagerFactory. Ese JpaTransactionManager para el primer dataSource seguramente resuelve el problema de la transacción extraña en el hilo de ScheduledThreadPool.

Actualización para Spring Boot 1.3.0.RELEASE

Encontré mi configuración anterior con @EnableAutoConfiguration para los datos predeterminados. Source tiene problemas para encontrar entityManagerFactory con la versión de Spring Boot 1.3. Quizás @EnableAutoConfiguration no genere entityManagerFactory por defecto, una vez que presente mi propio transactionManager. Entonces ahora creo entityManagerFactory por mi cuenta. Entonces no necesito usar @EntityScan. Así que parece que estoy obteniendo cada vez más configuración de @EnableAutoConfiguration.

El segundo dataSource se configura sin @EnableAutoConfiguration y crea ‘anotherTransactionManager’ de forma manual.

Dado que hay múltiples transactionManager se extiende desde PlatformTransactionManager, debemos especificar qué transactionManager usar en cada anotación @Transactional

Configuración de repository predeterminado

 @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; } } 

Otra configuración de repository

 @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 

Elija transactionManager adecuado para la anotación @Transactional

Servicio para la primera fuente de datos

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

Servicio para otra fuente de datos

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

aquí está mi solución. base en 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; } } 

Revisé el código fuente que proporcionó en GitHub. Hubo varios errores / errores tipográficos en la configuración.

En CustomerDbConfig / OrderDbConfig, debe consultar customerEntityManager y los paquetes deben apuntar a los paquetes existentes:

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

Los paquetes para escanear en customerEntityManager y orderEntityManager no señalaban el paquete correcto:

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

Además, la inyección de EntityManagerFactory adecuada no funcionó. Debería ser:

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

Lo anterior estaba causando el problema y la excepción. Al proporcionar el nombre en un método @Bean, está seguro de que se inyectan los campos electromagnéticos (EMF) adecuados.

Lo último que hice fue deshabilitar la configuración automática de JpaRepositories:

 @EnableAutoConfiguration(exclude = JpaRepositoriesAutoConfiguration.class) 

¡Y con todas las correcciones, la aplicación comienza como probablemente esperas!

gracias a las respuestas de Steve Park y Rafal Borowiec conseguí que mi código funcionara, sin embargo, tuve un problema: el DriverManagerDataSource es una implementación “simple” y NO le da un ConnectionPool (consulte http://docs.spring.io/ spring / docs / current / javadoc-api / org / springframework / jdbc / datasource / DriverManagerDataSource.html ).

Por lo tanto, reemplacé las funciones a las que devuelve el DataSource para el secondDB .

 public DataSource 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(); } 

Además, si no necesita EntityManager como tal, puede eliminar tanto el entityManager() como la anotación @Bean .

Además, es posible que desee eliminar la anotación basePackages de su clase de configuración: mantenerla con la llamada factoryBean.setPackagesToScan() es suficiente.

no sé por qué, pero funciona. Dos configuraciones son iguales, simplemente cambie xxx a su nombre.

 @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 properties = new HashMap(); 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; } 

}