SpringBoot自定义多数据源(AOP+注解)

主要思想:自定义一个@DataSource注解加在方法或者类上面,表示该方法或者类中的所有方法都使用某一个数据源(注解中的值就是是数据源名称)通过aop使用环绕通知在方法执行前将所使用的数据源名称存入到ThreadLocal中(并在方法执行完成后remove ThreadLocal的值),重新实现AbstractRoutingDataSource类,使其在方法执行期间读取数据源时,自动去ThreadLocal获取数据源名称。(没有注解使用默认数据源)

1.自定义一个注解@DataSource

//这个注解将来可以夹在某一个service类或者方法上,通过value属性来指定类或者方法应该使用哪个数据源
@Retention(RetentionPolicy.RUNTIME)//范围 编译成class文件之后注解仍然存在
@Target({ElementType.TYPE,ElementType.METHOD})//作用域
public @interface DataSource {
    //默认数据源名称为master
    String value() default DataSourceType.DEFAULT_DS_NAME;
}
public interface DataSourceType {
    String DEFAULT_DS_NAME = "master";//默认数据源
}

2.自定义DruidProperties类注入yaml中数据库配置信息

@ConfigurationProperties(prefix = "spring.datasource")
public class DruidProperties {
    private String type;
    private String driverClassName;
    private Map<String,Map<String,String>> druid;
    private Integer initialSize;
    private Integer minIdle;
    private Integer maxActive;
    private Integer maxWait;
# 数据源配置
spring:
    datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.cj.jdbc.Driver
        druid:
            # 主库数据源
            master:
                url: jdbc:mysql://localhost:3306/test01?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
                username: root
                password: 120405
            # 从库数据源
            slave:
                url: jdbc:mysql://localhost:3306/test02?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
                username: root
                password: 120405
        # 初始连接数
        initialSize: 5
        # 最小连接池数量
        minIdle: 10
        # 最大连接池数量
        maxActive: 20
        # 配置获取连接等待超时的时间
        maxWait: 60000

3.加载配置中的所有数据源放入map中


@Component
@EnableConfigurationProperties(DruidProperties.class)
public class LoadDataSource {
    @Autowired
    DruidProperties druidProperties;
    public Map<String, DataSource> loadAllDataSource() throws Exception {
        Map<String,DataSource> map = new HashMap<>();
        Map<String,Map<String,String>> ds = druidProperties.getDruid();//存放基本属性的map
        Set<String> keySet = ds.keySet();
        for(String key : keySet){
            //设置其余公共属性
            map.put(key, druidProperties.dataSource((DruidDataSource) DruidDataSourceFactory.createDataSource(ds.get(key))));
        }
        return map;
    }
}

4.定义切点 切面

@Component
@Aspect
public class DataSourceAspect {

    /**
     * 切点
     * @annotation(org.vds.dynamic_datasource.annotation.DataSource表示方法上有@DataSource注解就将方法拦截下来
     * @within(org.vds.dynamic_datasource.annotation.DataSource)表示如果类上面有@DataSource注解就将类中的方法拦截下来
     */
    @Pointcut("@annotation(org.vds.dynamic_datasource.annotation.DataSource) || @within(org.vds.dynamic_datasource.annotation.DataSource)")
    public void pc(){

    }

    @Around("pc()")
    public Object around(ProceedingJoinPoint pjp){
        //获取方法上面的有效注解
        DataSource dataSource = getDataSource(pjp);
        if(dataSource != null){
            String value = dataSource.value();//获取注解上的值
            DynamicDataSourceContextHolder.setDataSourceType(value);//存入ThreadLocal
        }
        try {
            return pjp.proceed();//被切入方法继续执行
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }finally {
            DynamicDataSourceContextHolder.clearDataSourceType();//remove ThreadLocal中的值
        }
    }

    private DataSource getDataSource(ProceedingJoinPoint pjp) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();//获取类的信息
        //查找方法上的注解
        DataSource annotation = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
        if(annotation != null){
            //说明方法上有@DataSource注解
            return annotation;
        }
        //查找类上的@DataSsource注解
        return AnnotationUtils.findAnnotation(signature.getDeclaringType(),DataSource.class);
    }

}

5.自定义DynamicDataSourceContextHolder 将数据源名称放入ThreadLocal

/**
 * 这个类用来存储当前线程所使用的数据源名称
 */
public class DynamicDataSourceContextHolder {
    private static ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    public static void setDataSourceType(String dsType){
        CONTEXT_HOLDER.set(dsType);
    }
    public static String getDataSourceType(){
        return CONTEXT_HOLDER.get();
    }
    public static void clearDataSourceType(){
        CONTEXT_HOLDER.remove();
    }
}

6.重新实现AbstractRoutingDataSource类

@Component
public class DynamicDataSource extends AbstractRoutingDataSource {
    public DynamicDataSource(LoadDataSource loadDataSource) throws Exception {
        //1.设置所有的数据源
        Map<String, DataSource> stringDataSourceMap = loadDataSource.loadAllDataSource();
        super.setTargetDataSources(new HashMap<>(stringDataSourceMap));
        //2.设置默认的数据源
        //没有@Datasource的注解方法所使用的数据源
        super.setDefaultTargetDataSource(stringDataSourceMap.get(DataSourceType.DEFAULT_DS_NAME));
        super.afterPropertiesSet();
    }

    /**
     * 这个方法用来返回数据源名称,当系统需要获取数据源的时候,会自动调用该方法获取数据源的名称
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }

测试

数据库user表展示

不加注解查询user表


@Service
public class UserService {
    @Autowired
    UserMapper userMapper;
    public List<User> getAllUsers(){
        return userMapper.getAllUsers();
    }
}
//test
@SpringBootTest
class DynamicDatasourceApplicationTests {

    @Autowired
    UserService userService;
    @Test
    void contextLoads() {
        List<User> allUsers = userService.getAllUsers();
        for(User user : allUsers){
            System.out.println(user);
        }
    }

}

结果

加上注解查询user表

@Service
public class UserService {
    @Autowired
    UserMapper userMapper;
    @DataSource("slave")//加上注解
    public List<User> getAllUsers(){
        return userMapper.getAllUsers();
    }
}

结果

测试成功

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值