親手教你SpringBoot中的多數(shù)據(jù)源集成問題
引言
其實對于分庫分表這塊的場景,目前市場上有很多成熟的開源中間件,eg:MyCAT,Cobar,sharding-JDBC等。
本文主要是介紹基于springboot的多數(shù)據(jù)源切換,輕量級的一種集成方案,對于小型的應(yīng)用可以采用這種方案,我之前在項目中用到是因為簡單,便于擴展以及優(yōu)化。
應(yīng)用場景
假設(shè)目前我們有以下幾種數(shù)據(jù)訪問的場景:
1.一個業(yè)務(wù)邏輯中對不同的庫進行數(shù)據(jù)的操作(可能你們系統(tǒng)不存在這種場景,目前都時微服務(wù)的架構(gòu),每個微服務(wù)基本上是對應(yīng)一個數(shù)據(jù)庫比較多,其他的需要通過服務(wù)來方案。),
2.訪問分庫分表的場景;后面的文章會單獨介紹下分庫分表的集成。
假設(shè)這里,我們以6個庫,每個庫1000張表的例子來介紹),因為隨著業(yè)務(wù)量的增長,一個庫很難抗住這么大的訪問量。比如說訂單表,我們可以根據(jù)userid進行分庫分表。
分庫策略:userId%6[表的數(shù)量];
分庫分表策略:庫路由[userId/(6*1000)/1000],表路由[userId/(6*1000)%1000].
集成方案
方案總覽:
方案是基于springjdbc中提供的api實現(xiàn),看下下面兩段代碼,是我們的切入點,我們都是圍繞著這2個核心方法進行集成的。
第一段代碼是注冊邏輯,會將defaultTargetDataSource和targetDataSources這兩個數(shù)據(jù)源對象注冊到resolvedDataSources中。
第二段是具體切換邏輯: 如果數(shù)據(jù)源為空,那么他就會找我們的默認數(shù)據(jù)源defaultTargetDataSource,如果設(shè)置了數(shù)據(jù)源,那么他就去讀這個值Object lookupKey = determineCurrentLookupKey();我們后面會重寫這個方法。
我們會在配置文件中配置主數(shù)據(jù)源(默認數(shù)據(jù)源)和其他數(shù)據(jù)源,然后在應(yīng)用啟動的時候注冊到spring容器中,分別給defaultTargetDataSource和targetDataSources進行賦值,進而進行Bean注冊。
真正的使用過程中,我們定義注解,通過切面,定位到當前線程是由訪問哪個數(shù)據(jù)源(維護著一個ThreadLocal的對象),進而調(diào)用determineCurrentLookupKey方法,進行數(shù)據(jù)源的切換。
public void afterPropertiesSet() { if (this.targetDataSources == null) { throw new IllegalArgumentException("Property 'targetDataSources' is required"); } this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size()); for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) { Object lookupKey = resolveSpecifiedLookupKey(entry.getKey()); DataSource dataSource = resolveSpecifiedDataSource(entry.getValue()); this.resolvedDataSources.put(lookupKey, dataSource); if (this.defaultTargetDataSource != null) { this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource); } @Override public Connection getConnection() throws SQLException { return determineTargetDataSource().getConnection(); } protected DataSource determineTargetDataSource() { Assert.notNull(this.resolvedDataSources, "DataSource router not initialized"); Object lookupKey = determineCurrentLookupKey(); DataSource dataSource = this.resolvedDataSources.get(lookupKey); if (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;
1.動態(tài)數(shù)據(jù)源注冊注冊默認數(shù)據(jù)源以及其他額外的數(shù)據(jù)源; 這里只是復(fù)制了核心的幾個方法,具體的大家下載git看代碼。
// 創(chuàng)建DynamicDataSource GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(DyncRouteDataSource.class); beanDefinition.setSynthetic(true); MutablePropertyValues mpv = beanDefinition.getPropertyValues(); //默認數(shù)據(jù)源 mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource); //其他數(shù)據(jù)源 mpv.addPropertyValue("targetDataSources", targetDataSources); //注冊到spring bean容器中 registry.registerBeanDefinition("dataSource", beanDefinition);
2.在Application中加載動態(tài)數(shù)據(jù)源,這樣spring容器啟動的時候就會將數(shù)據(jù)源加載到內(nèi)存中。
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class, MybatisAutoConfiguration.class }) @Import(DyncDataSourceRegister.class) @ServletComponentScan @ComponentScan(basePackages = { "com.happy.springboot.api","com.happy.springboot.service"}) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
3.數(shù)據(jù)源切換核心邏輯:創(chuàng)建一個數(shù)據(jù)源切換的注解,并且基于該注解實現(xiàn)切面邏輯,這里我們通過threadLocal來實現(xiàn)線程間的數(shù)據(jù)源切換;
import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface TargetDataSource { String value(); // 是否分庫 boolean isSharding() default false; // 獲取分庫策略 String strategy() default ""; } // 動態(tài)數(shù)據(jù)源上下文 public class DyncDataSourceContextHolder { private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); public static List<String> dataSourceNames = new ArrayList<String>(); public static void setDataSource(String dataSourceName) { contextHolder.set(dataSourceName); } public static String getDataSource() { return contextHolder.get(); public static void clearDataSource() { contextHolder.remove(); public static boolean containsDataSource(String dataSourceName) { return dataSourceNames.contains(dataSourceName); /** * * @author jasoHsu * 動態(tài)數(shù)據(jù)源在切換,將數(shù)據(jù)源設(shè)置到ThreadLocal對象中 */ @Component @Order(-10) @Aspect public class DyncDataSourceInterceptor { @Before("@annotation(targetDataSource)") public void changeDataSource(JoinPoint point, TargetDataSource targetDataSource) throws Exception { String dbIndx = null; String targetDataSourceName = targetDataSource.value() + (dbIndx == null ? "" : dbIndx); if (DyncDataSourceContextHolder.containsDataSource(targetDataSourceName)) { DyncDataSourceContextHolder.setDataSource(targetDataSourceName); } @After("@annotation(targetDataSource)") public void resetDataSource(JoinPoint point, TargetDataSource targetDataSource) { DyncDataSourceContextHolder.clearDataSource(); // public class DyncRouteDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DyncDataSourceContextHolder.getDataSource(); public DataSource findTargetDataSource() { return this.determineTargetDataSource();
4.與mybatis集成,其實這一步與前面的基本一致。
只是將在sessionFactory以及數(shù)據(jù)管理器中,將動態(tài)數(shù)據(jù)源注冊進去【dynamicRouteDataSource】,而非之前的靜態(tài)數(shù)據(jù)源【getMasterDataSource()】
@Resource DyncRouteDataSource dynamicRouteDataSource; @Bean(name = "sqlSessionFactory") public SqlSessionFactory getinvestListingSqlSessionFactory() throws Exception { String configLocation = "classpath:/conf/mybatis/configuration.xml"; String mapperLocation = "classpath*:/com/happy/springboot/service/dao/*/*Mapper.xml"; SqlSessionFactory sqlSessionFactory = createDefaultSqlSessionFactory(dynamicRouteDataSource, configLocation, mapperLocation); return sqlSessionFactory; } @Bean(name = "txManager") public PlatformTransactionManager txManager() { return new DataSourceTransactionManager(dynamicRouteDataSource);
5.核心配置參數(shù):
spring: dataSource: happyboot: driverClassName: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/happy_springboot?characterEncoding=utf8&useSSL=true username: root password: admin extdsnames: happyboot01,happyboot02 happyboot01: driverClassName: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/happyboot01?characterEncoding=utf8&useSSL=true username: root password: admin happyboot02: driverClassName: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/happyboot02?characterEncoding=utf8&useSSL=true username: root password: admin
6.應(yīng)用代碼
@Service public class UserExtServiceImpl implements IUserExtService { @Autowired private UserInfoMapper userInfoMapper; @TargetDataSource(value="happyboot01") @Override public UserInfo getUserBy(Long id) { return userInfoMapper.selectByPrimaryKey(id); } }
到此這篇關(guān)于關(guān)于SpringBoot中的多數(shù)據(jù)源集成的文章就介紹到這了,更多相關(guān)SpringBoot多數(shù)據(jù)源集成內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot項目多數(shù)據(jù)源及mybatis 駝峰失效的問題解決方法
- SpringBoot詳解如何進行整合Druid數(shù)據(jù)源
- SpringBoot整合Druid數(shù)據(jù)源的方法實現(xiàn)
- Springboot集成mybatis實現(xiàn)多數(shù)據(jù)源配置詳解流程
- SpringBoot超詳細講解多數(shù)據(jù)源集成
- SpringBoot多數(shù)據(jù)源的兩種實現(xiàn)方式實例
- SpringBoot多數(shù)據(jù)源切換實現(xiàn)代碼(Mybaitis)
- 使用SpringBoot配置多數(shù)據(jù)源的經(jīng)驗分享
- SpringBoot內(nèi)置數(shù)據(jù)源的持久化與解決方案
相關(guān)文章
Java中controller層如何接收帶參數(shù)的查詢
本文主要介紹了Java中controller層如何接收帶參數(shù)的查詢,在控制器層接收帶參數(shù)的查詢可以通過多種方式實現(xiàn),下面就詳細的介紹一下,感興趣的可以了解一下2023-08-08SpringBoot整合RabbitMQ的5種模式實戰(zhàn)
本文主要介紹了SpringBoot整合RabbitMQ的5種模式實戰(zhàn),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-08-08Java?SpringBoot整合shiro-spring-boot-starterqi項目報錯解決
這篇文章主要介紹了Java?SpringBoot整合shiro-spring-boot-starterqi項目報錯解決,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考一下2022-08-08