添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface DataSource { * 切换数据源名称 public DataSourceType value () default DataSourceType.MASTER;

枚举类型如下,指明了是主库(MASTER)还是从库(SLAVE),这将与之后的配置对应

* 数据源 * @author ruoyi public enum DataSourceType { MASTER, SLAVE

既然又是注解形式,在ruoyi-framework找到切面处理,代码也不算多

* 多数据源处理 * @author ruoyi @Aspect @Order(1) @Component public class DataSourceAspect { protected Logger logger = LoggerFactory.getLogger(getClass()); @Pointcut("@annotation(com.ruoyi.common.annotation.DataSource)" + "|| @within(com.ruoyi.common.annotation.DataSource)") public void dsPointCut () { @Around("dsPointCut()") public Object around (ProceedingJoinPoint point) throws Throwable { DataSource dataSource = getDataSource(point); if (StringUtils.isNotNull(dataSource)) { DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name()); try { return point.proceed(); } finally { // 销毁数据源 在执行方法之后 DynamicDataSourceContextHolder.clearDataSourceType(); * 获取需要切换的数据源 public DataSource getDataSource (ProceedingJoinPoint point) { MethodSignature signature = (MethodSignature) point.getSignature(); DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class); if (Objects.nonNull(dataSource)) { return dataSource; return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);

@within和@annotation的区别:
@within 对象级别
@annotation 方法级别

@Around("@within(org.springframework.web.bind.annotation.RestController") 
        这个用于拦截标注在类上面的@RestController注解
@Around("@annotation(org.springframework.web.bind.annotation.RestController") 
         这个用于拦截标注在方法上面的@RestController注解

可见DataSource注解代码上注释的没有问题,该注解能在方法上也能在类上,而且“优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准”。

切面代码也不复杂,简单来说就是获取注解,根据注解切换动态数据源。

数据源切换

* 数据源切换处理 * @author ruoyi public class DynamicDataSourceContextHolder { public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class); * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本, * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); * 设置数据源的变量 public static void setDataSourceType(String dsType) { log.info("切换到{}数据源", dsType); CONTEXT_HOLDER.set(dsType); * 获得数据源的变量 public static String getDataSourceType() { return CONTEXT_HOLDER.get(); * 清空数据源变量 public static void clearDataSourceType() { CONTEXT_HOLDER.remove();

这里的两个方法setDataSourceTypeclearDataSourceType,就是在前面的切面中调用了,这里还是用了ThreadLocal用于线程隔离。而getDataSourceType方法,则在DynamicDataSource类实现的AbstractRoutingDataSource抽象类方法中被调用。

* 动态数据源 * @author ruoyi public class DynamicDataSource extends AbstractRoutingDataSource { public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) { super.setDefaultTargetDataSource(defaultTargetDataSource); super.setTargetDataSources(targetDataSources); super.afterPropertiesSet(); @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceType();

这个抽象类AbstractRoutingDataSource,是Spring Boot提供的,可根据用户定义的规则选择当前数据源。使用方法参考上面的第二条链接即可。

既然是ruoyi自己做的框架,必然也要支持配置。虽然仅仅也是对于已有框架的封装,但也是值得学习的。ruoyi数据源配置类DruidProperties对应着数据源配置文件application-druid.yml

* druid 配置属性 * @author ruoyi @Configuration public class DruidProperties { @Value("${spring.datasource.druid.initialSize}") private int initialSize; @Value("${spring.datasource.druid.minIdle}") private int minIdle; @Value("${spring.datasource.druid.maxActive}") private int maxActive; @Value("${spring.datasource.druid.maxWait}") private int maxWait; @Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}") private int timeBetweenEvictionRunsMillis; @Value("${spring.datasource.druid.minEvictableIdleTimeMillis}") private int minEvictableIdleTimeMillis; @Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}") private int maxEvictableIdleTimeMillis; @Value("${spring.datasource.druid.validationQuery}") private String validationQuery; @Value("${spring.datasource.druid.testWhileIdle}") private boolean testWhileIdle; @Value("${spring.datasource.druid.testOnBorrow}") private boolean testOnBorrow; @Value("${spring.datasource.druid.testOnReturn}") private boolean testOnReturn; public DruidDataSource dataSource(DruidDataSource datasource) { /** 配置初始化大小、最小、最大 */ datasource.setInitialSize(initialSize); datasource.setMaxActive(maxActive); datasource.setMinIdle(minIdle); /** 配置获取连接等待超时的时间 */ datasource.setMaxWait(maxWait); /** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */ datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); /** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */ datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis); * 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。 datasource.setValidationQuery(validationQuery); /** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */ datasource.setTestWhileIdle(testWhileIdle); /** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */ datasource.setTestOnBorrow(testOnBorrow); /** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */ datasource.setTestOnReturn(testOnReturn); return datasource;

配置的读取就不用多说了,@Value("${spring.datasource.druid.initialSize}"),需要的加上注解即可。

至于数据源配置文件application-druid.yml如下

# 数据源配置
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    druid:
      # 主库数据源
      master:
        url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: root
        password: root
      # 从库数据源
      slave:
        # 从数据源开关/默认关闭
        enabled: false
        username:
        password:
      # 初始连接数
      initialSize: 5
      # 最小连接池数量
      minIdle: 10
      # 最大连接池数量
      maxActive: 20
      # 配置获取连接等待超时的时间
      maxWait: 60000
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      timeBetweenEvictionRunsMillis: 60000
      # 配置一个连接在池中最小生存的时间,单位是毫秒
      minEvictableIdleTimeMillis: 300000
      # 配置一个连接在池中最大生存的时间,单位是毫秒
      maxEvictableIdleTimeMillis: 900000
      # 配置检测连接是否有效
      validationQuery: SELECT 1 FROM DUAL
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      webStatFilter:
        enabled: true
      statViewServlet:
        enabled: true
        # 设置白名单,不填则允许所有访问
        allow:
        url-pattern: /druid/*
        # 控制台管理用户名和密码
        login-username: ruoyi
        login-password: 123456
      filter:
        stat:
          enabled: true
          # 慢SQL记录
          log-slow-sql: true
          slow-sql-millis: 1000
          merge-sql: true
        wall:
          config:
            multi-statement-allow: true

这些配置项绝大多数都属于druid的配置。不同的是这里的masterslaveruoyi自定义的,代表主从库,不同数据源嘛,这个要结合DruidConfig来看。

* druid 配置多数据源 * @author ruoyi @Configuration public class DruidConfig { @Bean @ConfigurationProperties("spring.datasource.druid.master") public DataSource masterDataSource(DruidProperties druidProperties) { DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); return druidProperties.dataSource(dataSource); @Bean @ConfigurationProperties("spring.datasource.druid.slave") @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true") public DataSource slaveDataSource(DruidProperties druidProperties) { DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); return druidProperties.dataSource(dataSource); @Bean(name = "dynamicDataSource") @Primary public DynamicDataSource dataSource(DataSource masterDataSource) { Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource); setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource"); return new DynamicDataSource(masterDataSource, targetDataSources); * 设置数据源 * @param targetDataSources 备选数据源集合 * @param sourceName 数据源名称 * @param beanName bean名称 public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName) { try { DataSource dataSource = SpringUtils.getBean(beanName); targetDataSources.put(sourceName, dataSource); } catch (Exception e) { * 去除监控页面底部的广告

标准的配置类,通过@ConfigurationProperties("spring.datasource.druid.master")@Bean来完成的。这里对应着application-druid.yml中的spring.datasource.druid.master配置;@Bean@ConfigurationProperties("spring.datasource.druid.slave")@ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")也是完成bean注入的,不同的是@ConditionalOnProperty,这个代表根据情况注入Bean,关于这个的具体含义是application-druid.yml中的以spring.datasource.druid.slave为前缀,配置有名为enabled且值为true的情况下来做Bean的注入。

下面两个方法结合着讲

这三个Bean都是用来配置dataSource,第三个注解@Bean(name = "dynamicDataSource")@Primary很能说明这个方法的重要性了。第一个masterDataSource的配置不用多说,而第二个slaveDataSource的配置就很有意思了,不同于主数据源配置,从数据源需要通过Spring工具类找到Bean才可注入,这也很容易想到,毕竟有时其实也就一个数据源,所以为了避免未设置从数据源导致的异常。

使用的相对简单了,上注解即可

* 查询参数配置信息 * @param configId 参数配置ID * @return 参数配置信息 @Override @DataSource(DataSourceType.MASTER) public SysConfig selectConfigById(Long configId) { SysConfig config = new SysConfig(); config.setConfigId(configId); return configMapper.selectConfig(config); 复制代码
  • 私信