Spring Boot教程十四:基于自定义注解的AOP数据源自动切换

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wang_shuyu/article/details/81384481

上一篇文章讲到了多数据源的配置和手动切换,手动切换费时费力,下面我们改进一下,改成基于注解的AOP数据源自动切换。

基础知识不在赘述,直接上代码:


public class DataSourceContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
    /*
      *管理所有的数据源id;
      *主要是为了判断数据源是否存在;
      */
    public static List<String> dataSourceIds = new ArrayList<String>();

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

    public static String getDataSourceType() {
        return contextHolder.get();
    }

    public static void clearDataSourceType() {
        contextHolder.remove();
    }

    public static boolean containsDataSourceType(String DataSourceId) {
        return dataSourceIds.contains(DataSourceId);
    }
}



/**
 * @author Shuyu.Wang
 * @package:com.ganinfo.datasource
 * @className:
 * @description:动态数据源(需要继承AbstractRoutingDataSource)
 * @date 2018-08-01 15:47
 **/

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        System.err.println("DataSourceContextHolder.getDataSourceType()       "+DataSourceContextHolder.getDataSourceType());
        return DataSourceContextHolder.getDataSourceType();
    }
}




@Aspect
@Order(-10)//保证该AOP在@Transactional之前执行
@Component
public class DynamicDataSourceAspect {

    @Pointcut("@annotation(com.ganinfo.datasource.TargetDataSource)")
    public void annotationPointcut(){
        System.out.println("======我是一个切入点");
    };
    /*
        * @Before("@annotation(ds)")
        *的意思是:
        * @Before:在方法执行之前进行执行:
        * @annotation(targetDataSource):
        *会拦截注解targetDataSource的方法,否则不拦截;
        */
    @Before("annotationPointcut()")
    public void changeDataSource(JoinPoint joinPoint) throws Throwable {
        MethodSignature signature=(MethodSignature) joinPoint.getSignature();
        Method method=signature.getMethod();
        TargetDataSource targetDataSource=method.getAnnotation(TargetDataSource.class);
        System.out.println("注解的拦截方法名注解内容前:"+targetDataSource.value());
        //获取当前的指定的数据源;
        String dsId = targetDataSource.value();
        //如果不在我们注入的所有的数据源范围之内,那么输出警告信息,系统自动使用默认的数据源。
        if (!DataSourceContextHolder.containsDataSourceType(dsId)) {
            System.err.println("数据源[{}]不存在,使用默认数据源 > {}" + targetDataSource.value() + joinPoint.getSignature());
        } else {
            System.out.println("UseDataSource : {} > {}" + targetDataSource.value() +"====="+ joinPoint.getSignature());
            //找到的话,那么设置到动态数据源上下文中。
            DataSourceContextHolder.setDataSourceType(targetDataSource.value());
            System.out.println("数据源切换为:"+ DataSourceContextHolder.getDataSourceType());
        }
    }

    @After("annotationPointcut()")
    public void restoreDataSource(JoinPoint joinPoint) {
        MethodSignature signature=(MethodSignature) joinPoint.getSignature();
        Method method=signature.getMethod();
        TargetDataSource targetDataSource=method.getAnnotation(TargetDataSource.class);
        System.out.println("注解的拦截方法名注解内容前:"+targetDataSource.value());
        //方法执行完毕之后,销毁当前数据源信息,进行垃圾回收。
        DataSourceContextHolder.clearDataSourceType();

        System.out.println("lear后:"+ DataSourceContextHolder.getDataSourceType());

    }


}



@Configuration // 该注解类似于spring配置文件
public class MyBatisConfig {
    @Autowired
    private Environment env;

    private String MYBATIS_CONFIG = "config/mybatis.xml";

    /**
     * 创建数据源(数据源的名称:方法名可以取为XXXDataSource(),XXX为数据库名称,该名称也就是数据源的名称)
     */
    @Bean("masterDbDataSource")
    public DataSource masterDbDataSource() throws Exception {
        Properties props = new Properties();
        props.put("driverClassName", env.getProperty("spring.datasource.driverClassName"));
        props.put("url", env.getProperty("spring.datasource.url"));
        props.put("username", env.getProperty("spring.datasource.username"));
        props.put("password", env.getProperty("spring.datasource.password"));
        return DruidDataSourceFactory.createDataSource(props);
    }

    @Bean("slaveDb2DataSource")
    public DataSource slaveDb2DataSource() throws Exception {
        Properties props = new Properties();
        props.put("driverClassName", env.getProperty("custom.datasource.ds1.driverClassName"));
        props.put("url", env.getProperty("custom.datasource.ds1.url"));
        props.put("username", env.getProperty("custom.datasource.ds1.username"));
        props.put("password", env.getProperty("custom.datasource.ds1.password"));
        return DruidDataSourceFactory.createDataSource(props);
    }

