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方法