Spring Загрузка, Spring Данные JPA с несколькими источниками данных
Я пытаюсь подключить каждую @Repositories к различным DataSource (s) с помощью Spring Boot и Spring Data JPA. В качестве ссылки я использовал следующее 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;
}
}
Во время запуска выбрасываются следующие исключения:
-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)
Заранее спасибо за помощь.
Ответы
Ответ 1
Существует еще один способ иметь несколько источников данных с помощью @EnableAutoConfiguration и application.properties.
В основном поместите информацию о конфигурации dataSource на application.properties и автоматически создайте настройку по умолчанию (dataSource и entityManagerFactory) для первого источника данных с помощью @EnableAutoConfiguration. Но для следующего источника данных создайте dataSource, entityManagerFactory и transactionManager вручную вручную с помощью информации из файла свойств.
Ниже приведен мой пример настройки двух источников данных.
Первый источник данных настраивается с помощью @EnableAutoConfiguration, который может быть назначен только для одной конфигурации, а не нескольких. И это создаст "transactionManager" с помощью DataSourceTransactionManager, который будет выглядеть по умолчанию transactionManager, сгенерированный аннотацией. Однако я видел, что транзакция не начиналась в потоке из пула запланированных потоков только для DataSourceTransactionManager по умолчанию, а также при наличии нескольких менеджеров транзакций.
Поэтому я создаю transactionManager вручную с помощью JpaTransactionManager также для первого источника данных с назначением имени 'transactionManager' bean и объекта entityManagerFactory по умолчанию. Этот JpaTransactionManager для первого источника данных, безусловно, устраняет проблему с нечеткой транзакцией в потоке из ScheduledThreadPool.
Обновление для Spring Загрузка 1.3.0.RELEASE
Я нашел свою предыдущую конфигурацию с @EnableAutoConfiguration для по умолчанию dataSource имеет проблему при поиске entityManagerFactory с версией Spring Boot 1.3. Может быть, default entityManagerFactory не генерируется @EnableAutoConfiguration, после того, как я представим свой собственный transactionManager.
Итак, теперь я сам создаю entityManagerFactory. Поэтому мне не нужно использовать @EntityScan. Так что кажется, что я все больше и больше выхожу из настройки с помощью @EnableAutoConfiguration.
Второй источник данных устанавливается без @EnableAutoConfiguration и создает "anotherTransactionManager" вручную.
Поскольку существует несколько transactionManager, выходящих из PlatformTransactionManager, мы должны указать, какой transactionManager использовать для каждой аннотации @Transactional
Конфигурация репозитория по умолчанию
@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;
}
}
Другой конфигуратор репозитория
@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
Выберите подходящий transactionManager для аннотации @Transactional
Служба для первого источника данных
@Service("mainService")
@Transactional("transactionManager")
public class DefaultDataSourceServiceImpl implements DefaultDataSourceService
{
//
}
Служба для другого источника данных
@Service("anotherService")
@Transactional("anotherTransactionManager")
public class AnotherDataSourceServiceImpl implements AnotherDataSourceService
{
//
}
Ответ 2
вот мое решение. base на 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;
}
}
Ответ 3
Я проверил исходный код, который вы указали в 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)
И со всеми исправлениями приложение запускается, как вы, вероятно, ожидаете!
Ответ 4
благодаря ответам Стива Парка и Рафала Боровец я получил свой код, однако у меня была одна проблема: DriverManagerDataSource - это "простая" реализация и НЕ дает вам ConnectionPool (проверьте http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/jdbc/datasource/DriverManagerDataSource.html).
Следовательно, я заменил функции, которые возвращают DataSource
для secondDB
в.
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()
.
Ответ 5
Я написал полную статью в Примере множественных источников данных JPA Spring Boot. В этой статье мы узнаем, как настроить несколько источников данных и подключиться к нескольким базам данных в типичном веб-приложении Spring Boot. Мы будем использовать Spring Boot 2.0.5, JPA, Hibernate 5, Thymeleaf и базу данных H2 для создания простого Spring Boot веб-приложения с несколькими источниками данных.
Ответ 6
не знаю почему, но он работает. Две конфигурации одинаковы, просто измените 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;
}
}