    /**
     * @Primary 该注解表示在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是让@autowire注解报错
     * @Qualifier 根据名称进行注入,通常是在具有相同的多个类型的实例的一个注入(例如有多个DataSource类型的实例)
     */
    @Bean
    @Primary
    public DynamicDataSource dataSource(@Qualifier("masterDbDataSource") DataSource masterDbDataSource, @Qualifier("slaveDb2DataSource") DataSource slaveDb2DataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("masterDbDataSource", masterDbDataSource);
        targetDataSources.put("slaveDb2DataSource", slaveDb2DataSource);
        DataSourceContextHolder.dataSourceIds.add("masterDbDataSource");
        DataSourceContextHolder.dataSourceIds.add("slaveDb2DataSource");
        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSources);// 该方法是AbstractRoutingDataSource的方法
        dataSource.setDefaultTargetDataSource(slaveDb2DataSource);// 默认的datasource设置为masterDbDataSource
        return dataSource;
    }

    /**
     * 根据数据源创建SqlSessionFactory
     */
    @Bean
    public SqlSessionFactory sqlSessionFactory(@Qualifier("masterDbDataSource") DataSource masterDbDataSource, @Qualifier("slaveDb2DataSource") DataSource slaveDb2DataSource) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();

        // 下边两句仅仅用于*.xml文件,如果整个持久层操作不需要使用到xml文件的话(只用注解就可以搞定),则不加
     /*   sqlSessionFactoryBean.setTypeAliasesPackage(env.getProperty("mybatis.typeAliasesPackage"));// 指定基包
        sqlSessionFactoryBean.setMapperLocations(
                new PathMatchingResourcePatternResolver().getResources(env.getProperty("mybatis.mapperLocations")));//*/
        /** 设置mybatis configuration 扫描路径 */
        System.out.println(env.getProperty("mybatis.configLocation"));
        sqlSessionFactoryBean.setConfigLocation(new ClassPathResource(MYBATIS_CONFIG));
        // 指定数据源(这个必须有,否则报错)
        sqlSessionFactoryBean.setDataSource(this.dataSource(masterDbDataSource, slaveDb2DataSource));
        /** 添加mapper 扫描路径 */
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(env.getProperty("mybatis.mapperLocations")));
        return sqlSessionFactoryBean.getObject();
    }

    /**
     * 配置事务管理器
     */
    @Bean
    public DataSourceTransactionManager transactionManager(DynamicDataSource dataSource) throws Exception {
        return new DataSourceTransactionManager(dataSource);
    }
}


/**
 * @author Shuyu.Wang
 * @package:com.ganinfo.datasource
 * @className:
 * @description:在方法上使用,用于指定使用哪个数据源
 * @date 2018-08-01 20:56
 **/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
    String value();
}




package com.ganinfo.datasource.mapper;

import com.ganinfo.datasource.TargetDataSource;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;

/**
 * @author Shuyu.Wang
 * @package:com.ganinfo.datasource.mapper
 * @className:
 * @description:
 * @date 2018-08-01 16:03
 **/
@Mapper
public interface Test {

    @TargetDataSource(value ="masterDbDataSource")
    @Select("SELECT name FROM t_user limit 1")
    List<Map<String,Object>> getList();


    @TargetDataSource(value ="slaveDb2DataSource")
    @Select("SELECT * FROM t_base_place limit 1")
    List<Map<String,Object>> getList2();
}




@Service
public class TestService {
    @Autowired
    private Test test;

    @TargetDataSource(value = "masterDbDataSource")
   public  List<Map<String, Object>> getList() {
        return test.getList();
    }


//    @TargetDataSource(value = "ds1")
    @TargetDataSource(value ="slaveDb2DataSource")
    public  List<Map<String, Object>> getList2() {
        return test.getList2();
    }


}







#主数据源,默认的
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url = jdbc:mysql://172.16.1.28:3306/closedauth?zeroDateTimeBehavior=convertToNull&amp;useUnicode=true&amp;characterEncoding=utf-8
spring.datasource.username = cweserver
spring.datasource.password = cweserveryzhh

#更多数据源
custom.datasource.names=ds1
custom.datasource.ds1.driverClassName = com.mysql.jdbc.Driver
custom.datasource.ds1.url = jdbc:mysql://172.16.1.28:3306/closeddata?zeroDateTimeBehavior=convertToNull&amp;useUnicode=true&amp;characterEncoding=utf-8
custom.datasource.ds1.username = cweserver
custom.datasource.ds1.password = cweserveryzhh

这里切记注解要在service方法中加,不能再mapper中添加,不然无法被aop拦截;

测试方法如下:


@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class TestTest {
    @Autowired
    private com.ganinfo.datasource.mapper.Test test;
    @Autowired
    private TestService testService;

    @Test
    public void getList2() throws Exception {
        List<Map<String, Object>> list = testService.getList2();
        System.out.println(GsonUtil.GsonString(list));
//        log.info("");
    }
    @org.junit.Test
    public void getList() throws Exception {
        List<Map<String, Object>> list = testService.getList();
        System.out.println(GsonUtil.GsonString(list));
    }



}

猜你喜欢

转载自blog.csdn.net/wang_shuyu/article/details/81384481