使用注解自动切换Spring不同数据源

项目框架:springboot+beetl.(懂原理持久层框架可以随便切换噢=^_^=)。

  1. 连接数据库信息的配置application.properties,这里业务只要求连接两个数据库,所以小编我只配置了两个数据源,配置信息如下.
    # JDBC 
    spring.datasource.primary.url=********
    spring.datasource.primary.username=********
    spring.datasource.primary.password=********
    spring.datasource.primary.driver-class-name=********
    
    #secondarySource
    spring.datasource.secondary.driver-class-name=*********
    spring.datasource.secondary.url=*******
    spring.datasource.secondary.username=****
    spring.datasource.secondary.password=******

        2.将配置信息注入进数据源中,因为有两个数据库连接,所以利用spring注解生成了两个DateSource数据源primaryDataSource和secondaryDataSource,另外还生成了一个动态数据源dataSource,管理

primaryDataSource和secondaryDataSource两个数据源(其实就是把连个数据源对象放到map集合里面,后面我们有用相应的key去读取),代码如下。

@Configuration
public class DataSourceConfig {

    @Bean(name = "primaryDataSource")
    @Primary
    @ConfigurationProperties(prefix="spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "secondaryDataSource")
    @ConfigurationProperties(prefix="spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    /**
     * 
     * @Title: dataSource 
     * @Description:动态数据源,通过AOP自动实现注入
     * @param @param primaryDataSource
     * @param @param secondaryDataSource
     * @param @return    
     * @return DataSource    返回类型 
     * @throws
     */
    @Bean(name = "dataSource")
    public DataSource dataSource(@Qualifier("primaryDataSource") DataSource                 primaryDataSource,@Qualifier("secondaryDataSource") DataSource secondaryDataSource) {
    	DynamicDataSource dynamicDataSource = new DynamicDataSource();
        // 默认数据源
        dynamicDataSource.setDefaultTargetDataSource(primaryDataSource());
        // 配置多数据源
        Map<Object, Object> dsMap = new HashMap(2);
        dsMap.put("primary", primaryDataSource());
        dsMap.put("second", secondaryDataSource());
        dynamicDataSource.setTargetDataSources(dsMap);
        return dynamicDataSource;
    }
}

        3.接下来就是讲解一个重要的自定义数据源类DynamicDataSource,继承抽象类AbstractRoutingDataSource,它的作用其实很简单,就是保存我们想要保存的数据源,然后从中获取到,覆盖AbstractRoutingDataSource的determineTargetDataSource方法,方法的作用是给我们设置dsMap的key,然后AbstractRoutingDataSource里面有自己对应的determineTargetDataSource方法获取到,代码如下.

/** 
 * 定义动态数据源,实现通过集成Spring提供的AbstractRoutingDataSource,只需要实现determineCurrentLookupKey方法即可 
 *  
 * 由于DynamicDataSource是单例的,线程不安全的,所以采用ThreadLocal保证线程安全,由DynamicDataSourceHolder完成。 
 *  
 * @author zhengjuntao 
 * 
 */ 
public class DynamicDataSource extends AbstractRoutingDataSource {

	/* (non-Javadoc)
	 * @see org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource#determineCurrentLookupKey()
	 */
	@Override
	protected Object determineCurrentLookupKey() {
		// TODO Auto-generated method stub
		//使用DynamicDataSourceHolder保证线程安全,并且得到当前数据中的数据源key
		System.err.println(DynamicDataSourceHolder.getDataSourceKey());
		return DynamicDataSourceHolder.getDataSourceKey();
	}

}
	
  //AbstractRoutingDataSource里面已经封装好的代码,这里贴出是为了读者们能更好的理解它的原理。
  protected DataSource determineTargetDataSource() {
		Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
		Object lookupKey = determineCurrentLookupKey();
		DataSource dataSource = this.resolvedDataSources.get(lookupKey);
		if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
			dataSource = this.resolvedDefaultDataSource;
		}
		if (dataSource == null) {
			throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
		}
		return dataSource;
	}

determineTargetDataSource方法先调用determineCurrentLookupKey()方法看开发者是否保存key,假如没有则使用默认的dataSouece对象,假如保存则返回key对应的dataSource对象。

下图是一个类似容器的代码,操作key对象.

/**
 * @Description:使用ThreadLocal技术来记录当前线程中的数据源的key
 * @author zhengjuntao@hjtechcn.cn
 * @Since:2017年10月26日
 * @Version:1.1.0
 */
public class DynamicDataSourceHolder {
	//写库对应的数据源key  
    private static final String PRIMARY = "primary";  
  
    //读库对应的数据源key  
    private static final String SECOND = "second";  
      
