Eric Liao
Eric Liao

Reputation: 13

Spring Boot auto generate tables from wrong datasource

My current project needs to connect to multiple databases. I set

spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=update

in application.properties.

and I have some dbConfig as below:

@Configuration
public class DBSourceConfiguration {
    public final static String DATA_SOURCE_PRIMARY = "dataSource";
    public final static String DATA_SOURCE_PROPERTIES = "propertiesDataSource";
    public final static String DATA_SOURCE_REPORT = "reportDataSource";
    public final static String DATA_SOURCE_NEW_DRAGON = "newDragonDataSource";

    @Primary
    @Bean(name = DATA_SOURCE_PRIMARY)
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = DATA_SOURCE_REPORT)
    @ConfigurationProperties(prefix = "externaldatasource.report")
    public DataSource reportDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = DATA_SOURCE_NEW_DRAGON)
    @ConfigurationProperties(prefix = "externaldatasource.newdragon")
    public DataSource newDragonDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = DATA_SOURCE_PROPERTIES)
    @ConfigurationProperties(prefix = "externaldatasource.properties")
    public DataSource propertiesDataSource() {
        return DataSourceBuilder.create().build();
    }
}

and

<!-- language: Java -->

    @Configuration
    @EnableTransactionManagement
    @EnableJpaRepositories(
            entityManagerFactoryRef = PrimaryDbConfig.ENTITY_MANAGER_FACTORY, 
            transactionManagerRef = PrimaryDbConfig.TRANSACTION_MANAGER, 
            basePackageClasses = { _TbsRepositoryBasePackage.class })
    public class PrimaryDbConfig extends AbstractDbConfig {
        public final static String ENTITY_MANAGER_FACTORY = "entityManagerFactoryPrimary";
        public final static String ENTITY_MANAGER = "entityManagerPrimary";
        public final static String TRANSACTION_MANAGER = "transactionManagerPrimary";

        @Autowired
        @Qualifier(DBSourceConfiguration.DATA_SOURCE_PRIMARY)
        private DataSource dataSource;

        @Primary
        @Bean(name = ENTITY_MANAGER_FACTORY)
        public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder) {
            return builder.dataSource(dataSource).properties(getVendorProperties(dataSource)).packages(_TbsEntityBasePackage.class).persistenceUnit("primaryPersistenceUnit").build();
        }

        @Primary
        @Bean(name = ENTITY_MANAGER)
        public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
            return entityManagerFactory(builder).getObject().createEntityManager();
        }

        @Primary
        @Bean(name = TRANSACTION_MANAGER)
        public PlatformTransactionManager transactionManager(EntityManagerFactoryBuilder builder) {
            return new JpaTransactionManager(entityManagerFactory(builder).getObject());
        }    
    }

and

<!-- language: Java -->

    @Configuration
    @EnableTransactionManagement
    @EnableJpaRepositories(
            entityManagerFactoryRef = PropertiesDbConfig.ENTITY_MANAGER_FACTORY, 
            transactionManagerRef = PropertiesDbConfig.TRANSACTION_MANAGER, 
            basePackageClasses = { _PropertiesRepositoryBasePackage.class })
    public class PropertiesDbConfig extends AbstractDbConfig {
        public final static String ENTITY_MANAGER_FACTORY = "entityManagerFactoryProperties";
        public final static String ENTITY_MANAGER = "entityManagerProperties";
        public final static String TRANSACTION_MANAGER = "transactionManagerProperties";

        @Autowired
        @Qualifier(DBSourceConfiguration.DATA_SOURCE_PROPERTIES)
        private DataSource dataSource;

        @Bean(name = ENTITY_MANAGER_FACTORY)
        public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder) {
            return builder.dataSource(dataSource).properties(getVendorProperties(dataSource)).packages(_PropertiesEntityBasePackage.class).persistenceUnit("propertiesPersistenceUnit").build();
        }

        @Bean(name = ENTITY_MANAGER)
        public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
            return entityManagerFactory(builder).getObject().createEntityManager();
        }

        @Bean(name = TRANSACTION_MANAGER)
        public PlatformTransactionManager transactionManager(EntityManagerFactoryBuilder builder) {
            return new JpaTransactionManager(entityManagerFactory(builder).getObject());
        }
    }

and two more DBConfig classes(just like two DbConfig classes above).

My problem is every time I run this web application, Entities (under different packages) will generate to all databases. In other words, Tbs's(Primary) entities will generate tables to newDragon and all other databases.

For instance, Entity A belongs to primary data source, Entity B belongs to properties datasouce. But framework generates table A, B to both primary database and newDragon database and other two database.


Update 2018/06/01 - 1

Although framework generate all entities to all databases, but I can still access tables from the right database. All my web application functionalities work very well. This is very odd, isn't it?

I guess my configuration is fine, so that there is no any problems while my application access database (like read from wrong database and get empty result or insert data to wrong database, etc). Probably something else cause this gernerte all tables to all databases problem.

Upvotes: 1

Views: 1126

Answers (1)

ajoshow
ajoshow

Reputation: 38

Based on the configuration you provided, CRUD tables from the right database shouldn't be problem. But generating tables into the right database, sometimes you may want to check whether the configuration picks entity/package names correctly or not.

Each LocalContainerEntityManagerFactoryBean is set with unique package class, the framework will then scan entities under this package name and generate tables accordingly at target datasource; however, there's a situation the packageToScan will be changed. As you have @EntityScan annotation, it would
overrides packagesToScan on all defined LocalContainerEntityManagerFactoryBean, reference code as follow: EntityScanRegistrar.java

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    if (bean instanceof LocalContainerEntityManagerFactoryBean) {
        LocalContainerEntityManagerFactoryBean factoryBean = (LocalContainerEntityManagerFactoryBean) bean;
        factoryBean.setPackagesToScan(this.packagesToScan);
        this.processed = true;
    }
    return bean;
}

As a result, even you have provided each LocalContainerEntityManagerFactoryBean with unique package class, the final result may still be overriden by the framework if you've @EntityScan somewhere at your application. Your configuration seems ok to me, so try to find and resolve the package names between @EntityScan and LocalContainerEntityManagerFactoryBean first, it should solve the issue.

reference: https://github.com/spring-projects/spring-boot/issues/6830

Upvotes: 1

Related Questions