【问题标题】:Dynamic datasource in Spring boot JPASpring Boot JPA 中的动态数据源
【发布时间】:2018-07-22 12:49:30
【问题描述】:

我有一个应用程序需要连接到几个不同的架构,但所有的架构都是相同类型的 (ORACLE)。哪个模式来自 UI 的决定。

如果用户选择schema1,那么实体应该在Schema1中持久化,如果选择other,那么它应该在选择的其他schema中。

我正在使用 Spring boot + Hibernate 和依赖“spring-boot-starter-data-jpa”

我已经创建了一个如下所示的数据源类,这样我就可以在每次调用数据层之前更改数据源对象中的“schemaName”。

@Component
public class SchemaDatasource extends AbstractDataSource {

    private String schemaName;

    @Autowired
    private DSManager dsm;

    public void setSchemaName(String schemaName) {
        this.schemaName = schemaName;
    }

    @Override
    public Connection getConnection() throws SQLException {
        if (schemaName!= null)
            return dsm.getConnection(schemaName);
        else
            return null;
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        if (schemaName!= null)
            return dsm.getConnection(schemaName);
        else
            return null;
    }

}

我的问题是在启动过程中,“HibernateJpaAutoConfiguration”尝试创建 sessionfactory。在创建过程中它尝试检查与数据源的连接但是由于启动时 schemaName 为空,我的 SchemaDatasource 返回空连接,应用程序引导失败。

有没有办法解决这个问题。我期待类似于 SessionFactory 在休眠中没有选项。

在 RoutingDatasource 的情况下,我也必须设置 defaultDatasource。

Spring boot version: 1.5.9.RELEASE

【问题讨论】:

    标签: hibernate spring-boot spring-orm


    【解决方案1】:

    这是我的数据源实现

    public class DataSourceManager implements DataSource {
    
        private Map<String, DataSource> dataSources = new HashMap<>();
        private DataSource dataSource;
    
        public DataSourceManager() {
        }
    
        public DataSourceManager(DataSource dataSource) {
            this.dataSource = dataSource;
        }
    
        public void add(String name, DataSource dataSource) {
            dataSources.put(name, dataSource);
        }
    
        public void switchDataSource(String name) {
            dataSource = dataSources.get(name);
        }
    
        @Override
        public PrintWriter getLogWriter() throws SQLException {
            return dataSource.getLogWriter();
        }
    
        @Override
        public void setLogWriter(PrintWriter out) throws SQLException {
            dataSource.setLogWriter(out);
        }
    
        @Override
        public void setLoginTimeout(int seconds) throws SQLException {
            dataSource.setLoginTimeout(seconds);
        }
    
        @Override
        public int getLoginTimeout() throws SQLException {
            return dataSource.getLoginTimeout();
        }
    
        @Override
        public Logger getParentLogger() throws SQLFeatureNotSupportedException {
            return dataSource.getParentLogger();
        }
    
        @Override
        public <T> T unwrap(Class<T> iface) throws SQLException {
            return dataSource.unwrap(iface);
        }
    
        @Override
        public boolean isWrapperFor(Class<?> iface) throws SQLException {
            return dataSource.isWrapperFor(iface);
        }
    
        @Override
        public Connection getConnection() throws SQLException {
            return dataSource.getConnection();
        }
    
        @Override
        public Connection getConnection(String username, String password) throws SQLException {
            return dataSource.getConnection(username, password);
        }
    }
    

    这是我的配置

    @Configuration
    public class DataSourceConfig {
    
        @Autowired
        private Environment env;
    
        public DataSource makeDataSource(String name) {
            return DataSourceBuilder.create()
                    .driverClassName(env.getProperty("spring.datasource." + name + ".driver-class-name"))
                    .url(env.getProperty("spring.datasource." + name + ".url")).build();
        }
    
        @Bean
        public DataSource dataSource() {
            DataSourceManager dataSourceManager = new DataSourceManager();
            dataSourceManager.add("test1", makeDataSource("test1"));
            dataSourceManager.add("test2", makeDataSource("test2"));
            dataSourceManager.switchDataSource("test1");
            return dataSourceManager;
        }
    
    }
    

    这里是 application.yml

    spring:
      jpa:
        hibernate:
          ddl-auto: create
        properties:
          hibernate:
            dialect: org.hibernate.dialect.H2Dialect
      datasource:
        test1:
          name: test2
          url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
          driver-class-name: org.h2.Driver
          username: h2
          password: h2
        test2:
          name: test1
          url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
          driver-class-name: org.h2.Driver
          username: h2
          password: h2
    

    【讨论】:

    • 谢谢,但我在切换时没有问题。问题是在不配置初始数据源的情况下引导应用程序。
    • 好的,我给出了更好的答案,你看看
    • 我知道这个问题比较老,但你能举一个如何在代码中切换数据源的用法示例吗?我也很好奇这将如何与 transactionalManagers (JpaRepository) 和 entityManagers 一起使用