zoukankan      html  css  js  c++  java
  • 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的这块的源码。如果早看到的话,估计时间会少花点。

  • 相关阅读:
    [Python] Unofficial Windows Binaries for Python Extension Packages
    [SublimeText] 之 Packages
    [Windows] Windows 8.x 取消触摸板切换界面
    [Shell] Backtick vs $() 两种方式内嵌值
    [OSX] 在 OS X 中安装 MacPorts 指南
    [OSX] 使用 MacPorts 安装 Python 和 pip 指南
    关于 g++ 编译器
    梦想成真,喜获微软MVP奖项,微软MVP FAQ?
    拥抱.NET Core,如何开发一个跨平台类库 (1)
    拥抱.NET Core,学习.NET Core的基础知识补遗
  • 原文地址:https://www.cnblogs.com/lanweijava/p/9158161.html
Copyright © 2011-2022 走看看