这几天看了一些关于MySQL
主从复制,读写分离的文章,对于数据库这方面的知识很有兴趣。主要还是之前真的没有做过,想学习一下。这下好了,ruoyi
入手吧。
参考
自定义注解
还是从ruoyi-common
来看,其中value是一个枚举,默认为MASTER
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface DataSource {
public DataSourceType value() default DataSourceType.MASTER; }
|
枚举
枚举类型如下,指明了是主库(MASTER)还是从库(SLAVE),这将与之后的配置对应
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
public enum DataSourceType {
MASTER,
SLAVE }
|
切面
既然又是注解形式,在ruoyi-framework找到切面处理,代码也不算多
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
|
@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
方法级别
1 2 3 4 5
| @Around("@within(org.springframework.web.bind.annotation.RestController") 这个用于拦截标注在类上面的@RestController注解
@Around("@annotation(org.springframework.web.bind.annotation.RestController") 这个用于拦截标注在方法上面的@RestController注解
|
可见DataSource
注解代码上注释的没有问题,该注解能在方法上也能在类上,而且“优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准”。
切面代码也不复杂,简单来说就是获取注解,根据注解切换动态数据源。
数据源切换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
public class DynamicDataSourceContextHolder { public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
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(); } }
|
这里的两个方法setDataSourceType
和clearDataSourceType
,就是在前面的切面中调用了,这里还是用了ThreadLocal
用于线程隔离。而getDataSourceType
方法,则在DynamicDataSource
类实现的AbstractRoutingDataSource
抽象类方法中被调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
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
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
|
@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);
datasource.setValidationQuery(validationQuery); datasource.setTestWhileIdle(testWhileIdle); datasource.setTestOnBorrow(testOnBorrow); datasource.setTestOnReturn(testOnReturn); return datasource; } }
|
配置的读取就不用多说了,@Value("${spring.datasource.druid.initialSize}")
,需要的加上注解即可。
至于数据源配置文件application-druid.yml
如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| 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 url: 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 log-slow-sql: true slow-sql-millis: 1000 merge-sql: true wall: config: multi-statement-allow: true
|
这些配置项绝大多数都属于druid
的配置。不同的是这里的master
和slave
是ruoyi
自定义的,代表主从库,不同数据源嘛,这个要结合DruidConfig
来看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
|
@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); }
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
才可注入,这也很容易想到,毕竟有时其实也就一个数据源,所以为了避免未设置从数据源导致的异常。
使用
使用的相对简单了,上注解即可
1 2 3 4 5 6 7 8 9 10 11 12 13
|
@Override @DataSource(DataSourceType.MASTER) public SysConfig selectConfigById(Long configId) { SysConfig config = new SysConfig(); config.setConfigId(configId); return configMapper.selectConfig(config); }
|