javascript
我已经把它摸的透透的了!!!Spring 动态数据源设计实践,全面解析
[
Spring 動態數據源
動態數據源是什么?它能解決什么???
在實際的開發中,同一個項目中使用多個數據源是很常見的場景。比如,一個讀寫分離的項目存在主數據源與讀數據源。
所謂動態數據源,就是通過Spring的一些配置來自動控制某段數據操作邏輯是走哪一個數據源。舉個讀寫分離的例子,項目中引用了兩個數據源,master、slave。通過Spring配置或擴展能力來使得一個接口中調用了查詢方法會自動使用slave數據源。
一般實現這種效果可以通過:
-
使用@MapperScan注解指定某個包下的所有方法走固定的數據源(這個比較死板些,會產生冗余代碼,到也可以達到效果,可以作為臨時方案使用);
-
使用注解+AOP+AbstractRoutingDataSource的形式來指定某個方法下的數據庫操作是走那個數據源。
-
通過 Sharding-JDBC組件來實現(需要引入外部依賴,如果項目本身引用了該組件,建議用這種方式實現)
<hr>
關鍵核心類
這里主要介紹通過注解+AOP+AbstractRoutingDataSource的聯動來實現動態數據源的方式。
一切的起點是AbstractRoutingDataSource這個類,此類實現了 DataSource 接口
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {// .... 省略 ... @Nullableprivate Map<Object, Object> targetDataSources;@Nullableprivate Map<Object, DataSource> resolvedDataSources;public void setTargetDataSources(Map<Object, Object> targetDataSources) {this.targetDataSources = targetDataSources;}public void setDefaultTargetDataSource(Object defaultTargetDataSource) {this.defaultTargetDataSource = defaultTargetDataSource;}@Overridepublic void afterPropertiesSet() {// 初始化 targetDataSources、resolvedDataSourcesif (this.targetDataSources == null) {throw new IllegalArgumentException("Property 'targetDataSources' is required");}this.resolvedDataSources = new HashMap<>(this.targetDataSources.size());this.targetDataSources.forEach((key, value) -> {Object lookupKey = resolveSpecifiedLookupKey(key);DataSource dataSource = resolveSpecifiedDataSource(value);this.resolvedDataSources.put(lookupKey, dataSource);});//加入Java開發交流君樣:756584822一起吹水聊天if (this.defaultTargetDataSource != null) {this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);}}@Overridepublic Connection getConnection() throws SQLException {return determineTargetDataSource().getConnection();}@Overridepublic Connection getConnection(String username, String password) throws SQLException {return determineTargetDataSource().getConnection(username, password);}/*** Retrieve the current target DataSource. Determines the* {@link #determineCurrentLookupKey() current lookup key}, performs* a lookup in the {@link #setTargetDataSources targetDataSources} map,* falls back to the specified* {@link #setDefaultTargetDataSource default target DataSource} if necessary.* @see #determineCurrentLookupKey()*/protected DataSource determineTargetDataSource() {Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");// @1 startObject lookupKey = determineCurrentLookupKey();DataSource dataSource = this.resolvedDataSources.get(lookupKey);// @1 endif (dataSource == null && (this.lenientFallback || lookupKey == null)) {dataSource = this.resolvedDefaultDataSource;}if (dataSource == null) {throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");}return dataSource;}/*** 返回一個key,這個key用來從 resolvedDataSources 數據源中獲取具體的數據源對象 見* //加入Java開發交流君樣:756584822一起吹水聊天 @1*/@Nullableprotected abstract Object determineCurrentLookupKey();}可以看到AbstractRoutingDataSource中有個可擴展抽象方法determineCurrentLookupKey(),利用這個方法可以來實現動態數據源效果。
從零寫一個簡單動態數據源組件
從上一個part我們知道可以通過實現AbstractRoutingDataSource的 determineCurrentLookupKey() 方法動態設置一個key,然后
在配置類下通過setTargetDataSources()方法設置我們提前準備好的DataSource Map。
注解,常量定義
/*** @author axin* @Summary 動態數據源注解定義*/ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyDS {String value() default "default"; }/*** @author axin* @Summary 動態數據源常量*/ public interface DSConst {String 默認 = "default";String 主庫 = "master";String 從庫 = "slave";String 統計 = "stat"; } /*** @author axin* @Summary 動態數據源 ThreadLocal 工具*/ public class DynamicDataSourceHolder {//加入Java開發交流君樣:756584822一起吹水聊天//保存當前線程所指定的DataSourceprivate static final ThreadLocal<String> THREAD_DATA_SOURCE = new ThreadLocal<>();public static String getDataSource() {return THREAD_DATA_SOURCE.get();}public static void setDataSource(String dataSource) {THREAD_DATA_SOURCE.set(dataSource);}public static void removeDataSource() {THREAD_DATA_SOURCE.remove();} }自定義一個AbstractRoutingDataSource類
/*** @author axin* @Summary 動態數據源*/ public class DynamicDataSource extends AbstractRoutingDataSource {/*** 從數據源中獲取目標數據源的key* @return*/@Overrideprotected Object determineCurrentLookupKey() {// 從ThreadLocal中獲取keyString dataSourceKey = DynamicDataSourceHolder.getDataSource();if (StringUtils.isEmpty(dataSourceKey)) {return DSConst.默認;}return dataSourceKey;} }AOP實現
/*** @author axin* @Summary 數據源切換AOP*/ @Slf4j @Aspect @Service public class DynamicDataSourceAOP {public DynamicDataSourceAOP() {log.info("/*---------------------------------------*/");log.info("/*---------- ----------*/");log.info("/*---------- 動態數據源初始化... ----------*/");log.info("/*---------- ----------*/");log.info("/*---------------------------------------*/");}/*** 切點*/@Pointcut(value = "@annotation(xxx.xxx.MyDS)")private void method(){}/*** 方法執行前,切換到指定的數據源* @param point*/@Before("method()")public void before(JoinPoint point) {MethodSignature methodSignature = (MethodSignature) point.getSignature();//獲取被代理的方法對象Method targetMethod = methodSignature.getMethod();//獲取被代理方法的注解信息CultureDS cultureDS = AnnotationUtils.findAnnotation(targetMethod, CultureDS.class);// 方法鏈條最外層的動態數據源注解優先級最高//加入Java開發交流君樣:756584822一起吹水聊天String key = DynamicDataSourceHolder.getDataSource();if (!StringUtils.isEmpty(key)) {log.warn("提醒:動態數據源注解調用鏈上出現覆蓋場景,請確認是否無問題");return;}if (cultureDS != null ) {//設置數據庫標志DynamicDataSourceHolder.setDataSource(MyDS.value());}}/*** 釋放數據源*/@AfterReturning("method()")public void doAfter() {DynamicDataSourceHolder.removeDataSource();} }DataSourceConfig配置
通過以下代碼來將動態數據源配置到 SqlSession 中去
/*** 數據源的一些配置,主要是配置讀寫分離的sqlsession,這里沒有使用mybatis annotation* @Configuration @EnableTransactionManagement @EnableAspectJAutoProxy class DataSourceConfig {/** 可讀寫的SQL Session */public static final String BEANNAME_SQLSESSION_COMMON = "sqlsessionCommon";/** 事務管理器的名稱,如果有多個事務管理器時,需要指定beanName */public static final String BEANNAME_TRANSACTION_MANAGER = "transactionManager";/** 主數據源,必須配置,spring啟動時會執行初始化數據操作(無論是否真的需要),選擇查找DataSource class類型的數據源 配置通用數據源,可讀寫,連接的是主庫 */@Bean@Primary@ConfigurationProperties(prefix = "datasource.common")public DataSource datasourceCommon() {// 數據源配置 可更換為其他實現方式return DataSourceBuilder.create().build();}/*** 動態數據源* @returnr*/@Beanpublic DynamicDataSource dynamicDataSource() {DynamicDataSource dynamicDataSource = new DynamicDataSource();LinkedHashMap<Object, Object> hashMap = Maps.newLinkedHashMap();hashMap.put(DSConst.默認, datasourceCommon());hashMap.put(DSConst.主庫, datasourceCommon());hashMap.put(DSConst.從庫, datasourceReadOnly());hashMap.put(DSConst.統計, datasourceStat());// 初始化數據源 MapdynamicDataSource.setTargetDataSources(hashMap);dynamicDataSource.setDefaultTargetDataSource(datasourceCommon());return dynamicDataSource;}/*** 配置事務管理器*/@Primary@Bean(name = BEANNAME_TRANSACTION_MANAGER)public DataSourceTransactionManager createDataSourceTransactionManager2() {DataSource dataSource = this.dynamicDataSource();DataSourceTransactionManager manager = new DataSourceTransactionManager(dataSource);return manager;}/*** 配置讀寫sqlsession*/@Primary@Bean(name = BEANNAME_SQLSESSION_COMMON)public SqlSession readWriteSqlSession() throws Exception {SqlSessionFactoryBean factory = new SqlSessionFactoryBean();//加入Java開發交流君樣:756584822一起吹水聊天// 設置動態數據源factory.setDataSource(this.dynamicDataSource());PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();factory.setConfigLocation(resolver.getResource("mybatis/mybatis-config.xml"));factory.setMapperLocations(resolver.getResources("mybatis/mappers/**/*.xml"));return new SqlSessionTemplate(factory.getObject());} }總結
綜上,利用AOP+注解實現了一個簡單的Spring動態數據源功能,使用的時候,僅需要在目標方法上加上 @MyDS 注解即可。許多開源組件,會在現有的基礎上增加一個擴展功能,比如路由策略等等。
順便聊一下 sharding-jdbc 的實現方式,更新寫入類sql自動走主庫,查詢類自動走讀庫,如果是新項目無歷史債務的話,是可以使用該方案的。如果你是在原有舊的項目上進行讀寫分離改造,那如果你使用了 sharding-jdbc 讀寫分離方案,你就必須梳理已有代碼邏輯中的sql調用情況,來避免主從延遲造成數據不一致對業務的影響。
主從延遲造成讀取數據不一致的情況是指:主從在同步的時候是有一定的延遲時間的,不管是什么網絡的情況,這個延遲的值都是存在的,一般在毫秒級左右。這個時候如果使用sharding-jdbc進行讀寫分離處理,進行實時數據插入并查詢判斷的時候,就會出現判斷異常的情況。
最后,祝大家早日學有所成,拿到滿意offer
總結
以上是生活随笔為你收集整理的我已经把它摸的透透的了!!!Spring 动态数据源设计实践,全面解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在银行存款50万,在中国是什么水平?
- 下一篇: 求职华为,被问观察者模式,从没有这种体验