如何在spring框架中解決多數據源的問題

AbstractRoutingDataSource動態數據源切換
上週末,室友通宵達旦的敲代碼處理他的多數據源的問題,搞的很是的緊張,也和我聊了聊天,大概的瞭解了他的業務的需求。通常的狀況下咱們都是使用SSH或者SSM框架進行處理咱們的數據源的信息。
操做數據通常都是在DAO層進行處理,能夠選擇直接使用JDBC進行編程(http://blog.csdn.net/yanzi1225627/article/details/26950615/)
或者是使用多個DataSource 而後建立多個SessionFactory,在使用Dao層的時候經過不一樣的SessionFactory進行處理,不過這樣的入侵性比較明顯,通常的狀況下咱們都是使用繼承HibernateSupportDao進行封裝了的處理,若是多個SessionFactory這樣處理就是比較的麻煩了,修改的地方估計也是蠻多的
最後一個,也就是使用AbstractRoutingDataSource的實現類經過AOP或者手動處理實現動態的使用咱們的數據源,這樣的入侵性較低,很是好的知足使用的需求。好比咱們但願對於讀寫分離或者其餘的數據同步的業務場景

下面看看圖片

css


單數據源的場景(通常的Web項目工程這樣配置進行處理,就已經比較可以知足咱們的業務需求)

多數據源多SessionFactory這樣的場景,估計做爲剛剛開始想象想處理在使用框架的狀況下處理業務,配置多個SessionFactory,而後在Dao層中對於特定的請求,經過特定的SessionFactory便可處理實現這樣的業務需求,不過這樣的處理帶來了不少的不便之處,全部不少狀況下咱們寧願直接使用封裝的JDBC編程,或者使用Mybatis處理這樣的業務場景
使用AbstractRoutingDataSource 的實現類,進行靈活的切換,能夠經過AOP或者手動編程設置當前的DataSource,不用修改咱們編寫的對於繼承HibernateSupportDao的實現類的修改,這樣的編寫方式比較好,至於其中的實現原理,讓我細細到來。咱們想看看如何去應用,實現原理慢慢的說!

編寫AbstractRoutingDataSource的實現類,HandlerDataSource就是提供給咱們動態選擇數據源的數據的信息,咱們這裏編寫一個根據當前線程來選擇數據源,而後經過AOP攔截特定的註解,設置當前的數據源信息,也能夠手動的設置當前的數據源,在編程的類中。

mysql

示例:spring

1.加個依賴sql

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
       <version>1.3.1</version> </dependency>

2.application.properties配置文件數據庫

複製代碼
#主從數據庫
master.db.driverClassName=com.mysql.jdbc.Driver master.db.url=jdbc:mysql://localhost:3306/cbd?characterEncoding=UTF-8&useUnicode=true&useSSL=false master.db.username=root master.db.password=admin slave.db.driverClassName=com.mysql.jdbc.Driver slave.db.url=jdbc:mysql://localhost:3306/cbd_test?characterEncoding=UTF-8&useUnicode=true&useSSL=false slave.db.username=root slave.db.password=admin mybatis.config-location= classpath:config/mybatis-config.xml mybatis.mapper-locations=classpath:mapper/**/*.xml
複製代碼

 

3.禁用springboot默認加載數據源配置編程

複製代碼
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) public class Application { public static void main(String[] args) throws Exception { SpringApplication.run(Application.class, args); } }
複製代碼

4.數據源配置類springboot

