spring boot使用AbstractRoutingDataSource实现动态数据源切换
一、AbstractRoutingDataSource
Spring boot提供了AbstractRoutingDataSource 根据用户定义的规则选择当前的数据源,这样我们可以在执行查询之前,设置使用的数据源。实现可动态路由的数据源,在每次数据库查询操作前执行。它的抽象方法 determineCurrentLookupKey() 决定使用哪个数据源。
org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource 源码的介绍:
大概意思是:
AbstractRoutingDataSource的getConnection() 方法根据查找 lookup key 键对不同目标数据源的调用,通常是通过(但不一定)某些线程绑定的事物上下文来实现。
AbstractRoutingDataSource的多数据源动态切换的核心逻辑是:在程序运行时,把数据源数据源通过 AbstractRoutingDataSource 动态织入到程序中,灵活的进行数据源切换。
基于AbstractRoutingDataSource的多数据源动态切换,可以实现读写分离,这么做缺点也很明显,无法动态的增加数据源。
实现逻辑:
- 定义DynamicDataSource类继承抽象类AbstractRoutingDataSource,并实现了determineCurrentLookupKey()方法。
- 把配置的多个数据源会放在AbstractRoutingDataSource的 targetDataSources和defaultTargetDataSource中,然后通过afterPropertiesSet()方法将数据源分别进行复制到resolvedDataSources和resolvedDefaultDataSource中。
- 调用AbstractRoutingDataSource的getConnection()的方法的时候,先调用determineTargetDataSource()方法返回DataSource在进行getConnection()。
二、具体实现
1、pom.xml
新建springboot项目,其中pom.xml 文件依赖如下
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--mybatis plus--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.0.1</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--druid--><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.10</version></dependency><!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.9</version></dependency><!-- swagger --><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version></dependency><dependency><groupId>com.github.caspar-chen</groupId><artifactId>swagger-ui-layer</artifactId><version>1.1.3</version></dependency>
如果不要swagger可以去掉swagger依赖
2、使用mybatis plus 生成实体与xml等代码
3、在spring boot 启动类上添加扫描mapper注解 - @MapperScan(“com.xh.mapper”)
4、在配置文件 application.properties 中添加多个(我这里是两个)数据源的配置信息
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driverClassName=com.mysql.jdbc.Driver
# 数据源1
spring.datasource.druid.first.url=jdbc:mysql://localhost:3306/test1?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8
spring.datasource.druid.first.username=root
spring.datasource.druid.first.password=****
# 数据源2 需要创建对应数据库 更改该库中 sys_user 表
spring.datasource.druid.second.url=jdbc:mysql://localhost:3306/test2?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8
spring.datasource.druid.second.username=root
spring.datasource.druid.second.password=****
如果还要添加数据源就按照 这种格式继续往下写。
5、集成动态数据源模块
5.1、新建注解 CurDataSource 指定要使用的数据源
/*** 多数据源注解* <p/>* 指定要使用的数据源** @author xiaohe* @version V1.0.0*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CurDataSource {String name() default "";}
5.2、新建常量存储于获取数据源
/*** 增加多数据源,在此配置** @author xiaohe* @version V1.0.0*/
public interface DataSourceNames {String FIRST = "first";String SECOND = "second";}
5.3、新建类 DynamicDataSource
DynamicDataSource扩展Spring的AbstractRoutingDataSource抽象类,重写 determineCurrentLookupKey() 方法
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;import javax.sql.DataSource;
import java.util.Map;/*** 扩展 Spring 的 AbstractRoutingDataSource 抽象类,重写 determineCurrentLookupKey 方法* 动态数据源* determineCurrentLookupKey() 方法决定使用哪个数据源** @author xiaohe* @version V1.0.0*/
public class DynamicDataSource extends AbstractRoutingDataSource {/*** ThreadLocal 用于提供线程局部变量,在多线程环境可以保证各个线程里的变量独立于其它线程里的变量。* 也就是说 ThreadLocal 可以为每个线程创建一个【单独的变量副本】,相当于线程的 private static 类型变量。*/private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();/*** 决定使用哪个数据源之前需要把多个数据源的信息以及默认数据源信息配置好** @param defaultTargetDataSource 默认数据源* @param targetDataSources 目标数据源*/public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {super.setDefaultTargetDataSource(defaultTargetDataSource);super.setTargetDataSources(targetDataSources);super.afterPropertiesSet();}@Overrideprotected Object determineCurrentLookupKey() {return getDataSource();}public static void setDataSource(String dataSource) {CONTEXT_HOLDER.set(dataSource);}public static String getDataSource() {return CONTEXT_HOLDER.get();}public static void clearDataSource() {CONTEXT_HOLDER.remove();}}
5.4、新建多数据源配置类
配置多数据源的信息,生成多个(我这里是两个,对应application.properties中定义的数据源)数据源
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;/*** 配置多数据源* @author xiaohe* @version V1.0.0*/
@Configuration
public class DynamicDataSourceConfig {@Bean@ConfigurationProperties("spring.datasource.druid.first")public DataSource firstDataSource(){return DruidDataSourceBuilder.create().build();}@Bean@ConfigurationProperties("spring.datasource.druid.second")public DataSource secondDataSource(){return DruidDataSourceBuilder.create().build();}@Bean@Primarypublic DynamicDataSource dataSource(DataSource firstDataSource, DataSource secondDataSource) {Map<Object, Object> targetDataSources = new HashMap<>(5);targetDataSources.put(DataSourceNames.FIRST, firstDataSource);targetDataSources.put(DataSourceNames.SECOND, secondDataSource);return new DynamicDataSource(firstDataSource, targetDataSources);}}
5.5、采用aop的方式,在需要修改数据源的地方使用注解方式去切换,然后切面修改ThreadLocal的内容
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;/*** 多数据源,切面处理类** @author xiaohe* @version V1.0.0*/
@Slf4j
@Aspect
@Component
public class DataSourceAspect implements Ordered {@Pointcut("@annotation(com.xh.datasource.CurDataSource)")public void dataSourcePointCut() {}@Around("dataSourcePointCut()")public Object around(ProceedingJoinPoint point) throws Throwable {MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();CurDataSource ds = method.getAnnotation(CurDataSource.class);if (ds == null) {DynamicDataSource.setDataSource(DataSourceNames.FIRST);log.debug("set datasource is " + DataSourceNames.FIRST);} else {DynamicDataSource.setDataSource(ds.name());log.debug("set datasource is " + ds.name());}try {return point.proceed();} finally {DynamicDataSource.clearDataSource();log.debug("clean datasource");}}@Overridepublic int getOrder() {return 1;}
}
5.6、启动类上添加数据源配置
因为数据源是自己生成的,所以要去掉原先springboot启动时候自动装配的数据源配置。
import com.xh.datasource.DynamicDataSourceConfig;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Import;/*** @author xiaohe*/
@MapperScan("com.xh.mapper")
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@Import({DynamicDataSourceConfig.class})
public class SpringBootDynamicDataSourceApplication {public static void main(String[] args) {SpringApplication.run(SpringBootDynamicDataSourceApplication.class, args);}}
5.7、测试数据源切换
service中定义两个查询,分别查两个数据库:
import com.xh.entity.SysUser;
import com.baomidou.mybatisplus.extension.service.IService;/*** <p>* 系统用户 服务类* </p>** @author xiaohe* @since 2019-06-04*/
public interface SysUserService extends IService<SysUser> {SysUser findUserByFirstDb(long id);SysUser findUserBySecondDb(long id);}
实现类:因为默认是使用第一个数据源,所以不用注解,使用数据源二需要添加注解 @CurDataSource(name = DataSourceNames.SECOND) 。
import com.xh.datasource.CurDataSource;
import com.xh.datasource.DataSourceNames;
import com.xh.entity.SysUser;
import com.xh.mapper.SysUserMapper;
import com.xh.service.SysUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;/*** <p>* 系统用户 服务实现类* </p>** @author xiaohe* @since 2019-06-04*/
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {@Overridepublic SysUser findUserByFirstDb(long id) {return this.baseMapper.selectById(id);}@CurDataSource(name = DataSourceNames.SECOND)@Overridepublic SysUser findUserBySecondDb(long id) {return this.baseMapper.selectById(id);}}
测试类
import com.xh.entity.SysUser;
import com.xh.service.SysUserService;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBootDynamicCurDataSourceApplicationTests {@Autowiredprivate SysUserService userService;@Testpublic void test() {SysUser user = userService.findUserByFirstDb(1);log.info("第一个数据库 : [{}]", user.toString());SysUser user2 = userService.findUserBySecondDb(1);log.info("第二个数据库 : [{}]", user2.toString());}
}
建表与初始化sql:
-- 系统用户
CREATE TABLE `sys_user`
(`user_id` bigint NOT NULL AUTO_INCREMENT,`username` varchar(50) NOT NULL COMMENT '用户名',`password` varchar(100) COMMENT '密码',`salt` varchar(20) COMMENT '盐',`email` varchar(100) COMMENT '邮箱',`mobile` varchar(100) COMMENT '手机号',`status` tinyint COMMENT '状态 0:禁用 1:正常',`create_user_id` bigint(20) COMMENT '创建者ID',`create_time` datetime COMMENT '创建时间',PRIMARY KEY (`user_id`),UNIQUE INDEX (`username`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8 COMMENT ='系统用户';-- 初始数据
INSERT INTO `sys_user` (`user_id`, `username`, `password`, `salt`, `email`, `mobile`, `status`, `create_user_id`,`create_time`)
VALUES ('1', 'admin', '9ec9750e709431dad22365cabc5c625482e574c74adaebba7dd02f1129e4ce1d', 'YzcmCZNvbXocrsz9dm8e','root@renren.io', '13612345678', '1', '1', '2016-11-11 11:11:11');
把库2的username改为:admin2222
最后的测试结果打印如下:
三、关于事务
AbstractRoutingDataSource 只支持单库事务,也就是说切换数据源要在开启事务之前执行。 spring DataSourceTransactionManager进行事务管理,开启事务,会将数据源缓存到DataSourceTransactionObject对象中进行后续的commit rollback等事务操作。
出现多数据源动态切换失败的原因是因为在事务开启后,数据源就不能再进行随意切换了,也就是说,一个事务对应一个数据源。
传统的Spring管理事务是放在Service业务层操作的,所以更换数据源的操作要放在这个操作之前进行。也就是切换数据源操作放在Controller层,可是这样操作会造成Controller层代码混乱的结果。
故而想到的解决方案是将事务管理在数据持久 (Dao层) 开启,切换数据源的操作放在业务层进行操作,就可在事务开启之前顺利进行数据源切换,不会再出现切换失败了。
四、demo 代码
github地址:https://github.com/ChaseDreamBoy/spring-boot-dynamic-data-source
spring boot使用AbstractRoutingDataSource实现动态数据源切换相关推荐
- java 数据源调用_实战分享: Spring boot 调用之间实现动态数据源
需求: 根据项目部署在不同的域名,在同一套系统下,分别访问不同的数据库 (当然在看别人帖子的时候,也发现了不同接口访问不同数据源问题,就是分库动态数据源需求了,其实实现都一样) 业务描述: 部署的时候 ...
- Spring(AbstractRoutingDataSource)实现动态数据源切换
参考:http://linhongyu.blog.51cto.com/6373370/1615895 一.前言 近期一项目A需实现数据同步到另一项目B数据库中,在不改变B项目的情况下,只好选择项目A中 ...
- Spring(AbstractRoutingDataSource)实现动态数据源切换--转载
原始出处:http://linhongyu.blog.51cto.com/6373370/1615895 一.前言 近期一项目A需实现数据同步到另一项目B数据库中,在不改变B项目的情况下,只好选择项目 ...
- Spring Boot + Mybatis多数据源和动态数据源配置
转载自 http://blog.csdn.net/neosmith/article/details/61202084 网上的文章基本上都是只有多数据源或只有动态数据源,而最近的项目需要同时使用两种方式 ...
- Spring Boot + Mybatis 配合 AOP 和注解实现动态数据源切换配置
Spring Boot + Mybatis 配合 AOP 和注解实现动态数据源切换配置 前言: 1. 数据库准备: 2. 环境准备: 3.代码部分 4. 测试: 5.等等 6.配合注解实现 7 .测试 ...
- Spring AOP之四:利用AOP实现动态数据源切换
2019独角兽企业重金招聘Python工程师标准>>> 简介和依赖 项目的前提是安装了MySQL数据库,并且建立了2个数据库一个是master,一个是slave,并且这2个数据库都有 ...
- 动态数据源切换--AbstractRoutingDataSource
转载自http://blog.csdn.net/x2145637/article/details/52461198 在Spring 2.0.1中引入了AbstractRoutingDataSource ...
- spring environment_程序员:Spring项目中简单几步实现多个动态数据源切换
每一个请求与其他的用户是面对不同的数据库,这就需要用到动态数据源切换,来满足不同数据库.不同数据表(不同数据源)的灵活调用. 动态数据源切换 满足mysql.oracle等主流数据库进行动态数据源切换 ...
- Spring Boot 集成 Mybatis 实现双数据源
转载自 Spring Boot 集成 Mybatis 实现双数据源 这里用到了Spring Boot + Mybatis + DynamicDataSource配置动态双数据源,可以动态切换数据源 ...
最新文章
- c++ RTTI(运行时类型识别)
- Scrum中如何实现一个Sprint?
- 计算机术语所见即所得,计算机应用基础作业四(16页)-原创力文档
- c# 两个list比较_C#刷遍Leetcode面试题系列连载(1) 入门与工具简介(VS Code amp; VS)...
- 五、Elasticsearch中的API的简单使用(Python版本)
- CCtalk高可用多媒体服务技术选型与实现
- Apple Swift编程语言新手教程
- leetcode387. 字符串中的第一个唯一字符
- 7款很棒的 HTML5 视频播放器
- 保存界面cd的内容图片到本地
- Linux操作环境下配置MMIX环境
- 远程连接到 SQL Server 2005 时的问题汇集!!
- MySql常用函数大全
- 轨迹路线生成与运动插件 Curvy Spline 的使用
- 智障儿童欢乐多,蹦蹦哒哒过六一:用 Python 开发连连看小游戏
- 【蓝队攻防演练思路】From 滴滴蓝军
- 移植安卓系统到树莓派开发板
- eBPF: 深入探究 Map 类型
- pubg显示服务器安装失败,绝地求生全军出击怎么安装不了 安装失败解决办法
- HTTP/HTTPS与流量劫持/DNS劫持