点击关注公众号,利用碎片时间学习

前言

随着应用用户数量的增加,相应的并发请求的数量也会跟着不断增加,慢慢地,单个数据库已经没有办法满足我们频繁的数据库操作请求了,在某些场景下,我们可能会需要配置多个数据源,使用多个数据源(例如实现数据库的读写分离)来缓解系统的压力等。

同样的,Springboot官方提供了相应的实现来帮助开发者们配置多数据源,一般分为两种方式(目前我所了解到的),分包和AOP,在之前Springboot +Mybatis实现多数据源配置中,我们实现了静态多数据源的配置,但是这种方式怎么说呢,在实际的使用中不够灵活。

为了解决这个问题,我们可以使用上文提到的第二种方法,即使用AOP面向切面编程的方式配合我们的自定义注解来实现在不同数据源之间动态切换的目的。

1. 数据库准备

数据库准备仍然和之前的例子相同,具体建表sql语句则不再详细说明,表格如下:

并分别插入两条记录,为了方便对比,其中testdatasource1为芳年25岁的张三,  testdatasource2为芳年30岁的李四。

2. 环境准备

首先新建一个Springboot项目,我这里版本是2.1.7.RELEASE,并在pom文件中引入相关依赖,和上次相比,这次主要额外新增了aop相关的依赖,如下:

<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId>
</dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>5.1.5.RELEASE</version>
</dependency>
<dependency><groupId>junit</groupId><artifactId>junit</artifactId>
</dependency>
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.2</version>
</dependency>

3.代码部分

首先呢,在我们Springboot的配置文件中配置我们的datasourse,和以往不一样的是,因为我们有两个数据源,所以要指定相关数据库的名称,其中主数据源为primary,次数据源为secondary如下:

#配置主数据库
spring.datasource.primary.jdbc-url=jdbc:mysql://localhost:3306/testdatasource1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
spring.datasource.primary.username=root
spring.datasource.primary.password=root
spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver##配置次数据库
spring.datasource.secondary.jdbc-url=jdbc:mysql://localhost:3306/testdatasource2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
spring.datasource.secondary.username=root
spring.datasource.secondary.password=root
spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driverspring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true

需要我们注意的是,Springboot2.0 在配置数据库连接的时候需要使用jdbc-url,如果只使用url的话会报jdbcUrl is required with driverClassName.错误。

新建一个配置文件,DynamicDataSourceConfig 用来配置我们相关的bean,代码如下

@Configuration
@MapperScan(basePackages = "com.mzd.multipledatasources.mapper", sqlSessionFactoryRef = "SqlSessionFactory") //basePackages 我们接口文件的地址
public class DynamicDataSourceConfig {// 将这个对象放入Spring容器中@Bean(name = "PrimaryDataSource")// 表示这个数据源是默认数据源@Primary// 读取application.properties中的配置参数映射成为一个对象// prefix表示参数的前缀@ConfigurationProperties(prefix = "spring.datasource.primary")public DataSource getDateSource1() {return DataSourceBuilder.create().build();}@Bean(name = "SecondaryDataSource")@ConfigurationProperties(prefix = "spring.datasource.secondary")public DataSource getDateSource2() {return DataSourceBuilder.create().build();}@Bean(name = "dynamicDataSource")public DynamicDataSource DataSource(@Qualifier("PrimaryDataSource") DataSource primaryDataSource,@Qualifier("SecondaryDataSource") DataSource secondaryDataSource) {//这个地方是比较核心的targetDataSource 集合是我们数据库和名字之间的映射Map<Object, Object> targetDataSource = new HashMap<>();targetDataSource.put(DataSourceType.DataBaseType.Primary, primaryDataSource);targetDataSource.put(DataSourceType.DataBaseType.Secondary, secondaryDataSource);DynamicDataSource dataSource = new DynamicDataSource(); dataSource.setTargetDataSources(targetDataSource);dataSource.setDefaultTargetDataSource(primaryDataSource);//设置默认对象return dataSource;}@Bean(name = "SqlSessionFactory")public SqlSessionFactory SqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource)throws Exception {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();bean.setDataSource(dynamicDataSource);bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/*/*.xml"));//设置我们的xml文件路径return bean.getObject();}
}

而在这所有的配置中,最核心的地方就是DynamicDataSource这个类了,DynamicDataSource是我们自定义的动态切换数据源的类,该类继承了AbstractRoutingDataSource 类并重写了它的determineCurrentLookupKey()方法。

AbstractRoutingDataSource 类内部维护了一个名为targetDataSources的Map,并提供的setter方法用于设置数据源关键字与数据源的关系,实现类被要求实现其determineCurrentLookupKey()方法,由此方法的返回值决定具体从哪个数据源中获取连接。同时AbstractRoutingDataSource类提供了程序运行时动态切换数据源的方法,在dao类或方法上标注需要访问数据源的关键字,路由到指定数据源,获取连接。

