亲手教你SpringBoot中的多数据源集成问题

网友投稿 585 2022-08-22


亲手教你SpringBoot中的多数据源集成问题

目录引言应用场景集成方案

引言

其实对于分库分表这块的场景,目前市场上有很多成熟的开源中间件,eg:MyCAT,Cobar,sharding-JDBC等。

本文主要是介绍基于springboot的多数据源切换,轻量级的一种集成方案,对于小型的应用可以采用这种方案,我之前在项目中用到是因为简单,便于扩展以及优化。

应用场景

假设目前我们有以下几种数据访问的场景:1.一个业务逻辑中对不同的库进行数据的操作(可能你们系统不存在这种场景,目前都时微服务的架构,每个微服务基本上是对应一个数据库比较多,其他的需要通过服务来方案。),2.访问分库分表的场景;后面的文章会单独介绍下分库分表的集成。

假设这里,我们以6个库,每个库1000张表的例子来介绍),因为随着业务量的增长,一个库很难抗住这么大的访问量。比如说订单表,我们可以根据userid进行分库分表。分库策略:userId%6[表的数量];分库分表策略:库路由[userId/(6*1000)/1000],表路由[userId/(6*1000)%1000].

集成方案

方案总览:方案是基于springjdbc中提供的api实现,看下下面两段代码,是我们的切入点,我们都是围绕着这2个核心方法进行集成的。

第一段代码是注册逻辑,会将defaultTargetDataSource和targetDataSources这两个数据源对象注册到resolvedDataSources中。

第二段是具体切换逻辑: 如果数据源为空,那么他就会找我们的默认数据源defaultTargetDataSource,如果设置了数据源,那么他就去读这个值Object lookupKey = determineCurrentLookupKey();我们后面会重写这个方法。

我们会在配置文件中配置主数据源(默认数据源)和其他数据源,然后在应用启动的时候注册到spring容器中,分别给defaultTargetDataSource和targetDataSources进行赋值,进而进行Bean注册。

真正的使用过程中,我们定义注解,通过切面,定位到当前线程是由访问哪个数据源(维护着一个ThreadLocal的对象),进而调用determineCurrentLookupKey方法,进行数据源的切换。

public void afterPropertiesSet() {

if (this.targetDataSources == null) {

throw new IllegalArgumentException("Property 'targetDataSources' is required");

}

this.resolvedDataSources = new HashMap(this.targetDataSources.size());

for (Map.Entry 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.动态数据源注册注册默认数据源以及其他额外的数据源; 这里只是复制了核心的几个方法,具体的大家下载git看代码。

// 创建DynamicDataSource

GenericBeanDefinition beanDefinition = new GenericBeanDefinition();

beanDefinition.setBeanClass(DyncRouteDataSource.class);

beanDefinition.setSynthetic(true);

MutablePropertyValues mpv = beanDefinition.getPropertyValues();

//默认数据源

mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);

//其他数据源

mpv.addPropertyValue("targetDataSources", targetDataSources);

//注册到spring bean容器中

registry.registerBeanDefinition("dataSource", beanDefinition);

2.在Application中加载动态数据源,这样spring容器启动的时候就会将数据源加载到内存中。

@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(StrsKDdGQwQFwing[] args) {

SpringApplication.run(Application.class, args);

}

}

3.数据源切换核心逻辑:创建一个数据源切换的注解,并且基于该注解实现切面逻辑,这里我们通过threadLocal来实现线程间的数据源切换;

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 "";

}

// 动态数据源上下文

public class DyncDataSourceContextHolder {

private static final ThreadLocal contextHolder = new ThreadLocal();

public static List dataSourceNames = new ArrayList();

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

* 动态数据源在切换,将数据源设置到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以及数据管理器中,将动态数据源注册进去【dynamicRouteDataSource】,而非之前的静态数据源【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 = csKDdGQwQFwreateDefaultSqlSessionFactory(dynamicRouteDataSource, configLocation,

mapperLocation);

return sqlSessionFactory;

}

@Bean(name = "txManager")

public PlatformTransactionManager txManager() {

return new DataSourceTransactionManager(dynamicRouteDataSource);

5.核心配置参数:

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.应用代码

@Service

public class UserExtServiceImpl implements IUserExtService {

@Autowired

private UserInfoMapper userInfoMapper;

@TargetDataSource(value="happyboot01")

@Override

public UserInfo getUserBy(Long id) {

return userInfoMapper.selectByPrimaryKey(id);

}

}


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Anaconda介绍及安装(anaconda安装教程简书)
下一篇:Anaconda介绍及安装(anaconda 安装教程)
相关文章