複製代碼
/** * 主數據源 */ @Configuration @ConfigurationProperties(prefix = "master.db") public class MasterDataSourceConfig { private String url; private String username; private String password; private String driverClassName; }
複製代碼
複製代碼
/** * 從數據源配置 */ @Configuration @ConfigurationProperties(prefix = "slave.db") public class SlaveDataSourceConfig { private String url; private String username; private String password; private String driverClassName; }
複製代碼
複製代碼
/** * 數據源配置類 */ @Configuration public class DataSourceComponent { @Resource private MasterDataSourceConfig masterDataSourceConfig; @Resource private SlaveDataSourceConfig slaveDataSourceConfig;  @Bean(name = "master") public DataSource masterDataSource() { DataSource dataSource = new DataSource(); dataSource.setUrl(masterDataSourceConfig.getUrl()); dataSource.setUsername(masterDataSourceConfig.getUsername()); dataSource.setPassword(masterDataSourceConfig.getPassword()); dataSource.setDriverClassName(masterDataSourceConfig.getDriverClassName()); return dataSource; }  @Bean(name = "slave") public DataSource slaveDataSource() { DataSource dataSource = new DataSource(); dataSource.setUrl(slaveDataSourceConfig.getUrl()); dataSource.setUsername(slaveDataSourceConfig.getUsername()); dataSource.setPassword(slaveDataSourceConfig.getPassword()); dataSource.setDriverClassName(slaveDataSourceConfig.getDriverClassName()); return dataSource; } @Primary//不加這個會報錯。 @Bean(name = "multiDataSource") public MultiRouteDataSource exampleRouteDataSource() { MultiRouteDataSource multiDataSource = new MultiRouteDataSource(); Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put("master", masterDataSource()); targetDataSources.put("slave", slaveDataSource()); multiDataSource.setTargetDataSources(targetDataSources); multiDataSource.setDefaultTargetDataSource(masterDataSource()); return multiDataSource; }
/**
     * 根據數據源建立SqlSessionFactory
     */
    @Bean
    public SqlSessionFactory sqlSessionFactory(@Qualifier("ngDataSource") DataSource ngDataSource,
            @Qualifier("tzDataSource") DataSource tzDataSource, @Qualifier("ghDataSource") DataSource ghDataSource)
            throws Exception {
        SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
        fb.setDataSource(this.dataSource());// 指定數據源(這個必須有,不然報錯)
        // 下邊兩句僅僅用於*.xml文件,若是整個持久層操做不須要使用到xml文件的話(只用註解就能夠搞定),則不加
        fb.setTypeAliasesPackage("com.dxz.multidatasources.mapper");//指定基包
        // String[] mybatisMapperLocations = {"classpath*:/orm/*.xml", "classpath*:/orm/**/*.xml"};
        fb.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:/orm/*.xml"));//

        return fb.getObject();
    }

    /**
     * 配置事務管理器
     */
    @Bean
    public DataSourceTransactionManager transactionManager(DynamicDataSource dataSource) throws Exception {
        return new DataSourceTransactionManager(dataSource);
    }

    /**
     * 註冊ServletRegistrationBean
     *
     * @return
     */
    @Bean
    public ServletRegistrationBean druidServlet() {
        ServletRegistrationBean reg = new ServletRegistrationBean();
        reg.setServlet(new StatViewServlet());
        reg.addUrlMappings("/druid/*");
        reg.addInitParameter("allow", ""); // 白名單 return reg;
        reg.addInitParameter("loginUsername", "duan");
        reg.addInitParameter("loginPassword", "123456");
        reg.addInitParameter("resetEnable", "false");
        return reg;
    }

    /**
     * 註冊FilterRegistrationBean
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new WebStatFilter());
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        filterRegistrationBean.addInitParameter("profileEnable", "true");
        filterRegistrationBean.addInitParameter("principalCookieName", "USER_COOKIE");
        filterRegistrationBean.addInitParameter("principalSessionName", "USER_SESSION");
        filterRegistrationBean.addInitParameter("DruidWebStatFilter", "/*");
        return filterRegistrationBean;
    } }
複製代碼

5.數據源上下文mybatis

複製代碼
/** * 數據源上下文 */ public class DataSourceContext { private static final ThreadLocal<String> contextHolder = new ThreadLocal<>(); public static void setDataSource(String value) { contextHolder.set(value); } public static String getDataSource() { return contextHolder.get(); } public static void clearDataSource() { contextHolder.remove(); } }
複製代碼

6.DataSource路由類app

複製代碼
/* * 重寫的函數決定了最後選擇的DataSource */ public class MultiRouteDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { //經過綁定線程的數據源上下文實現多數據源的動態切換,有興趣的能夠去查閱資料或源碼 return DataSourceContext.getDataSource(); } }
複製代碼

 

7.使用,修改上下文中的數據源就能夠切換本身想要使用的數據源了。框架

複製代碼
    public UserVO findUser(String username) { DataSourceContext.setDataSource("slave"); UserVO userVO = userMapper.findByVO(username); System.out.println(userVO.getName()+"====================="); return null; }
複製代碼

這種是在業務中使用代碼設置數據源的方式,也可使用AOP+註解的方式實現控制,方法多多!

問題

有的springboot項目啓動時報錯:

***************************
APPLICATION FAILED TO START
***************************

Description:

Cannot determine embedded database url for database type NONE

Action:

If you want an embedded database please put a supported one on the classpath. If you have database settings to be loaded from a particular profile you may need to active it (no profiles are currently active).

 

解決辦法:

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})

 

參考:

https://blog.csdn.net/hengyunabc/article/details/78762097

相關文章
相關標籤/搜索