javascript
关于SpringBoot中的多数据源集成
引言
其實(shí)對(duì)于分庫分表這塊的場景,目前市場上有很多成熟的開源中間件,eg:MyCAT,Cobar,sharding-JDBC等。?
本文主要是介紹基于springboot的多數(shù)據(jù)源切換,輕量級(jí)的一種集成方案,對(duì)于小型的應(yīng)用可以采用這種方案,我之前在項(xiàng)目中用到是因?yàn)楹唵?#xff0c;便于擴(kuò)展以及優(yōu)化。
應(yīng)用場景
假設(shè)目前我們有以下幾種數(shù)據(jù)訪問的場景:?
1.一個(gè)業(yè)務(wù)邏輯中對(duì)不同的庫進(jìn)行數(shù)據(jù)的操作(可能你們系統(tǒng)不存在這種場景,目前都時(shí)微服務(wù)的架構(gòu),每個(gè)微服務(wù)基本上是對(duì)應(yīng)一個(gè)數(shù)據(jù)庫比較多,其他的需要通過服務(wù)來方案。),?
2.訪問分庫分表的場景;后面的文章會(huì)單獨(dú)介紹下分庫分表的集成。
假設(shè)這里,我們以6個(gè)庫,每個(gè)庫1000張表的例子來介紹),因?yàn)殡S著業(yè)務(wù)量的增長,一個(gè)庫很難抗住這么大的訪問量。比如說訂單表,我們可以根據(jù)userid進(jìn)行分庫分表。?
分庫策略:userId%6[表的數(shù)量];?
分庫分表策略:庫路由[userId/(6*1000)/1000],表路由[?userId/(6*1000)%1000].
集成方案
方案總覽:?
方案是基于springjdbc中提供的api實(shí)現(xiàn),看下下面兩段代碼,是我們的切入點(diǎn),我們都是圍繞著這2個(gè)核心方法進(jìn)行集成的。
第一段代碼是注冊(cè)邏輯,會(huì)將defaultTargetDataSource和targetDataSources這兩個(gè)數(shù)據(jù)源對(duì)象注冊(cè)到resolvedDataSources中。
第二段是具體切換邏輯: 如果數(shù)據(jù)源為空,那么他就會(huì)找我們的默認(rèn)數(shù)據(jù)源defaultTargetDataSource,如果設(shè)置了數(shù)據(jù)源,那么他就去讀這個(gè)值?Object lookupKey = determineCurrentLookupKey();我們后面會(huì)重寫這個(gè)方法。
我們會(huì)在配置文件中配置主數(shù)據(jù)源(默認(rèn)數(shù)據(jù)源)和其他數(shù)據(jù)源,然后在應(yīng)用啟動(dòng)的時(shí)候注冊(cè)到spring容器中,分別給defaultTargetDataSource和targetDataSources進(jìn)行賦值,進(jìn)而進(jìn)行Bean注冊(cè)。
真正的使用過程中,我們定義注解,通過切面,定位到當(dāng)前線程是由訪問哪個(gè)數(shù)據(jù)源(維護(hù)著一個(gè)ThreadLocal的對(duì)象),進(jìn)而調(diào)用determineCurrentLookupKey方法,進(jìn)行數(shù)據(jù)源的切換。
1 public void afterPropertiesSet() { 2 if (this.targetDataSources == null) { 3 throw new IllegalArgumentException("Property 'targetDataSources' is required"); 4 } 5 this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size()); 6 for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) { 7 Object lookupKey = resolveSpecifiedLookupKey(entry.getKey()); 8 DataSource dataSource = resolveSpecifiedDataSource(entry.getValue()); 9 this.resolvedDataSources.put(lookupKey, dataSource); 10 } 11 if (this.defaultTargetDataSource != null) { 12 this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource); 13 } 14 } 15 16 @Override 17 public Connection getConnection() throws SQLException { 18 return determineTargetDataSource().getConnection(); 19 } 20 21 protected DataSource determineTargetDataSource() { 22 Assert.notNull(this.resolvedDataSources, "DataSource router not initialized"); 23 Object lookupKey = determineCurrentLookupKey(); 24 DataSource dataSource = this.resolvedDataSources.get(lookupKey); 25 if (dataSource == null && (this.lenientFallback || lookupKey == null)) { 26 dataSource = this.resolvedDefaultDataSource; 27 } 28 if (dataSource == null) { 29 throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]"); 30 } 31 return dataSource; 32 }1.動(dòng)態(tài)數(shù)據(jù)源注冊(cè)?注冊(cè)默認(rèn)數(shù)據(jù)源以及其他額外的數(shù)據(jù)源; 這里只是復(fù)制了核心的幾個(gè)方法,具體的大家下載git看代碼。
1 // 創(chuàng)建DynamicDataSource 2 GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); 3 beanDefinition.setBeanClass(DyncRouteDataSource.class); 4 beanDefinition.setSynthetic(true); 5 MutablePropertyValues mpv = beanDefinition.getPropertyValues(); 6 //默認(rèn)數(shù)據(jù)源 7 mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource); 8 //其他數(shù)據(jù)源 9 mpv.addPropertyValue("targetDataSources", targetDataSources); 10 //注冊(cè)到spring bean容器中 11 registry.registerBeanDefinition("dataSource", beanDefinition);2.在Application中加載動(dòng)態(tài)數(shù)據(jù)源,這樣spring容器啟動(dòng)的時(shí)候就會(huì)將數(shù)據(jù)源加載到內(nèi)存中。
1 @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class, MybatisAutoConfiguration.class }) 2 @Import(DyncDataSourceRegister.class) 3 @ServletComponentScan 4 @ComponentScan(basePackages = { "com.happy.springboot.api","com.happy.springboot.service"}) 5 public class Application { 6 public static void main(String[] args) { 7 SpringApplication.run(Application.class, args); 8 } 9 }3.數(shù)據(jù)源切換核心邏輯:創(chuàng)建一個(gè)數(shù)據(jù)源切換的注解,并且基于該注解實(shí)現(xiàn)切面邏輯,這里我們通過threadLocal來實(shí)現(xiàn)線程間的數(shù)據(jù)源切換;
1 import java.lang.annotation.Documented; 2 import java.lang.annotation.ElementType; 3 import java.lang.annotation.Retention; 4 import java.lang.annotation.RetentionPolicy; 5 import java.lang.annotation.Target; 6 7 8 @Target({ ElementType.TYPE, ElementType.METHOD }) 9 @Retention(RetentionPolicy.RUNTIME) 10 @Documented 11 public @interface TargetDataSource { 12 String value(); 13 // 是否分庫 14 boolean isSharding() default false; 15 // 獲取分庫策略 16 String strategy() default ""; 17 } 18 19 // 動(dòng)態(tài)數(shù)據(jù)源上下文 20 public class DyncDataSourceContextHolder { 21 22 private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); 23 24 public static List<String> dataSourceNames = new ArrayList<String>(); 25 26 public static void setDataSource(String dataSourceName) { 27 contextHolder.set(dataSourceName); 28 } 29 30 public static String getDataSource() { 31 return contextHolder.get(); 32 } 33 34 public static void clearDataSource() { 35 contextHolder.remove(); 36 } 37 38 public static boolean containsDataSource(String dataSourceName) { 39 return dataSourceNames.contains(dataSourceName); 40 } 41 } 42 43 /** 44 * 45 * @author jasoHsu 46 * 動(dòng)態(tài)數(shù)據(jù)源在切換,將數(shù)據(jù)源設(shè)置到ThreadLocal對(duì)象中 47 */ 48 @Component 49 @Order(-10) 50 @Aspect 51 public class DyncDataSourceInterceptor { 52 53 @Before("@annotation(targetDataSource)") 54 public void changeDataSource(JoinPoint point, TargetDataSource targetDataSource) throws Exception { 55 String dbIndx = null; 56 57 String targetDataSourceName = targetDataSource.value() + (dbIndx == null ? "" : dbIndx); 58 if (DyncDataSourceContextHolder.containsDataSource(targetDataSourceName)) { 59 DyncDataSourceContextHolder.setDataSource(targetDataSourceName); 60 } 61 } 62 63 @After("@annotation(targetDataSource)") 64 public void resetDataSource(JoinPoint point, TargetDataSource targetDataSource) { 65 DyncDataSourceContextHolder.clearDataSource(); 66 } 67 } 68 69 // 70 public class DyncRouteDataSource extends AbstractRoutingDataSource { 71 @Override 72 protected Object determineCurrentLookupKey() { 73 return DyncDataSourceContextHolder.getDataSource(); 74 } 75 public DataSource findTargetDataSource() { 76 return this.determineTargetDataSource(); 77 } 78 }4.與mybatis集成,其實(shí)這一步與前面的基本一致。?
只是將在sessionFactory以及數(shù)據(jù)管理器中,將動(dòng)態(tài)數(shù)據(jù)源注冊(cè)進(jìn)去【dynamicRouteDataSource】,而非之前的靜態(tài)數(shù)據(jù)源【getMasterDataSource()】
5.核心配置參數(shù):
1 spring: 2 dataSource: 3 happyboot: 4 driverClassName: com.mysql.jdbc.Driver 5 url: jdbc:mysql://localhost:3306/happy_springboot?characterEncoding=utf8&useSSL=true 6 username: root 7 password: admin 8 extdsnames: happyboot01,happyboot02 9 happyboot01: 10 driverClassName: com.mysql.jdbc.Driver 11 url: jdbc:mysql://localhost:3306/happyboot01?characterEncoding=utf8&useSSL=true 12 username: root 13 password: admin 14 happyboot02: 15 driverClassName: com.mysql.jdbc.Driver 16 url: jdbc:mysql://localhost:3306/happyboot02?characterEncoding=utf8&useSSL=true 17 username: root 18 password: admin6.應(yīng)用代碼
1 @Service 2 public class UserExtServiceImpl implements IUserExtService { 3 @Autowired 4 private UserInfoMapper userInfoMapper; 5 @TargetDataSource(value="happyboot01") 6 @Override 7 public UserInfo getUserBy(Long id) { 8 return userInfoMapper.selectByPrimaryKey(id); 9 } 10 }本文轉(zhuǎn)載自:https://blog.csdn.net/xuxian6823091/article/details/81051515
?
轉(zhuǎn)載于:https://www.cnblogs.com/xiyunjava/p/9317625.html
總結(jié)
以上是生活随笔為你收集整理的关于SpringBoot中的多数据源集成的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 免费开源JAVA报表工具对比 BIRT报
- 下一篇: 从实战浅析运营商云资源池网络—技术的抉择