Spring動態切換數據源及事務


  前段時間花了幾天來解決公司框架ssm上事務問題。如果不動態切換數據源話,直接使用spring的事務配置,是完全沒有問題的。由於框架用於各個項目的快速搭建,少去配置各個數據源配置xml文件等。采用了動態切換數據源方式。在解決問題的時候查看了相關源代碼等。接下來對動態數據源切換、事務相關的核心源代碼個分析總結,總結不到位,請諒解。

第一、實現動態切換數據源

       思路大概如下:具體切換到哪個數據源通過包名來控制,寫一個類實現使用spring提供MethodInterceper接口,再通過aop來切面到service,這樣來確定我們需要的數據源。

我們通過在properties文件中配置相關數據連接配置:如:

 

 

在公用工程中只需簡單配置sys系統庫的數據源配置,因為任何工程都需要這個基礎的數據源,如下圖:

 

在properties文件中,我們配置了3個數據庫的連接,其中sys系統庫已經配置了,那么其他兩個庫數據源沒有在xml中配置,我們可以通過啟動項目后由spring給我們自動創建相關的bean,spring的強大之處哦。如下面這段代碼是創建相關數據源bean的代碼:

/** 
 * <Description> 
 *  動態生成其他數據源bean,並且注冊到spring容器中。
 *  
 *  實現spring ApplicationContextAware接口,spring在實例化此bean時候會自動調用setApplicationContext方法,
 *  這樣此bean就具有拿到容器,那么你想怎么搞就怎么搞了。
 *  
 *  實現InitializingBean接口,我們知道在初始化bean的時候,會自動調用afterPropertiesSet方法,在spring源碼中有大量
 *  實現了此接口的類。可以看出spring的為我們提供了強大的擴展性
 * @author 蘭偉
 * @CreateDate 2018年6月9日 上午12:07:36
 * @since V1.0
 */
public class DataSources implements ApplicationContextAware, InitializingBean {  
	private static Logger logger = Logger.getLogger(DataSources.class);
	public static String DEFAULT_DATASOURCE="ds.sys";
	private ApplicationContext context;
	
	@Override
	public void setApplicationContext(ApplicationContext context)
			throws BeansException {
		this.context = context;
	}
	
