親手教你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: admin6.應(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)文章
Java8中List轉(zhuǎn)Map(Collectors.toMap) 的技巧分享
在最近的工作開發(fā)之中,慢慢習(xí)慣了很多Java8中的Stream的用法,很方便而且也可以并行的去執(zhí)行這個流,這篇文章主要給大家介紹了關(guān)于Java8中List轉(zhuǎn)Map(Collectors.toMap) 的相關(guān)資料,需要的朋友可以參考下2021-07-07
Java開發(fā)環(huán)境配置JDK超詳細整理(適合新手入門)
這篇文章主要給大家介紹了關(guān)于Java開發(fā)環(huán)境配置JDK超詳細整理的相關(guān)資料,非常適合新手入門,JDK是Java語言的軟件開發(fā)工具包,主要用于移動設(shè)備、嵌入式設(shè)備上的java應(yīng)用程序,需要的朋友可以參考下2023-11-11
Java中StringBuilder類常用方法總結(jié)
這篇文章主要介紹了Java中StringBuilder類常用方法的相關(guān)資料,StringBuilder類是Java中用于頻繁修改字符串的可變字符串緩沖區(qū)類,它提供了多種方法進行字符串操作,如添加、插入、刪除、替換字符等,需要的朋友可以參考下2024-12-12
解決運行jar包出錯:ClassNotFoundException問題
這篇文章主要介紹了解決運行jar包出錯:ClassNotFoundException問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12

