springboot基於方法級別注解事務的多數據源切換問題


springBoot多數據源配置

  配置讀數據源

@Component
@ConfigurationProperties(prefix = "jdbc.read")
@PropertySource("classpath:application.properties")
public class ReadDataSource{
        private String userName;
        private String password;
        private String driver;
        private String url;
    
    //TODO 此處應有get set方法
}    

  配置寫數據源  

@Component
@ConfigurationProperties(prefix = "jdbc.read")
@PropertySource("classpath:application.properties")
public class WriteDataSource{
        private String userName;
        private String password;
        private String driver;
        private String url;
    
    //TODO 此處應有get set方法
}  

 

//配置數據源適配器  通過此類的set方法可以動態切換數據源,我們只需出入數據源對應key即可

 

public class DataSourceHolder {

    
    private static final ThreadLocal<String> dataSourceTypes = new ThreadLocal<String>() {
        @Override
        protected String initialValue() {
            return "writeDataSource";
        }
    };

    public static String get() {
        if(StringUtils.isEmpty(dataSourceTypes.get())){
            return "writeDataSource";
        }
        return dataSourceTypes.get();
    }

    public static void set(String dataSourceType) {
        dataSourceTypes.set(dataSourceType);
    }

    public static void reset() {
        dataSourceTypes.set("writeDataSource");
    }

    public static void remove() {
        dataSourceTypes.remove();
    }

}

 

配置多數據源  此處多數據源的動態切換主要就是通過determineCurrentLookupKey獲取對應數據源的key去決定使用哪個數據源

此處需要注意如果處於同一事務中,則數據源不可切換,在事務中,會直接去獲取上一次緩存的數據源,沒有則調用該方法獲取,但只獲取一次,所以有可能會導致數據源切換失敗.后續我們會通過切面去清除緩存數據源.但僅僅是拿到開啟事務第一次獲取的數據源.

 

public class MultipleDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceHolder.get();
    }
    
}

@ConfigurationProperties(prefix = "jdbc.read")此處映射以jdbc.read開頭的配置屬性名和實體類屬性名一致

@PropertySource("classpath:application.properties") 指定從那個屬性配置文件讀取數據源,我的是Maven項目,所以放在resources下

注意:必須要能夠被spring管理起來,所以需要配置到spring掃描路徑.

接下來我們需要一個配置類:配置多數據源

  

//basePackages 指定讀和寫mapper包位置
@Configuration @MapperScan(basePackages
= {"com.xxx.template.dal.mapper.read","com.xxx.template.dal.mapper.write"},sqlSessionTemplateRef = "sqlSessionTemplate") public Class DataSourceConfig{ @AutoWried private ReadDataSource readDataSourceProperties; @AutoWried private ReadDataSource writeDataSourceProperties;

//配置讀數據源屬性 @Bean(destroyMethod = "close") public BasicDataSource readDataSource() { BasicDataSource dataSource = new BasicDataSource(); dataSource.setDriverClassName(readDataSourceProperties.getDriver()); dataSource.setUrl(readDataSourceProperties.getUrl()); dataSource.setUsername(readDataSourceProperties.getUserName()); dataSource.setPassword(readDataSourceProperties.getPassword()); dataSource.setInitialSize(readDataSourceProperties.getInitialSize()); dataSource.setMaxTotal(readDataSourceProperties.getMaxTotal()); dataSource.setMaxIdle(readDataSourceProperties.getMaxIdle()); dataSource.setRemoveAbandonedOnBorrow(true); dataSource.setRemoveAbandonedTimeout(10); dataSource.setMaxWaitMillis(30000); dataSource.setTestWhileIdle(true); dataSource.setTestOnBorrow(false); dataSource.setTestOnReturn(false); dataSource.setValidationQuery("SELECT 1"); dataSource.setTimeBetweenEvictionRunsMillis(30000); dataSource.setNumTestsPerEvictionRun(30); dataSource.setMinEvictableIdleTimeMillis(600000); return dataSource; } //配置寫數據源屬性 @Bean(destroyMethod = "close") public BasicDataSource writeDataSource() { BasicDataSource dataSource = new BasicDataSource(); dataSource.setDriverClassName(writeDataSourceProperties.getDriver()); dataSource.setUrl(writeDataSourceProperties.getUrl()); dataSource.setUsername(writeDataSourceProperties.getUserName()); dataSource.setPassword(writeDataSourceProperties.getPassword()); dataSource.setInitialSize(writeDataSourceProperties.getInitialSize()); dataSource.setMaxTotal(writeDataSourceProperties.getMaxTotal()); dataSource.setMaxIdle(writeDataSourceProperties.getMaxIdle()); dataSource.setRemoveAbandonedOnBorrow(true); dataSource.setRemoveAbandonedTimeout(10); dataSource.setMaxWaitMillis(30000); dataSource.setTestWhileIdle(true); dataSource.setTestOnBorrow(false); dataSource.setTestOnReturn(false); dataSource.setValidationQuery("SELECT 1"); dataSource.setTimeBetweenEvictionRunsMillis(30000); dataSource.setNumTestsPerEvictionRun(30); dataSource.setMinEvictableIdleTimeMillis(600000); return dataSource; }
//配置動態數據源屬性 動態數據源包含讀寫數據源
@Bean
public MultipleDataSource dataSource() {
MultipleDataSource multipleDataSource = new MultipleDataSource();
Map<Object, Object> map = new HashMap<>();
map.put("readDataSource",readDataSource());
map.put("writeDataSource" ,writeDataSource());
  //此處存放多數據源進入map,根據key動態切換
multipleDataSource.setTargetDataSources(map);
return multipleDataSource;
}
//配置sqlSessionFactory
@Bean
public SqlSessionFactoryBean sqlSessionFactory() throws IOException {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource());
//指定mapper.xml的位置
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(DataSourceConfig.MAPPER_LOCATION));
//配置mybatis配置的位置
sqlSessionFactoryBean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource(DataSourceConfig.CONFIG_LOCATION));
return sqlSessionFactoryBean;
}
//配置sqlSessionTemplate
@Bean
public SqlSessionTemplate sqlSessionTemplate() throws Exception {
SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory().getObject());
return sqlSessionTemplate;
}

//配置事務管理
@Bean
public DataSourceTransactionManager transactionManager() {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource());
return dataSourceTransactionManager;
}
}

 

此刻我們數據源已經配好,接下來可以手動切換數據源,通過DataSourceHolder 的各種方法獲取,清除,重置.使用完數據源做好調用清除方法,避免緩存導致無法切換數據源

我們也可以指定一個切面類去動態切換數據源

@Aspect
@Order(-1)
@Component
public class DataSourceSwitch {

@Before("此處填寫切入點表達式")
public void before(){
  切換為讀數據源  
 DataSourceHolder.set("writeDataSource");
}
@Before(
"此處填寫切入點表達式") public void before1(){ 切換為讀數據源 DataSourceHolder.set("readDataSource"); } @After("此處填寫切入點表達式") public void after(){ //移除數據源 DataSourceHolder.remove(); } @After("此處填寫切入點表達式") public void after1(){ //移除數據源 DataSourceHolder.remove(); } }

 

 

在多數據源和事務結合起來的情況下,無法一個事務下切換數據源,因此只能一個事務下指定一個數據源,比如我們想讀和寫,那么最好使用寫數據源,只讀就只指定讀數據源.

最后在我們方法級別加上@Transactional

在啟動類上加@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})排除spring的默認數據源配置

 

 

 

 

 

 

  

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM