在 Spring Boot 应用程序的多个数据库中未创建表

Posted

技术标签:

【中文标题】在 Spring Boot 应用程序的多个数据库中未创建表【英文标题】:Tables not getting created in multiple databases in spring boot application 【发布时间】:2017-08-24 15:24:46 【问题描述】:

我正在开发 Spring Boot 多租户应用程序。我已经配置了多个数据源,如下所示:

application.properties

spring.multitenancy.datasource1.url=jdbc:mysql://localhost:3306/db1
spring.multitenancy.datasource1.username=root
spring.multitenancy.datasource1.password=****
spring.multitenancy.datasource1.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update

spring.multitenancy.datasource2.url=jdbc:mysql://localhost:3306/db2
spring.multitenancy.datasource2.username=root
spring.multitenancy.datasource2.password=****
spring.multitenancy.datasource2.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update

spring.multitenancy.datasource3.url=jdbc:mysql://localhost:3306/db3
spring.multitenancy.datasource3.username=root
spring.multitenancy.datasource3.password=****
spring.multitenancy.datasource3.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update

DataSourceBasedMultiTenantConnectionProviderImpl.java

@Component
public class DataSourceBasedMultiTenantConnectionProviderImpl extends AbstractDataSourceBasedMultiTenantConnectionProviderImpl 

    private static final long serialVersionUID = 8168907057647334460L;
    private static final String DEFAULT_TENANT_ID = "tenant_1";

    @Autowired
    private DataSource dataSource1;

    @Autowired
    private DataSource dataSource2;

    @Autowired
    private DataSource dataSource3;

    private Map<String, DataSource> map;

    @PostConstruct
    public void load() 
        map = new HashMap<>();
        map.put("tenant_1", dataSource1);
        map.put("tenant_2", dataSource2);
        map.put("tenant_3", dataSource3);
    

    @Override
    protected DataSource selectAnyDataSource() 
        return map.get(DEFAULT_TENANT_ID);
    

    @Override
    protected DataSource selectDataSource(String tenantIdentifier) 
        return map.get(tenantIdentifier);
    

多租户属性.java

@ConfigurationProperties("spring.multitenancy")
public class MultitenancyProperties 

    @NestedConfigurationProperty
    private DataSourceProperties datasource1;

    @NestedConfigurationProperty
    private DataSourceProperties datasource2;

    @NestedConfigurationProperty
    private DataSourceProperties datasource3;

    public DataSourceProperties getDatasource1() 
        return datasource1;
    

    public void setDatasource1(DataSourceProperties datasource1) 
        this.datasource1 = datasource1;
    

    public DataSourceProperties getDatasource2() 
        return datasource2;
    

    public void setDatasource2(DataSourceProperties datasource2) 
        this.datasource2 = datasource2;
    

    public DataSourceProperties getDatasource3() 
        return datasource3;
    

    public void setDatasource3(DataSourceProperties datasource3) 
        this.datasource3 = datasource3;
    

MultiTenancyJpaConfiguration.java

@Configuration
@EnableConfigurationProperties(JpaProperties.class)
public class MultiTenancyJpaConfiguration 

    @Autowired
    private DataSource dataSource;

    @Autowired
    private JpaProperties jpaProperties;

    @Autowired
    private MultiTenantConnectionProvider multiTenantConnectionProvider;

    @Autowired
    private CurrentTenantIdentifierResolver currentTenantIdentifierResolver;

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder) 
        Map<String, Object> hibernateProps = new LinkedHashMap<>();
        hibernateProps.putAll(jpaProperties.getHibernateProperties(dataSource));

        hibernateProps.put(Environment.MULTI_TENANT, MultiTenancyStrategy.DATABASE);
        hibernateProps.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProvider);
        hibernateProps.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolver);
        hibernateProps.put(Environment.DIALECT, "org.hibernate.dialect.MySQLDialect");

        return builder.dataSource(dataSource).packages(HotelEntity.class.getPackage().getName()).properties(hibernateProps).jta(false).build();
    

