多数据源的配置

场景如下:

现在使用的是spring+mybatis+mysql 数据源只有一个,mysql的一个库;现在因为其中一个表dau_baseinfo的数据量太大,千万级别。页面查询实在太慢,所以准备把dau_baseinfo表迁移到clickhouse,此时就需要再引入一个数据源,即clickhouse对应的数据源

下面开始配置多数据源

第一步:创建一个DynamicDataSource的类,继承AbstractRoutingDataSource并重写determineCurrentLookupKey方法,代码如下:

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        // 从自定义的位置获取数据源标识
        return DynamicDataSourceHolder.getDataSource();
    }
}

第二步:创建DynamicDataSourceHolder用于持有当前线程中使用的数据源标识,代码如下:

public class DynamicDataSourceHolder {
    /**
     * 注意:数据源标识保存在线程变量中,避免多线程操作数据源时互相干扰
     */
    private static final ThreadLocal THREAD_DATA_SOURCE = new ThreadLocal();
    public static String getDataSource() {
        return THREAD_DATA_SOURCE.get();
    }

    public static void setDataSource(String dataSource) {
        THREAD_DATA_SOURCE.set(dataSource);
    }

    public static void clearDataSource() {
        THREAD_DATA_SOURCE.remove();
    }
}

第三步:增加clickhouse对应常量配置

在application.properties中增加如下clickhouse的配置:

#clickhouse
clickhouse.driver=ru.yandex.clickhouse.ClickHouseDriver
#test
clickhouse.url=jdbc:clickhouse://192.168.*.86:8123/bi

#online
#clickhouse.url=jdbc:clickhouse://172.18.**.250:8123/bi

clickhouse.username=****
clickhouse.password=****
#连接池初始化时创建的连接数
clickhouse.pool.minIdle=5
#最大空闲连接:连接池中容许保持空闲状态的最大连接数量,超过空闲连接将被标记为不可用,然后被释放
clickhouse.pool.maxIdle=20
#最大活动连接:连接池在同一时间能够分配的最大活动连接的数量
clickhouse.pool.maxActive=50
#最大等待时间:当没有可用连接时,连接池等待连接被归还的最大时间数(单位毫秒)
clickhouse.pool.maxWait=120000
#连接池中,连接的可空闲的时间,超过就被收回
clickhouse.pool.minEvictableIdleTimeMillis=6000
#标标记是否删除泄漏的连接
clickhouse.pool.removeAbandoned=true
#泄漏的连接可以被删除的超时时间值
clickhouse.pool.removeAbandonedTimeout=6000

 

第四步配置数据源

   
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
    
    
    
        
    

    
      
         
      
  

    
     
         
         
         
         
         
         
         
         
         

         

         

     





    

        

            
                
                
                
            
        
        

        

    
    



        

        

        
        
        
        
          
            
                classpath*:/mybatis/*.xml
                classpath*:/mybatis/*/*.xml
            
        
        
              
                 
                     
                         
                            helperDialect = mysql
                         
                     
                 
             
        
    

      

    
        
        
    
    
   

 

配置已经完成,下面就是对数据源的使用了

public List countDauNew(Map condtiotnMap){
       //此处将数据源指定为dataSource2,即连接clickhouse
        DynamicDataSourceHolder.setDataSource("dataSource2");
        List rhShowInfoList = mapper.countDau(condtiotnMap);
        //此处将数据源重置为dataSource此处防止内存泄漏,所以使用clear
        DynamicDataSourceHolder.clearDataSource();
        return  rhShowInfoList;
}

 

如果觉得以上、方式比较麻烦,可以使用注解的方式:

--------------------------------------------------------------------------------------华丽的分割线--------------------------------------------------------------------

 

但是问题来了

1如果每次切换数据源时都调用DynamicDataSourceHolder.setDataSource("xxx")就显得十分繁琐了,而且代码量大了很容易会遗漏,后期维护起来也比较麻烦。

2正常方式下,是先设置数据源,执行sql,最后clear数据源,使用默认数据源;但是如果执行sql有问题,就会导致clear数据源的代码没有执行,会导致偶尔数据源混乱的问题。

解决方案是使用注解@DataSource("xxx")就指定访问数据源,然后配合aop来解决上面两个问题

首先,我们得定义一个名为Datasource的注解,代码如下:

