TienChin项目学习—自定义动态数据源注解

102 阅读2分钟

学习江南一点雨Tienchin项目学习之自定义多数据源注解。自定义注解后,可以实现将注解标注在类或者方法上,实现多数据源的切换。核心思想是,在标注的@DataSource注解的时候,那么就将该方法需要使用的数据源名称存入到 ThreadLocal。在后续的操作中去ThreadLocal中拿数据源的名称。

自定义数据源注解

DataSource注解

首先自定义一个注解,代码如下:

/**
 * 定义一个注解,可以加在某一个service类或者方法上
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface DataSource {
    // 只有一个方法,指定数据源的名称
    String value() default DataSourcetype.DEFAULT_DS_NAME;
}

然后定义一个切面,解析@DataSource 注解,当一个方法或者类上面有 @DataSource 注解的时候,将 @DataSource 注解所标记的数据源存入到 ThreadLocal 中。代码如下:

@Component
@Aspect
public class DataSourceAspect {
    /**
     * @annotation(com.changanw.dd.annotation.DataSource)   表示方法上有注解拦截下来
     * @within(com.changanw.dd.annotation.DataSource)  表示类上有这个注解的话,把类中的方法拦截下来
     */
    @Pointcut("@annotation(com.changanw.dd.annotation.DataSource) || @within(com.changanw.dd.annotation.DataSource)")
    public void pc(){

    }
    @Around("pc()")
    public Object around(ProceedingJoinPoint point){
        //获取方法上的注解
        DataSource dataSource = getDataSource(point);
        if (dataSource != null){
            final String value = dataSource.value();
            DynamicDataSourceContextHolder.setDatasourceType(value);
        }
        try {
            return point.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }finally {
            DynamicDataSourceContextHolder.clearDataSource();
        }
        return null;
    }

    private DataSource getDataSource(ProceedingJoinPoint point) {
        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        //查找方法上的注解
        DataSource annotation = AnnotationUtils.findAnnotation(methodSignature.getMethod(),DataSource.class);
        if (annotation != null){
            //说明方法上有DataSource注解
            return annotation;
        }
        return AnnotationUtils.findAnnotation(methodSignature.getDeclaringType(),DataSource.class);
    }
}

DataSource解析相关代码

1、DataSourceType 类型 常量,只指定了一个主数据源的名称

public interface DataSourcetype {
    String DEFAULT_DS_NAME = "master";
}

2、DruidProperties 配置代码

@ConfigurationProperties(prefix = "spring.datasource")
public class DruidProperties {
    private String type;
    private String driverClassName;
    private Map<String,Map<String,String>> ds;
    private int initialSize;
    private int minIdle;
    private int maxActive;
    private int maxWait;

    /**
     * 在外部构造一个DruidDataSource 对象,但是这个对象只包含三个重要属性,url username  password
     * 在这个方法里设置公共属性
     * @param druidDataSource
     * @return
     */
    public DataSource dataSource(DruidDataSource druidDataSource){
        druidDataSource.setInitialSize(initialSize);
        druidDataSource.setMinIdle(minIdle);
        druidDataSource.setMaxActive(maxActive);
        druidDataSource.setMaxWait(maxWait);
        return druidDataSource;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getDriverClassName() {
        return driverClassName;
    }

    public void setDriverClassName(String driverClassName) {
        this.driverClassName = driverClassName;
    }

    public Map<String, Map<String, String>> getDs() {
        return ds;
    }

    public void setDs(Map<String, Map<String, String>> ds) {
        this.ds = ds;
    }

    public int getInitialSize() {
        return initialSize;
    }

    public void setInitialSize(int initialSize) {
        this.initialSize = initialSize;
    }

    public int getMinIdle() {
        return minIdle;
    }

    public void setMinIdle(int minIdle) {
        this.minIdle = minIdle;
    }

    public int getMaxActive() {
        return maxActive;
    }

    public void setMaxActive(int maxActive) {
        this.maxActive = maxActive;
    }

    public int getMaxWait() {
        return maxWait;
    }

    public void setMaxWait(int maxWait) {
        this.maxWait = maxWait;
    }
}

3、DynamicDatasource 类,继承自AbstractRoutingDataSource类,当 Mapper 执行的时候,需要 DataSource,他会自动去 AbstractRoutingDataSource 类中查找需要的数据源,我们只需要在 AbstractRoutingDataSource 中返回 ThreadLocal 中的值即可。

@Component
public class DynamicDatasource extends AbstractRoutingDataSource {

    public DynamicDatasource(LoadDataSource loadDataSource) {
        final Map<String, DataSource> dataSourceMap = loadDataSource.loadAllDatasource();
        //1、设置所有的数据源
        super.setTargetDataSources(new HashMap<>(dataSourceMap));
        //2、设置默认数据源
        super.setDefaultTargetDataSource(dataSourceMap.get(DataSourcetype.DEFAULT_DS_NAME));
        //3
        super.afterPropertiesSet();
    }

    /**
     * 返回数据源名称,系统需要获取数据源时,会调用这个方法,获取数据源名称
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {

        return DynamicDataSourceContextHolder.getDataSource();
    }
}

4、定义一个DynamicDataSourceContextHolder,用于存放当前使用注解上标注的数据源的名称,主要是一个ThreadLocal。

**
 * 用例存放当前线程所使用的数据源名称
 */
public class DynamicDataSourceContextHolder {
    private static ThreadLocal<String> DS_NAME = new ThreadLocal<>();

    public static void setDatasourceType(String dsType){
        DS_NAME.set(dsType);
    }

    public static String getDataSource(){
        return DS_NAME.get();
    }

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

}

5、定义一个LoadDataSource,主要是加载配置文件中配置的数据源,将全部数据源加载到一个map中

@Component
@EnableConfigurationProperties(DruidProperties.class)
public class LoadDataSource {
    @Autowired
    DruidProperties druidProperties;
    public Map<String, DataSource> loadAllDatasource(){
        Map<String, DataSource> dataSourceMap = new HashMap<>();
        final Map<String, Map<String, String>> ds = druidProperties.getDs();
        try {
            final Set<String> keySet = ds.keySet();
            for (String s : keySet) {
                dataSourceMap.put(s,druidProperties.dataSource((DruidDataSource) DruidDataSourceFactory.createDataSource(ds.get(s))));
            }
        }catch (Exception e){
            e.printStackTrace();
        }

        return dataSourceMap;
    }
}

6、最后定义一个service类,并且标注上注解,准备一个测试数据,创建两个数据源,原先代码测试注解的运行情况。

@Service
public class UserService {
    @Autowired
    UserMapper userMapper;
    @DataSource("slave")
    public List<User> getAllUser(){
        return userMapper.getAllUser();
    }
}

最后自定义的@DataSource 注解就完成了。完整的代码在 gitee.com/cnchangan/t… 可以去访问,然后拷贝下来运行一下,其中配置文件中的数据源需要换成自己的。