亲手教你SpringBoot中的多数据源集成问题
作者:java不简单 发布时间:2023-08-19 02:57:20
引言
其实对于分库分表这块的场景,目前市场上有很多成熟的开源中间件,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<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.动态数据源注册注册默认数据源以及其他额外的数据源; 这里只是复制了核心的几个方法,具体的大家下载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(String[] 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<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
* 动态数据源在切换,将数据源设置到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 = createDefaultSqlSessionFactory(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);
}
}
来源:https://www.cnblogs.com/xiyunjava/p/9317625.html
猜你喜欢
- 实例如下所示:package test;import java.util.ArrayList;import java.util.Collec
- 在没介绍正文之前,先给大家介绍下websocket的背景和原理:背景在浏览器中通过http仅能实现单向的通信,comet可以一定程度上模拟双
- eclipse中的javac命令与java命令一、eclipse的javac命令:当eclipse对.java(源文件)文件进行保存操作时(
- Java类之间的关系图在Java以及其他的面向对象设计模式中,类与类之间主要有6种关系,他们分别是:依赖、关联、聚合、组合、继承、实现。他们
- 一. CodeCache简介从字面意思理解就是代码缓存区,它缓存的是JIT(Just in Time)编译器编译的代码,简言之codeCac
- @RequestBody出现400 Bad Request的问题今天与同事调试一个接口,发现后台使用@RequestBody老是获取不到数据
- 本文实例讲述了Java Scanner类用法及nextLine()产生的换行符问题。分享给大家供大家参考,具体如下:分析理解:Scanner
- JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,易于阅读和编写,同时也易于机器解析和生成。同X
- 1、Java内存模型 Java虚拟机在执行程序时把它管理的内存分为若干数据区域,这些数据区域分布情况如下图所示:程序计数器:一块较小内存区域
- 本文为大家分享了java摄像头截图的具体代码,供大家参考,具体内容如下本来sun有个jmf组件可以很方便的实现摄像头截图的,不过这版本后来停
- 1 spring-retry是什么?以往我们在进行网络请求的时候,需要考虑网络异常的情况,本文就介绍了利用spring-retry,是spr
- java与scala数组及集合的操作这篇博客介绍了scala的数组 + 可变数组的基本使用,及其与java数组的区别scala数组基本操作d
- 1. 概述为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。2. 模式中的角色2.1 外
- 去公司面试,对方一口一个controller,一口一个service,dao,搞得我很紧张。其实都是很简单的东西,只是自己当时不知道罢了,接
- 目录1、synchronized锁的底层实现原理2、基于synchronized实现单例模式3、利用类加载实现单例模式(饿汉模式)1、syn
- Ajax本质上和普通的HTTP请求是一样的,只不过普通的HTTP请求是给人看的,而Ajax请求是给JS代码去用的。所以Ajax请求的页面一般
- 本文实例为大家分享了java生成字母验证码的具体代码,供大家参考,具体内容如下import java.awt.BasicStroke;imp
- MyBatis一对多的xml配置用的是window上面的画图板,没法以文字的方式展示出来,见谅嵌套查询嵌套结果一对多关联查询xml配置写法
- 一、首先在Spring Boot项目中,手动添加异常方法进行测试@Transactional(rollbackFor=Exception.c
- 本文介绍IntelliJ IDEA中Project 窗口的一些设置技巧,参考IntelliJ IDEA 简体中文专题教程,英文好的同学可以查