@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Datasource {
     /** 数据库类型:mysqlBI或clickhouseBI **/
     String datasourceType() default DataSourceConstant.MYSQL_BOSS_BI;
}

上面datasourceType就是实际的数据源,默认可以指定

然后,定义AOP切面以便拦截所有带有注解@Datasource的方法,取出注解的值作为数据源标识放到DynamicDataSourceHolder的线程变量中:

public class DataSourceAspect {

    static org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(DataSourceAspect.class);

    /**
     * 功能描述: 根据注解内容获取对应的数据源
     *
     */
    public void beforeAdvice(JoinPoint joinPoint) {
        try {
            Signature signature = joinPoint.getSignature();
            String methodName = signature.getName();
            MethodSignature methodSignature = (MethodSignature) signature;
            // 被拦截的方法
            Method method = methodSignature.getMethod();
            //获取被拦截方法上面的 @Datasource注解的内容
            if (method.getAnnotation(Datasource.class) == null) {
                return;
            }
            String datasourceType = method.getAnnotation(Datasource.class).datasourceType();
            if(StringUtils.isEmpty(datasourceType)){
                return ;
            }
            DynamicDataSourceHolder.setDataSource(datasourceType);

        } catch (Exception e) {
            // 记录本地异常日志
            logger.error("数据源处理失败", e);
        }

    }

    /**
     * 功能描述: 清除数据源信息,使用默认数据源
     *
     */
    public void afterAdvice(JoinPoint joinPoint) {
        try {
//            Signature signature = joinPoint.getSignature();
//
//            String methodName = signature.getName();
//            MethodSignature methodSignature = (MethodSignature) signature;
//            // 被拦截的方法
//            Method method = methodSignature.getMethod();
//            //获取被拦截方法上面的 @Datasource注解的内容
//            if (method.getAnnotation(Datasource.class) == null) {
//                return;
//            }
            DynamicDataSourceHolder.clearDataSource();
        } catch (Exception e) {
            // 记录本地异常日志
            logger.error("数据源处理失败", e);
        }
    }
}

复制代码

最后在spring配置文件中配置拦截规则就可以了,比如拦截service层或者dao层的所有方法:

    
    
    
        
        
            
            
            
            
            
            
        
    


OK,这样就可以直接在类或者方法上使用注解@Datasource来指定数据源,不需要每次都手动设置了。

 

示例代码如下:

@Service
public class RhCityService {


    @Autowired
    public RhCityMapper mapper;

    /**
    * 功能描述:按照层级查询城市,如果传入为null则查询全部
    *
    * @param:
    * @return:
    * @auther: mazhen
    * @date: 2018/9/29 下午4:08
    */
    @Datasource(datasourceType = DataSourceConstant.MYSQL_BOSS_BI)
    public List listCityByLevel(String cityLevel) {
        HashMap map = new HashMap<>();
        map.put("cityLevel",cityLevel);
        List rhCityDtos = mapper.listCityByLevel(map);
        return rhCityDtos;
    }
}

提示:注解@Datasource既可以加在方法上,也可以加在接口或者接口的实现类上,优先级别:方法>实现类>接口。也就是说如果接口、接口实现类以及方法上分别加了@DataSource注解来指定数据源,则优先以方法上指定的为准。

--------------------------------------------------------------------------------------------——————————————-----------------

2018年10月 最近发现又掉坑里了

发现有的时候aop没有生效,详细如下:

内部方法调用导致的 aop失效问题

见:https://blog.csdn.net/h2604396739/article/details/102610610

--------------------------------------------------------------------------------------------------------------------------------------------------

近两天面试再次入坑,数据源动态切换 + transational 会有效吗?

不会,DataSource动态切换会失效,因为SpringManagedTransaction.getConnection()为空时,会从AbstractRoutingDataSource中获取数据源;但是用了transactional,会发现因为在getConnection之前由会获取到对应的数据源,所以AbstractRoutingDataSource失效。
动态数据源的配置的AOP切片上加入Order(1),让其先执行即可
多个aop可以指定执行顺序,@Order(1)加到切面上 或者 继承ordered方法。
具体可以参考:https://www.cnblogs.com/zhwbqd/p/3757060.html和https://my.oschina.net/HuifengWang/blog/304188

你可能感兴趣的:(框架,spring,多数据源,aop,环绕通知)