    //使用ThreadLocal记录当前线程的数据源key  
    private static final ThreadLocal<String> holder = new ThreadLocal<String>(); 
    
    //默认数据源
    public static final String DEFAULT_DS = "primary";
  
    /** 
     * 设置数据源key 
     * @param key 
     */  
    public static void putDataSourceKey(String key) {  
        holder.set(key);  
    }  
  
    /** 
     * 获取数据源key 
     * @return 
     */  
    public static String getDataSourceKey() {  
        return holder.get();  
    }  
      
    /** 
     * 标记第一库 
     */  
    public static void markPrimary(){  
    	System.err.println("Primary");
        putDataSourceKey(PRIMARY);  
    }  
      
    /** 
     * 标记第二库 
     */  
    public static void markSecond(){
    	System.err.println("SECOND");
        putDataSourceKey(SECOND);  
    }  
    
    /** 
    * 清楚数据名
    */
    public static void clearDB() {
    	holder.remove();
    }
}

这里DynamicDataSourceHolder有个重要的容器类ThreadLocal,每个ThreadLocal可以放一个线程级别的变量,但是它本身可以被多个线程共享使用,而且又可以达到线程安全的目的,且绝对线程安全.(稍微讲解一下threadlocal原理:其实每个Thread对象里面会有一个threadlocalMap对象变量名叫inheritableThreadLocals,并在调用Threadlocal的set方法中初始化此threadlocalMap集合,再以threadlocal对象地址是key,用户存储的对象作为value保存到threadlocalMap集合中,从而保证每个线程有自己单独的变量,与其说是技术,展现更多的是一种技巧,详细参考链接http://blog.csdn.net/coslay/article/details/38293689)。

        4.这里我使用自定义注解的方式来判断需要注入进方法的dataSource,使用方法如下

@Second
	public PageResults findFeedPageList(Map<String,String> params, PageResults pageResults) {
		PageQuery query = new PageQuery(pageResults.getPageNo(), pageResults.getPageSize());
		query.setParas(params);
		query.setOrderBy(params.get("orderBy").toString());
		sqlManager.pageQuery("tbFeedback.findPageList", Map.class, query);
		pageResults.setTotalCount((int) query.getTotalRow());
		pageResults.setPageCount((int) query.getTotalPage());
		pageResults.setResults(query.getList());
		return pageResults;
	}

关于aop方法切面程序如下,

/**
 * @Description:Aop动态切换数据源逻辑
 * @author zhengjuntao@hjtechcn.cn
 * @Since:2017年10月26日
 * @Version:1.1.0
 */
@Aspect
@Component
public class DynamicDataSourceAspect {
	 public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
	 
	@Before("@annotation(Second)")
    public void beforeSwitchSecond(JoinPoint point){
        //获得当前访问的class
        Class<?> className = point.getTarget().getClass();
        //获得访问的方法名
        String methodName = point.getSignature().getName();
        //得到方法的参数的类型
        Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();
        String dataSource = DynamicDataSourceHolder.DEFAULT_DS;
        try {
            // 得到访问的方法对象
            Method method = className.getMethod(methodName, argClass);
            //判断是否存在@SECOND注解
           if (method.isAnnotationPresent(Second.class)) {
            	Second annotation = method.getAnnotation(Second.class);
                // 取出注解中的数据源名
                dataSource = annotation.value();
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("动态注入切面出错"+e.getMessage());
        }
        // 切换数据源
        DynamicDataSourceHolder.putDataSourceKey(dataSource);;
		System.out.println("进入切面");
    }


    @After("@annotation(Second)")
    public void afterSwitchSecond(JoinPoint point){
    	DynamicDataSourceHolder.clearDB();
    }
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Second {
	String value() default Constant.SECOND;
}

这里有一个坑,Second注解类要与DynamicDataSourceAspect出现在同一个Package下,不然会出现扫描不到对应的注解的情况。

/**
 * @Description:
 * @author zhengjuntao@hjtechcn.cn
 * @Since:2017年10月26日
 * @Version:1.1.0
 */
public class Constant {
	//库一
	public static final String PRIMARY="primary";
	//库二
	public static final String SECOND="second";
}

切面的目的是获取到注解里的value属性,然后保存进threadlocal对象中,给后面动态数据源注入提前设置好需要的key.

        5.如果觉得这篇博客对你有帮助,千万不要吝啬你手中的赞。如果觉得有什么疑惑或写的不清楚的地方又或者不对的地方,欢迎留言 ~

 

 

  参考博客:threadlocal原理     http://blog.csdn.net/coslay/article/details/38293689

转载于:https://my.oschina.net/u/3343526/blog/1557221

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值