DynamicDataSource代码如下:

public class DynamicDataSource extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {DataSourceType.DataBaseType dataBaseType = DataSourceType.getDataBaseType();return dataBaseType;}}

DataSourceType类的代码如下:

public class DataSourceType {//内部枚举类,用于选择特定的数据类型public enum DataBaseType {Primary, Secondary}// 使用ThreadLocal保证线程安全private static final ThreadLocal<DataBaseType> TYPE = new ThreadLocal<DataBaseType>();// 往当前线程里设置数据源类型public static void setDataBaseType(DataBaseType dataBaseType) {if (dataBaseType == null) {throw new NullPointerException();}TYPE.set(dataBaseType);}// 获取数据源类型public static DataBaseType getDataBaseType() {DataBaseType dataBaseType = TYPE.get() == null ? DataBaseType.Primary : TYPE.get();return dataBaseType;}// 清空数据类型public static void clearDataBaseType() {TYPE.remove();}}

接下来编写我们相关的Mapper和xml文件,代码如下:

@Component
@Mapper
public interface PrimaryUserMapper {List<User> findAll();
}@Component
@Mapper
public interface SecondaryUserMapper {List<User> findAll();
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jdkcb.mybatisstuday.mapper.one.PrimaryUserMapper"><select id="findAll" resultType="com.jdkcb.mybatisstuday.pojo.User">select * from sys_user;</select></mapper><?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jdkcb.mybatisstuday.mapper.two.SecondaryUserMapper"><select id="findAll" resultType="com.jdkcb.mybatisstuday.pojo.User">select * from sys_user2;</select></mapper>

相关接口文件编写好之后,就可以编写我们的aop代码了:

@Aspect
@Component
public class DataSourceAop {//在primary方法前执行@Before("execution(* com.jdkcb.mybatisstuday.controller.UserController.primary(..))")public void setDataSource2test01() {System.err.println("Primary业务");DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Primary);}//在secondary方法前执行@Before("execution(* com.jdkcb.mybatisstuday.controller.UserController.secondary(..))")public void setDataSource2test02() {System.err.println("Secondary业务");DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Secondary);}
}

编写我们的测试 UserController:。

代码如下:

@RestController
public class UserController {@Autowiredprivate PrimaryUserMapper primaryUserMapper;@Autowiredprivate SecondaryUserMapper secondaryUserMapper;@RequestMapping("primary")public Object primary(){List<User> list = primaryUserMapper.findAll();return list;}@RequestMapping("secondary")public Object secondary(){List<User> list = secondaryUserMapper.findAll();return list;}}

4. 测试

启动项目,在浏览器中分别输入http://127.0.0.1:8080/primaryhttp://127.0.0.1:8080/primary ,结果如下:

[{"user_id":1,"user_name":"张三","user_age":25}][{"user_id":1,"user_name":"李四","user_age":30}]

5.等等

等等,啧啧啧,我看你这不行啊,还不够灵活,几个菜啊,喝成这样,这就算灵活了?

那肯定不能的,aop也有aop的好处,比如两个包下的代码分别用两个不同的数据源,就可以直接用aop表达式就可以完成了,但是,如果想本例中方法级别的拦截,就显得优点不太灵活了,这个适合就需要我们的注解上场了。

6.配合注解实现

首先自定义我们的注解 @DataSource

/*** 切换数据注解 可以用于类或者方法级别 方法级别优先级 > 类级别*/
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {String value() default "primary"; //该值即key值,默认使用默认数据库
}

通过使用aop拦截,获取注解的属性value的值。如果value的值并没有在我们DataBaseType里面,则使用我们默认的数据源,如果有的话,则切换为相应的数据源。

@Aspect
@Component
public class DynamicDataSourceAspect {private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);@Before("@annotation(dataSource)")//拦截我们的注解public void changeDataSource(JoinPoint point, DataSource dataSource) throws Throwable {String value = dataSource.value();if (value.equals("primary")){DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Primary);}else if (value.equals("secondary")){DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Secondary);}else {DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Primary);//默认使用主数据库}}@After("@annotation(dataSource)") //清除数据源的配置public void restoreDataSource(JoinPoint point, DataSource dataSource) {DataSourceType.clearDataBaseType();}
}

7 .测试

修改我们的mapper,添加我们的自定义的@DataSouse注解,并注解掉我们DataSourceAop类里面的内容。如下:

@Component
@Mapper
public interface PrimaryUserMapper {@DataSourceList<User> findAll();
}@Component
@Mapper
public interface SecondaryUserMapper {@DataSource("secondary")//指定数据源为:secondaryList<User> findAll();
}

启动项目,在浏览器中分别输入http://127.0.0.1:8080/primaryhttp://127.0.0.1:8080/primary ,结果如下:

[{"user_id":1,"user_name":"张三","user_age":25}][{"user_id":1,"user_name":"李四","user_age":30}]

到此,就算真正的大功告成啦。

来源:juejin.cn/post/6844903945416998919

推荐:

最全的java面试题库

PS:因为公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。点“在看”支持我们吧!

SpringBoot 优雅实现动态数据源切换配置相关推荐