    @Override
    public void afterPropertiesSet() {
        try {
            regDynamicBean();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

	private void regDynamicBean() throws IOException{
	    //CustomPropertyConfigurer中讀取了properties配置文件
		Map<String, Object> pmap = CustomPropertyConfigurer.getctxPropertiesMap();
		Map<String, DataSourceInfo> mapCustom = getDbSourceInfo(pmap);
		// 把數據源bean注冊到容器中
		addSourceBeanToApp(mapCustom);
		
	}
	@SuppressWarnings("unchecked")
    private void addSourceBeanToApp(Map<String, DataSourceInfo> mapCustom) {
		DefaultListableBeanFactory acf = (DefaultListableBeanFactory) context.getAutowireCapableBeanFactory();
		BeanDefinition beanDefinition;
		Iterator<String> iter = mapCustom.keySet().iterator();
		while(iter.hasNext()){
			String dataSourceId = iter.next();
			// 得到Bean定義,並添加到容器中
			beanDefinition = new ChildBeanDefinition(DEFAULT_DATASOURCE);
			// 注意:必須先注冊到容器中,再得到Bean進行修改,否則數據源屬性不能有效修改
			acf.registerBeanDefinition(dataSourceId, beanDefinition);
			// 再得到數據源Bean定義,並修改連接相關的屬性
			DruidDataSource cpds = (DruidDataSource)context.getBean( dataSourceId);
			cpds.setUrl(mapCustom.get(dataSourceId).connUrl);
			cpds.setUsername(mapCustom.get(dataSourceId).userName);
			cpds.setPassword(mapCustom.get(dataSourceId).password);
			cpds.setDriverClassName(mapCustom.get(dataSourceId).driverClass);
			if(cpds.getDriverClassName().indexOf( "jtds" )!=-1||cpds.getDriverClassName().indexOf( "sqlserver" )!=-1){
				cpds.setValidationQuery("select 1");
			}else if( cpds.getDriverClassName().indexOf( "oracle" )!=-1 ){
				cpds.setValidationQuery("SELECT 'x' FROM dual");
			}
			((Map<String, Object>) SpringUtils.getBean("targetDataSources")).put(dataSourceId, cpds);
		}
	}
	   public Map<String, DataSourceInfo> getDbSourceInfo(Map<String, Object> pmap,boolean includeSys) throws IOException {
	        Map<String, DataSourceInfo> mapDataSource = new HashMap<String,DataSourceInfo>();
	        String source = (String) pmap.get("db.source");
	        if(source==null || source.equals("jndi")){
	            return mapDataSource;
	        }
	        Matcher matcher;
	        Pattern pattern = Pattern.compile("^(\\w+\\.\\w+)\\.jdbc\\.(url|username|password|driverclass|validationQuery)$");
	        for(Map.Entry<String, Object> entry : pmap.entrySet()) {
	            String keyProp = entry.getKey();
	            String valueProp = entry.getValue().toString(); 
	            matcher = pattern.matcher(keyProp );
	            if(matcher.find()){
	                String dsName = matcher.group(1);
	                if(StringUtils.equals(dsName, DEFAULT_DATASOURCE) && !includeSys){
	                    continue;
	                }
	                String dsPropName = matcher.group(2);
	                DataSourceInfo dsi;
	                if(mapDataSource.containsKey(dsName)){
	                    dsi = mapDataSource.get(dsName);
	                }
	                else{
	                    dsi = new DataSourceInfo();
	                }
	                // 根據屬性名給數據源屬性賦值
	                if("url".equals(dsPropName)){
	                    dsi.connUrl = valueProp;
	                }else if("username".equals(dsPropName)){
	                    dsi.userName = valueProp;
	                }else if("password".equals(dsPropName)){
	                    dsi.password = valueProp;
	                }else if("driverclass".equals(dsPropName)){
	                    dsi.driverClass = valueProp;
	                }else if("validationQuery".equals(dsPropName)){
	                    dsi.validationQuery = valueProp;
	                }
	                mapDataSource.put(dsName, dsi);
	            }
	        }
	        return mapDataSource;
	}
	public  Map<String, DataSourceInfo> getDbSourceInfo(Map<String, Object> pmap) throws IOException {
		return getDbSourceInfo(pmap,false);
	}
	
	public class DataSourceInfo{
		public String connUrl;
		public String userName;
		public String password;
		public String driverClass;
		public String validationQuery;
	}
}

  

在上面我們已經實現了各個數據源。接下來就是根據包名來動態切換數據源的問題了。如何切換,我們最好不要在業務代碼中來寫什么DataSourceContextHolder.setDbType(”ds.sms”)這樣的代碼。我們可以通過spring提供MethodIntercepter接口,采用aop的方式來。

及spring的相關配置

Spring中提供AbstractRoutingDataSource抽象類,重寫determineCurrentLookupKey方法,當需要查詢數據的時候會自動切換到指定數據庫,核心代碼如下

 

DataSourceContextHolder核心代碼,在這里有個局部線程變量,這是和線程綁定起來。我們在寫代碼的時候一定要注意,不要隨意開啟新線程,否則是不起作用的哦,除非重新在設置一把。

動態切換數據源主要工作已完成.

第二、接下來看看事務以及mybatis與spring整合核心配置

在配置中我們可以看到自己去實現了一套DynamicSqlSessionTemplate、DynamicSqlSessionFactoryBean、CustomDataSourceTransactionManager。這幾個是修改后的。在之前的代碼中,我們還是采用自帶的SqlSessionTemplate、SqlSessionFactoryBean、DataSourceTransactionManager,如果使用之前的,在看下datasource的配置,這個是系統庫,那么切換數據源后,兩個DataSource就不一致的。這就會導致同一個線程中始終是無法獲取到由spring管理的事務相關設置。所以想要保證事務的話,必須要datasource是同一個。所以自己就實現了一個DataSourceTransactionManager,修改了getDataSource方法等。

Mybatis與spring整合事務相關的核心源碼可以查看

TransactionInterceptor事務攔截器,繼承了TransactionAspectSupport類

TransactionAspectSupport

 

AbstractPlatformTransactionManager

SqlSessionUtils  有個核心方法getSqlSession,這個就需要從當前線程中去獲取sqlSesson

DataSourceTransactionManager 事務管理 繼承了AbstractPlatformTransactionManager。

SqlSessionFactoryBean

SqlSessionTemplate這里面有個核心SqlSessionInterceptor攔截器,其實也是個代理通過代理模式來。

這么幾個核心類。

 

有篇博客轉門對DataSourceTransactionManager 核心的幾個類做了分析

做了源碼分析https://www.cnblogs.com/chihirotan/p/6739748.html,大家可以參考下。

 

在解決問題之前沒有找到他的這篇文章,害的我挨着擼了一把spring及mybatis-spring的這塊的源碼。如果早看到的話,估計時間會少花點。


免責聲明!

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



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