应用程序启动器

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableConfigurationProperties(MultitenancyProperties.class)
public class Application 

    public static void main(String[] args) 
        ApplicationContext ctx = SpringApplication.run(Application.class, args);
    

当我运行启动应用程序时,所有表都仅在第一个数据源中创建。 1) 如何在应用程序启动时在所有数据源中创建表? 2) 如何查看每个数据源打开/关闭的连接? 3) 有没有更好的方法来使用 Spring Boot 配置多租户应用程序以获得更好的性能?

【问题讨论】:

selectAnyDataSource 总是返回相同的,也许你可以在那里添加一些逻辑? selectAnyDataSource 代码用于根据租户 id 选择数据源。数据将根据tenantId 填充到所有数据源中。但是在应用程序启动时并没有在所有数据源中创建表。我不得不手动将表复制到 datasource2 和 datasource3。 请任何人帮助解决此问题 您是否尝试在调试模式下运行您的程序以查看初始化的内容和未初始化的内容? 无论如何,您可能需要重新考虑让 hibernate 自动创建和更新您的架构,如下所述:***.com/questions/221379 并使用例如flywaydb.org 用于在启动时创建和更新架构 【参考方案1】:

正如@Alex 所说,您需要不同的 EntityManagers、TransactionManager 和 Datasources。这是我的做法

@Configuration
@EnableJpaRepositories(
    entityManagerFactoryRef = "dataSource1EntityManagerFactory",
    transactionManagerRef = "dataSource1TransactionManager",
    basePackageClasses = dataSource1Repository.class)
public class DataSource1Config extends SqlConfig// Put all common code in base class SqlConfig. If not remove it

    @Bean
    @Primary
    public DataSource dataSource1() 
        //create dataSource using MultitenancyProperties::getDataSource1
    

    @Primary
    @Bean(name = "dataSource1TransactionManager")
    PlatformTransactionManager dataSource1TransactionManager(EntityManagerFactory dataSource1EntityManagerFactory) 
        JpaTransactionManager txManager = new JpaTransactionManager();
        txManager.setEntityManagerFactory(dataSource1EntityManagerFactory);
        return txManager;
    

    @Primary
    @Bean(name = "dataSource1EntityManagerFactory")
    LocalContainerEntityManagerFactoryBean dataSource1EntityManagerFactory() 
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource1());
        em.setPackagesToScan(dataSource1Repository.class.getPackage().getName(), dataSource1ModelClass.class.getPackage().getName());
        em.setPersistenceUnitName("dataSource1Db");

        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setGenerateDdl(false);
        em.setJpaVendorAdapter(vendorAdapter);
        return em;
    

您可以像这样创建另外两个类。请记住仅在数据源、事务管理器和实体管理器的一个实例上使用@Primary(不管哪一个)。再提醒一句,请确保 Repository 类位于所有三个数据库的不同包中。

【讨论】:

【参考方案2】:

尝试添加以下属性:

spring.jpa.generate-ddl=true

【讨论】:

【参考方案3】:

您需要 2 个不同的持久性工厂,而不是一个,每个都应该为不同的数据源生成不同的 EntityManager。此外,每个实体映射都应标记为仅与一个实体管理器一起使用。 在此处查看完整解决方案:

http://www.baeldung.com/spring-data-jpa-multiple-databases

【讨论】:

以上是关于在 Spring Boot 应用程序的多个数据库中未创建表的主要内容,如果未能解决你的问题,请参考以下文章

在测试中实例化多个 Spring Boot 应用程序

如何在 MySQL 和 MariaDB 的 Spring Boot 应用程序中配置多个(两个以上)数据源,

在春季批处理(spring-boot-1.5.2.RELEASE)中使用多个数据源在启动时引发异常

带有 Spring Boot 的 Spring Data JPA 中的多个数据源,[重复]

具有在运行时创建的多个数据源的 Spring Boot 和 Spring Data 应用程序

使用多个 jdbc 连接运行并行查询的 Spring Boot 应用程序