  1. Spring Boot + Mybatis 配合 AOP 和注解实现动态数据源切换配置

    Spring Boot + Mybatis 配合 AOP 和注解实现动态数据源切换配置 前言: 1. 数据库准备: 2. 环境准备: 3.代码部分 4. 测试: 5.等等 6.配合注解实现 7 .测试 ...

  2. SpringBoot+Mybatis 实现动态数据源切换方案

    背景 最近让我做一个大数据的系统,分析了一下,麻烦的地方就是多数据源切换抽取数据.考虑到可以跨服务器跨数据库抽数,再整理数据,就配置了这个动态数据源的解决方案.在此分享给大家. 实现方案 数据库配置文 ...

  3. springboot动态数据源切换(多数据源配置)

    动态数据源切换即多数据源切换,由于业务的需要或者历史的遗留等原因,一个项目中配置了多个数据库,用于查询不同类型的数据,因此我们就需要经常在各个库中切换数据源,接下来我们将进行具体的说明: 项目结构如下 ...

  4. SpringBoot多数据源切换,AOP实现动态数据源切换

    SpringBoot多数据源切换,AOP实现动态数据源切换 操作数据一般都是在DAO层进行处理,可以选择直接使用JDBC进行编程 或者是使用多个DataSource 然后创建多个SessionFact ...

  5. 动态数据源切换--AbstractRoutingDataSource

    转载自http://blog.csdn.net/x2145637/article/details/52461198 在Spring 2.0.1中引入了AbstractRoutingDataSource ...

  6. spring environment_程序员:Spring项目中简单几步实现多个动态数据源切换

    每一个请求与其他的用户是面对不同的数据库,这就需要用到动态数据源切换,来满足不同数据库.不同数据表(不同数据源)的灵活调用. 动态数据源切换 满足mysql.oracle等主流数据库进行动态数据源切换 ...

  7. springboot动态切换数据源_Springboot整合Mybatis注解实现动态数据源切换

    AbstractRoutingDataSource AbstractRoutingDataSource是spring-jdbc包提供的一个了AbstractDataSource的抽象类,它实现了Dat ...

  8. spring boot使用AbstractRoutingDataSource实现动态数据源切换

    一.AbstractRoutingDataSource Spring boot提供了AbstractRoutingDataSource 根据用户定义的规则选择当前的数据源,这样我们可以在执行查询之前, ...

  9. Spring(AbstractRoutingDataSource)实现动态数据源切换

    参考:http://linhongyu.blog.51cto.com/6373370/1615895 一.前言 近期一项目A需实现数据同步到另一项目B数据库中,在不改变B项目的情况下,只好选择项目A中 ...

最新文章

  1. 浅谈Lucene中的DocValues
  2. 并查集练习(0743) SWUST OJ
  3. [导入]设计模式初学者系列-工厂方法
  4. centos7操作SSH/SSHD服务(查看/启动/重启/自启)
  5. 平衡二叉树操作的演示
  6. MDP蒙特卡罗方法(四)
  7. oracle fopen函数,Utl_File.Fopen异常
  8. calender get方法_Calendar.get()方法--- WEEK_OF_YEAR 、MONTH、
  9. emmet工具使用和技巧
  10. MySQL OCP备考
  11. 为什么现在不看好 CV 方向了呢?
  12. win10分辨率设置_win10分辨率设置方法教程
  13. iPhoneSE3变化却提高了价格,安卓手机获得了喘息的空间
  14. SE3可能是苹果最失败的手机,销量不达预期致上市半月降价促销
  15. 利用代码将网站预测蛋白稳定性突变自动化
  16. 计算机专硕都是数二英二吗,【专硕初试】大改革?英二、数二都不考了?
  17. CRM八面体:客户关系管理成功案例2 Yorkshire Water
  18. 建立良好人际关系的原则
  19. [AtCoder ARC093F]Dark Horse
  20. [CSP2020]儒略日

热门文章

  1. 绩效辅导面谈中的STAR法则和SPIN
  2. 数据安全的下一个风口:SASE云服务平台
  3. Vue/React组件化开发的一些思考
  4. 江苏大学计算机学硕还是专硕简单,2021江苏大学研究生成绩排名是学硕和专硕分开的吗?...
  5. 机器学习实战-python3勘误
  6. 我终于实现了前辈的梦想!
  7. 从知道到懂得,从懂得到做到
  8. 泛微OA集成Wps-V5版本,获取授权相关数据
  9. Android SQLite在应用中数据保存查询,但是拷贝的xxx.db没有数据或没有更新,.db_wal和.db_shm文件
  10. 摘《阿里巴巴JAVA开发手册》易错题目