1.首先是创建执行策略枚举类
/**
* ${DESCRIPTION}
* 动态数据源执行策略
* @author syliu
* @create 2020-02-04 上午 10:23
**/
public enum DataSourceStrategy {
RANDOM("random","随机策略"),
TRAINING("training","轮询策略")
;
DataSourceStrategy( String name,String desc) {
this.name = name;
this.desc = desc;
}
private final String name;
private final String desc;
public String getName ( ) {
return name;
}
public String getDesc ( ) {
return desc;
}
}
2.编写AbstractRoutingDataSource的实现类,DynamicDataSource来动态获取数据源
/**
* Spring boot提供了AbstractRoutingDataSource 根据用户定义的规则选择当前的数据源,
* 这样我们可以在执行查询之前,设置使用的数据源。实现可动态路由的数据源,
* 在每次数据库查询操作前执行。它的抽象方法 determineCurrentLookupKey() 决定使用哪个数据源。
*
* @author syliu
* @create 2020-02-04 上午 10:23
**/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
3.DynamicDataSourceContextHolder,保存及获取数据源,包括全部数据源和单独的从库数据源,方便获取和计算
/**
* DynamicDataSourceContextHolder,保存及获取数据源
*
* @author syliu
* @create 2020-02-04 上午 10:22
**/
public class DynamicDataSourceContextHolder {
/**
* 存放当前线程使用的数据源类型信息
*/
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
/**
* 存放所有数据源id
*/
public static List<String> dataSourceIds = new ArrayList<String>();
/**
* 存放其他从库数据源的数据
*/
public static List<String> customDataSourceIds = new ArrayList<String>();
/**
* 设置数据源
* @param dataSourceType
*/
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
/**
* 获取数据源
* @return
*/
public static String getDataSourceType() {
return contextHolder.get();
}
/**
* 清除数据源
*/
public static void clearDataSourceType() {
contextHolder.remove();
}
/**
* 判断当前数据源是否存在
* @param dataSourceId
* @return
*/
public static boolean isContainsDataSource(String dataSourceId) {
return dataSourceIds.contains(dataSourceId);
}
}
4.DynamicDataSourceRegister实现数据源注册,实现EnvironmentAware接口,从而获取application.yml配置文件中数据源的配置信息,实现ImportBeanDefinitionRegistrar,从而注册DynamicDataSource
/**
* DynamicDataSourceRegister实现数据源注册,实现EnvironmentAware接口,
* 从而获取application.yml配置文件中数据源的配置信息
* 实现ImportBeanDefinitionRegistrar,从而注册DynamicDataSource
*
* @author syliu
* @create 2020-02-04 上午 9:07
**/
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private Logger logger = LoggerFactory.getLogger (DynamicDataSourceRegister.class );
/**
* 指定默认数据源
*/
private static final String DATASOURCE_TYPE_DEFAULT = "com.alibaba.druid.pool.DruidDataSource";
/**
* 默认数据源
*/
private DataSource defaultDataSource;
/**
* 用户自定义数据源
*/
private Map<String, DataSource> customDataSources = new HashMap<>();
@Override
public void setEnvironment(Environment environment) {
//初始化默认数据源
initDefaultDataSource(environment);
//初始化从库数据源
initCustomDataSources(environment);
}
private void initDefaultDataSource(Environment env) {
// 读取主数据源
Map<String, Object> dsMap = new HashMap<>();
dsMap.put("driver-class-name", env.getProperty("spring.datasource.druid.driver-class-name"));
dsMap.put("url", env.getProperty("spring.datasource.druid.url"));
dsMap.put("username", env.getProperty("spring.datasource.druid.username"));
dsMap.put("password", env.getProperty("spring.datasource.druid.password"));
defaultDataSource = buildDataSource(dsMap);
}
private void initCustomDataSources(Environment env) {
// 读取配置文件获取更多数据源
String dataSourcesPre = env.getProperty("custom.datasource.names");
for (String dataSourcesPrefix : dataSourcesPre.split(",")) {
// 多个数据源
Map<String, Object> dsMap = new HashMap<>(4);
dsMap.put("driver-class-name", env.getProperty("custom.datasource." + dataSourcesPrefix + ".driver-class-name"));
dsMap.put("url", env.getProperty("custom.datasource." + dataSourcesPrefix + ".url"));
dsMap.put("username", env.getProperty("custom.datasource." + dataSourcesPrefix + ".username"));
dsMap.put("password", env.getProperty("custom.datasource." + dataSourcesPrefix + ".password"));
DataSource ds = buildDataSource(dsMap);
customDataSources.put(dataSourcesPrefix, ds);
}
}
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
//添加默认数据源
targetDataSources.put("dataSource", this.defaultDataSource);
DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
//添加其他数据源
targetDataSources.putAll(customDataSources);
for (String key : customDataSources.keySet()) {
DynamicDataSourceContextHolder.dataSourceIds.add(key);
DynamicDataSourceContextHolder.customDataSourceIds.add ( key );
}
//创建DynamicDataSource
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(DynamicDataSource.class);
beanDefinition.setSynthetic(true);
MutablePropertyValues mpv = beanDefinition.getPropertyValues();
mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
mpv.addPropertyValue("targetDataSources", targetDataSources);
//注册 - BeanDefinitionRegistry
beanDefinitionRegistry.registerBeanDefinition("dataSource", beanDefinition);
logger.info("动态数据源注册");
}
public DataSource buildDataSource(Map<String, Object> dataSourceMap) {
try {
Object type = dataSourceMap.get("type");
if (type == null) {
// 默认DataSource
type = DATASOURCE_TYPE_DEFAULT;
}
Class<? extends DataSource> dataSourceType;
dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);
String driverClassName = dataSourceMap.get("driver-class-name").toString();
String url = dataSourceMap.get("url").toString();
String username = dataSourceMap.get("username").toString();
String password = dataSourceMap.get("password").toString();
// 自定义DataSource配置
DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url)
.username(username).password(password).type(dataSourceType);
return factory.build();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
5.自定义注解,用来标注哪些执行切面的时候切换从数据源
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
/**
* 执行策略
* @return
*/
DataSourceStrategy strategy () default DataSourceStrategy.RANDOM;
}
6.自定义切面,用来切换数据源
/**
* 自定义切面,处理数据源
*
* @author syliu
* @create 2020-02-04 上午 10:28
**/
@Aspect
@Order(-10)//保证该AOP在@Transactional之前执行
@Component
public class DynamicDataSourceAspect {
private Logger logger = LoggerFactory.getLogger (DynamicDataSourceAspect.class );
private static final AtomicInteger dataSourceIndex=new AtomicInteger ( 0 );
/**
* 改变数据源
* @param joinPoint
* @param targetDataSource
*/
@Before("@annotation(targetDataSource)")
public void changeDataSource(JoinPoint joinPoint, TargetDataSource targetDataSource) {
List < String > dataSourceIds = DynamicDataSourceContextHolder.customDataSourceIds;
//执行策略
DataSourceStrategy strategy = targetDataSource.strategy ( );
String dataSourceId=getDbId(dataSourceIds,strategy);
if (!DynamicDataSourceContextHolder.isContainsDataSource(dataSourceId)) {
//joinPoint.getSignature() :获取连接点的方法签名对象
logger.error("数据源 " + dataSourceId + " 不存在使用默认的数据源 -> " + joinPoint.getSignature());
} else {
logger.info ("使用数据源:" + dataSourceId);
DynamicDataSourceContextHolder.setDataSourceType(dataSourceId);
}
}
@After("@annotation(targetDataSource)")
public void clearDataSource(JoinPoint joinPoint, TargetDataSource targetDataSource) {
logger.info("清除数据源 " + targetDataSource.value() + " !");
DynamicDataSourceContextHolder.clearDataSourceType();
}
/**
* 数据源策略
* @param dataSourceIds
* @param strategy
* @return
*/
private String getDbId(List<String> dataSourceIds,DataSourceStrategy strategy){
if ( dataSourceIds.size ()>0 ){
int length = dataSourceIds.size ();
//随机策略
if ( DataSourceStrategy.RANDOM==strategy ){
int index=(int)(Math.random()*length);
String dataSource = dataSourceIds.get (index);
logger.info ( "执行随机策略,返回数据源:{}" ,dataSource);
return dataSource;
}else if ( DataSourceStrategy.TRAINING==strategy){
int index = dataSourceIndex.get ( );
if ( index==0 ){
dataSourceIndex.addAndGet ( length-1 );
}else {
dataSourceIndex.decrementAndGet ();
}
String dataSource = dataSourceIds.get (index);
logger.info ( "执行轮讯策略,返回数据源:{}" ,dataSource);
return dataSource;
}
}
return "";
};
}
7.注册多数据源数据库,需要在启动类开启
Import ({ DynamicDataSourceRegister.class}) //注册多源数据库
8.配置文件
9.使用
@TargetDataSource ( strategy=DataSourceStrategy.TRAINING )
执行策略有轮询和随机,主要是减轻一些压力,而不需要用到mysql中间件,所以自己简单写了下,如果互为主从数据库的所有service都可以使用它来进行读写压力分离,如果是只有从库的情况下,只能在所有查询service上进行使用,service方法中必须全为select 查询的mysql方法