一.前言
为了应对数据库服务器的压力,在数据库的架构上需要进行读写分离的架构,代码也需要支持读写分离。
二.实践操作
2.1 yml 配置
spring:
datasource:
master:
url: jdbc:mysql://xxxxxxx:3306/xxxxxx?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useAffectedRows=true
username: xxxx
password: xxx
driver-class-name: com.mysql.cj.jdbc.Driver
#从库
slave:
url: jdbc:mysql://localhost:3306/xxxx?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useAffectedRows=true
username: xxxxx
password: xxxxx
driver-class-name: com.mysql.cj.jdbc.Driver
2.2 枚举
public enum EDataSource {
MASTER("master", "主库"),
SLAVE("slave", "从库");
private String code;
private String desc;
EDataSource(String code, String desc) {
this.code = code;
this.desc = desc;
}
public String getCode() {
return code;
}
public String getDesc() {
return desc;
}
}
2.3 数据源Bean的初始化
import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.wall.WallConfig;
import com.alibaba.druid.wall.WallFilter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Configuration
public class DataSourceConfigurer {
public final static String MASTER_DATASOURCE = "master";
public final static String SLAVE_DATASOURCE = "slave";
@Bean(MASTER_DATASOURCE)
@ConfigurationProperties(prefix = "spring.datasource.master")
public DruidDataSource masterDataSource() {
DruidDataSource build = DataSourceBuilder.create().type(DruidDataSource.class).build();
log.info("配置主数据库:{}", build);
return build;
}
@Bean(SLAVE_DATASOURCE)
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DruidDataSource slaveDataSource() {
DruidDataSource build = DataSourceBuilder.create().type(DruidDataSource.class).build();
log.info("配置从数据库:{}", build);
return build;
}
/**
* Primary 优先使用该Bean
* DependsOn 先执行主从数据库的配置
* Qualifier 指定使用哪个Bean
*
* @param masterDataSource
* @param slaveDataSource
* @return
*/
@Bean
@Primary
@DependsOn(value = {MASTER_DATASOURCE, SLAVE_DATASOURCE})
public DataSource routingDataSource(@Qualifier(MASTER_DATASOURCE) DruidDataSource masterDataSource,
@Qualifier(SLAVE_DATASOURCE) DruidDataSource slaveDataSource) {
if (StringUtils.isBlank(slaveDataSource.getUrl())) {
log.info("没有配置从数据库,默认使用主数据库");
return masterDataSource;
}
Map<Object, Object> map = new HashMap<>();
map.put(DataSourceConfigurer.MASTER_DATASOURCE, masterDataSource);
map.put(DataSourceConfigurer.SLAVE_DATASOURCE, slaveDataSource);
RoutingDataSource routing = new RoutingDataSource();
//设置动态数据源
routing.setTargetDataSources(map);
//设置默认数据源
routing.setDefaultTargetDataSource(masterDataSource);
log.info("主从数据库配置完成");
return routing;
}
}
2.4 继承 AbstractRoutingDataSource 这一步是关键
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
@Slf4j
public class RoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
String dataSource = RoutingDataSourceHolder.getDataSource();
if(StringUtils.isEmpty(dataSource)){
dataSource = EDataSource.MASTER.getCode();
}
log.info("使用数据源:{}", dataSource);
return dataSource;
}
}
2.5 数据源注解 DataSourceSwitch
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
public @interface DataSourceSwitch {
EDataSource value() default EDataSource.MASTER;
}
2.6 Aop 切面实现数据源的前置设置
@Aspect
@Order(-1)// 保证该AOP在@Transactional之前运行
@Component
@Slf4j
public class DataSourceWithAspect {
/**
* 使用DataSourceWith注解就拦截
*
*/
@Pointcut("@annotation(com.xxx.record.Interceptor.datasource.DataSourceSwitch)")
public void doPointcut() {
}
/**
* 方法前,为了在事务前设置
*/
@Before("doPointcut()")
public void doBefore(JoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
// 获取注解对象
DataSourceSwitch dataSource = method.getAnnotation(DataSourceSwitch.class);
if (dataSource == null) {
//方法没有就获取类上的
dataSource = method.getDeclaringClass().getAnnotation(DataSourceSwitch.class);
}
String key = (dataSource == null?EDataSource.MASTER.getCode():dataSource.value().getCode());
RoutingDataSourceHolder.setDataSource(key);
}
@After("doPointcut()")
public void doAfter(JoinPoint joinPoint) {
RoutingDataSourceHolder.clearDataSource();
}
}
三.注意
1.有一些包需要自己依赖注入