it-swarm.com.de

Spring Boot, Spring Data JPA mit mehreren DataSources

Ich versuche, @Repositories mit Spring Boot und Spring Data JPA mit verschiedenen DataSource (s) zu verbinden. Ich habe folgendes verwendet: http://xantorohara.blogspot.com/2013/11/spring-boot-jdbc-with-multiple.html als Referenz. Hier ist der Code, den ich verwende, um eine ähnliche Lösung mit Spring Data JPA zu implementieren.

CustomerDbConfig.Java (erste Datenquellenverbindung)

@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 (zweite Datenquelle)

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

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

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

}

Schließlich 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;
        }
}

Beim Start werden folgende Ausnahmen ausgelöst:

-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

Den vollständigen Code für das Beispiel finden Sie auf GitHub ( https://github.com/tonym2105/samples/tree/master/boot-multidb-sample ).

Vielen Dank im Voraus für die Hilfe.

31
tonym2105

Es gibt eine andere Möglichkeit, mehrere DataSources mithilfe von @EnableAutoConfiguration und application.properties zu erstellen.

Legen Sie im Allgemeinen mehrere dataSource-Konfigurationsinformationen in application.properties ab und generieren Sie die Standardeinstellung (dataSource und entityManagerFactory) automatisch für die erste dataSource von @EnableAutoConfiguration. Für die nächste Datenquelle müssen Sie jedoch dataSource, entityManagerFactory und transactionManager manuell anhand der Info aus der Eigenschaftendatei erstellen.

Im Folgenden finden Sie mein Beispiel zum Einrichten zweier dataSources . Die erste dataSource wird von @EnableAutoConfiguration eingerichtet, die nur einer Konfiguration zugewiesen werden kann, nicht mehreren. Dadurch wird 'transactionManager' von DataSourceTransactionManager generiert, der den von der Annotation generierten Standard-Transaktionsmanager darstellt. Ich habe jedoch festgestellt, dass das Problem der Transaktion im geplanten Thread-Pool nicht nur für den Standard-DataSourceTransactionManager ausgegeben wurde und auch, wenn mehrere Transaktionsmanager vorhanden sind. Also erstelle ich den transactionManager manuell von JpaTransactionManager und auch für die erste DataSource mit der Zuordnung des 'transactionManager'-Bean-Namens und der Standard-EntityManagerFactory. Dieser JpaTransactionManager für die erste dataSource behebt das seltsame Transaktionsproblem des Threads von ScheduledThreadPool.

Update für Spring Boot 1.3.0.RELEASE

Ich habe meine vorherige Konfiguration mit @EnableAutoConfiguration für die Standard-DataSource gefunden, bei der das Finden von entityManagerFactory mit Spring Boot 1.3-Version ein Problem darstellt. Möglicherweise wird die Standard-EntityManagerFactory nicht von @EnableAutoConfiguration generiert, sobald ich meinen eigenen Transaktionsmanager vorgestellt habe. Nun erstelle ich die EntityManagerFactory selbst. Ich brauche also @EntityScan nicht. Es sieht also so aus, als würde ich mit @EnableAutoConfiguration immer mehr aus dem Setup herausholen.

Die zweite dataSource wird ohne @EnableAutoConfiguration eingerichtet und erstellt manuell einen weiteren TransactionManager.

Da es mehrere TransactionManager-Erweiterungen von PlatformTransactionManager gibt, sollten Sie angeben, welcher TransactionManager für jede @Transactional-Anmerkung verwendet werden soll

Standard-Repository-Konfiguration

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

Eine weitere Repository-Konfiguration

@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

Wählen Sie den richtigen transactionManager für die @Transactional-Anmerkung

Service für erste Datenquelle

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

   //

}

Dienst für eine andere Datenquelle

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

   //

}
36
Steve Park

hier ist meine Lösung. Basis auf 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;
    }
}
6
david

Ich habe den auf GitHub bereitgestellten Quellcode überprüft. Es gab einige Fehler/Tippfehler in der Konfiguration.

In CustomerDbConfig/OrderDbConfig sollten Sie sich auf customerEntityManager beziehen und Pakete sollten auf vorhandene Pakete verweisen:

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

Die Pakete, die in customerEntityManager und orderEntityManager geprüft werden sollen, weisen nicht auf das richtige Paket hin:

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

Auch die Injektion der richtigen EntityManagerFactory funktionierte nicht. Es sollte sein:

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

}

Das oben genannte verursachte das Problem und die Ausnahme. Wenn Sie den Namen in einer @Bean-Methode angeben, sind Sie sicher, dass Sie die korrekte EMF - Injektion erhalten.

Das letzte, was ich getan habe, war die automatische Konfiguration von JpaRepositories zu deaktivieren:

@EnableAutoConfiguration(exclude = JpaRepositoriesAutoConfiguration.class)

Und mit allen Fixes startet die Anwendung wie erwartet!

2
Rafal Borowiec

dank der Antworten von Steve Park und Rafal Borowiec bekam ich meinen Code zum Laufen. Ich hatte jedoch ein Problem: DriverManagerDataSource ist eine "einfache" Implementierung und gibt Ihnen NOT einen ConnectionPool (check http: //). docs.spring.io/spring/docs/current/javadoc-api/org/springframework/jdbc/datasource/DriverManagerDataSource.html ).

Daher habe ich die Funktionen ersetzt, die DataSource für secondDB an zurückgeben.

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

Wenn Sie EntityManager nicht als solche benötigen, können Sie sowohl die entityManager()- als auch die @Bean-Anmerkung entfernen. 

Außerdem möchten Sie möglicherweise die Annotation basePackages Ihrer Konfigurationsklasse entfernen: Die Aufrechterhaltung mit dem Aufruf factoryBean.setPackagesToScan() ist ausreichend.

1
codemonkey

Ich habe einen vollständigen Artikel unter Spring Boot JPA Multiple Data Sources Example geschrieben. In diesem Artikel erfahren Sie, wie Sie in einer typischen Spring Boot-Webanwendung mehrere Datenquellen konfigurieren und eine Verbindung zu mehreren Datenbanken herstellen. Wir werden die Datenbanken Spring Boot 2.0.5, JPA, Hibernate 5, Thymeleaf und H2 verwenden, um eine einfache Webanwendung für mehrere Spring Boot-Datenquellen zu erstellen.

0
Ramesh Fadatare