Spring中事务源码解读

前言

之前的文章是解析spring中ioc源码 以及 aop源码 ,包括核心的bean的生命周期 以及 各个扩展部分,以及 aop源码 如何开启注解时, 解析注解标签时,将 所有 aop所拥有的控件在bean实例化 之前 和实例化之后的一个 扩展 AnnotationAwareAspectJAutoProxyCreator 这个类上 面做的所有的处理和扩展。本篇文章会继续 研究 事务源码部分, 包括事务隔离级别,以及 事务如何实现的。

Spring事务管理

​​​​​​Data Access (spring.io)

Spring 框架为事务管理提供一个一套统一的抽象,带来的好处有: 1. 跨不同事务 API 的统一的编程模型,无论你使用的是 jdbc 、 jta 、 jpa 、 hibernate 。 2. 支持声明式事务 3. 简单的事务管理 API 4. 能与 spring 的数据访问抽象层完美集成

Spring中事务源码解读

 Spring框架的事务支持模型的优势

传统上,Java EE 开发人员对事务管理有两种选择:全局事务或本地事务,这两者都有很大的局限性。接下来的两节将回顾全局和本地事务管理,然后讨论Spring框架的事务管理支持如何解决全局和本地事务模型的局限性。

事务概念

Isolation 隔离级别 此事务与其他事务的工作隔离的程度。例如,该事务能否看到来自其他事务的未提交的写操作
  • READ_UNCOMMITTED 读未提交
  • READ_COMMITTED 读已提交
  • REPEATABLE_READ 可重复读
  • SERIALIZABLE 序列化(串行)
Read/Write 读写 该事务操作是读、是写、还是有读有写 Timeout 超时 对事务执行的时长设置一个阀值,如果超过阀值还未完成则回滚。 Propagation 传播行为 当一个方法开启事务后,在方法中调用了其他的方法,其他方法可能也需要事务管理,此时就涉及事务该如何传播了。 1. TransactionDefinition.PROPAGATION_REQUIRED :如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。 2. TransactionDefinition.PROPAGATION_REQUIRES_NEW :创建一个新的事务,如果当前存在事务,则把当前事务挂起。 3. TransactionDefinition.PROPAGATION_SUPPORTS :如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。 4. TransactionDefinition.PROPAGATION_NOT_SUPPORTED :以非事务方式运行,如果当前存在事务,则把当前事务挂起。 5. TransactionDefinition.PROPAGATION_NEVER :以非事务方式运行,如果当前存在事务,则抛出异常。 6. TransactionDefinition.PROPAGATION_MANDATORY :如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。 7. TransactionDefinition.PROPAGATION_NESTED :如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 TransactionDefinition.PROPAGATION_REQUIRED。 SavePoint 保存点 事务中可以设置一些保存点(阶段标识),回滚时可以指定回滚到前面的哪个保存点。 Commit/Rollback 提交 / 回滚 提交、回滚事务

Spring中的事务使用

<!--transication--> 
	<dependency> 
		<groupId>org.springframework</groupId> 
		<artifactId>spring-jdbc</artifactId> 
		<version>5.2.8.RELEASE</version>
	</dependency> 
	<dependency> 
		<groupId>com.alibaba</groupId> 
		<artifactId>druid</artifactId> 
		<version>1.2.1</version> 
	</dependency> 
	<dependency> 
		<groupId>mysql</groupId> 
		<artifactId>mysql-connector-java</artifactId> 
		<version>8.0.19</version> 
	</dependency> 
	<!-- jta api --> 
	<dependency> 
		<groupId>javax.transaction</groupId> 
		<artifactId>javax.transaction-api</artifactId> 
		<version>1.3</version> 
	</dependency> 
	<!-- atomikos 数据库的TM组件 --> 
	<dependency> 
		<groupId>com.atomikos</groupId> 
		<artifactId>transactions-jdbc</artifactId> 
		<version>4.0.6</version> 
	</dependency> 
	<!-- atomikos JMS 有MQ需要事务管理时加入 --> 
	<dependency> 
		<groupId>com.atomikos</groupId> 
		<artifactId>transactions-jms</artifactId> 
		<version>4.0.6</version> 
		
	</dependency>

XML配置方式

application.xml 文件中配置
<!-- 配置事务管理器 --> 
<bean id="txManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 
    <property name="dataSource" ref="dataSource"/>
 </bean>
Spring 事务管理起到的作用
  • 不配置事务运行Main类,查看数据库操作结果
  • 配置事务运行Main类,查看数据库操作结果
UserService 操作了 User 、 Log 两张表,两张表,在配置和不配置事务管理的情况下,两张数据库表一致性不一样。 其次需要结合 aop来使用
<!-- 配置事务增强的advice -->
    <tx:advice id="txAdvice" transaction-manager="txManager">
		<!--配置事务的属性,通过method来进行配置-->
        <tx:attributes>
            <!-- all methods starting wit`h 'get' are read-only -->
            <tx:method name="get*" read-only="true" />
            <!-- other methods use the default transaction settings (see below) -->
            <tx:method name="*" />
        </tx:attributes>
    </tx:advice>

 <!-- 配置事务的AOP切面 --> 
	<!--<aop:config>
        <aop:pointcut id="allService" expression="execution(* edu.courseware.spring.tx.service.*Service.*(..)))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="allService"/>
    </aop:config> -->

以及隔离级别处理两个事务之间的关系,回滚 与不回滚的操作

Spring中事务源码解读

注解配置方式

在 xml 中开始注解方式支持
<!-- 开启注解方式的事务配置支持( 注解的方式:@EnableTransactionManagement) --> 
<tx:annotation-driven transaction-manager="txManager"/>
或以注解的方式开启
@Configuration 
@ComponentScan("com.study.mike.spring.sample.tx") 
@ImportResource("classpath:com/study/mike/spring/sample/tx/application.xml") 
@EnableTransactionManagement 
public class TxMain { 
}
在要加事务管理的类或方法上加 @Transactional 注解
@Transactional(propagation = Propagation.REQUIRES_NEW) 
public void insertLog(Log log) { 
    this.logDao.insert(log);
}
掌握 @Transactional 的属性配置: Spring中事务源码解读rollbackFor 默认情况下是对 RuntimeException 进行回滚。

声明式事务管理

 Spring提供基于AOP的声明式事务管理,当有大量的事务需要进行管理时,声明式事务管理更合适,让 我们的事务管理变得简单、易用!

编程式事务管理

需要快速简单的事务管理时,适用编程式事务管理。

// 1、创建事务定义
		DefaultTransactionDefinition definition = new DefaultTransactionDefinition();

		// 2、根据定义开启事务
		TransactionStatus status = txManager.getTransaction(definition);

		try {
			this.userDao.insert(u);
			Log log = new Log(System.currentTimeMillis() + "", System.currentTimeMillis() + "-" + u.getUserName());
			// this.doAddUser(u);
			this.logService.insertLog(log);
			// 3、提交事务
			txManager.commit(status);
		} catch (Exception e) {
			// 4、异常了,回滚事务
			txManager.rollback(status);
			throw e;
		}
		// 在TransactionTemplate中使用的也是编程式事务管理方式

Spring中事务源码解读

TransactionDefinition 结构 编程式事务管理简化使用 TransactionTemplate , TransactionTemplate 中的 execute 方法代码实现:

 Spring中事务源码解读

Spring事务管理API 

Spring 为事务管理提供了统一的抽象建模,这样我们使用 Spring 来进行事务管理时,就只需要学会这套 API即可,无论底下使用的是何种事务管理方式, jdbc 也好, jpa 也好, hibernate 也好, jta 也好。我们的业务代码中都是面向spring 的事务 API 。

TransactionDefinition

TransactionDefinition :事务定义。 Spring 事务管理框架将为我们管理事务,但不清楚该如何替我们管 理,我们就通过事务定义来定义我们需要的事务管理信息,把这些信息给事务管理器,它就知道我们的意图了。

TransactionDefinition接口的内部

Spring中事务源码解读

ISOLATION_DEFAULT :使用的是底层(数据库)默认的隔离级别,不同的数据库默认隔离级别不同, 数据库也是可以配置这个默认属性的。 TransactionDefinition 实现体系 Spring中事务源码解读 默认的事务配置 , 包括下面的。 Spring中事务源码解读

Spring中事务源码解读

TransactionAttribute 前面的事务定义中没有回滚的信息,在 TransactionAttribute 有定义 Spring中事务源码解读

TransactionDefinition的继承体系

Spring中事务源码解读

Spring中事务源码解读

PlatformTransactionManager

PlatformTransactionManager 平台级的事务管理器,它抽象定义了事务管理行为,不同的事务管理实现实现该接口。我们编程面向该接口。 PlatformTransactionManager 的接口定义 接口中定义了的事务管理行为。 Spring中事务源码解读

 PlatformTransactionManager的实现

Spring中事务源码解读

分布式事务,则用 JTA AbstractPlatformTransactionManager 对 PlatformTransactionManager 的三个方法的实现逻辑
// 下面的三个动作都是由这个类来完成的
 getTransaction() 
commit() 
rollback()
getTransaction的流程  1. 获取事务对象 2. 如果当前存在事务,则根据事务定义中的传播行为来进行处理 3. 当前不存在事务,则根据事务定义的传播行为来决定如何处理 3.1 如果一定需要事务,而当前又不存在事务,则抛出异常 3.2 如果是下面的三种传播行为,则创建事务 挂起当前事务 开始一个新的事务 3.3 如果传播行为是默认的,使用一个空的事务,交给底层去进行处理 Spring中事务源码解读

 Spring中事务源码解读

开启新的事务方法

创建一个新的同步 开始事务 初始化事务同步控制的信息 Spring中事务源码解读

Spring中事务源码解读

Spring中事务源码解读

 统一控制  dao层的方法  。Spring中事务源码解读

 prepareSynchronization

同步器加锁

Spring中事务源码解读

Spring中事务源码解读

 挂起当前的事务操作

Spring中事务源码解读

 判断事务是否活跃。

 并 返回 事务的holder  放的是事务挂起信息Spring中事务源码解读

 Spring中事务源码解读

 对于 事务活跃的情况,把相关属性装起来,作一个切换。

Spring中事务源码解读

 这些挂起的资源 都放到新的状态里面去了

Spring中事务源码解读

TransactionStatus

TransactionStatus 事务状态,持有事务的状态信息。事务管理代码可通过它获取事务状态、以及显式地设置回滚(代替异常的方式)。它继承了SavePoint 接口。在它的实现中会持有事务的很多对象:如事务对象、被挂起的事务资源等等。 从 TransactionManager 中获取事务得到它,提交 / 回滚事务时要给入它: Spring中事务源码解读

控制  事务等等,保存点。

 Spring中事务源码解读

 DefaultTransactionStatus

Spring中事务源码解读

DataSourceTransactionManager

DataSourceTransactionManager 是基于 jdbc connection 的本地事务管理实现。多个方法调用参与到同一个事务,是通过共用connection 来完成的,这里面实现的标准 Spring中事务源码解读

 获取事务,获得连接  处理事务。

断点调试看执行过程

使用的地方

Spring中事务源码解读

AbstractPlatformTransactionManager.getTransaction  打断点看情况

Spring中事务源码解读

getTransaction ,看调用栈 获取到连接,并做绑定起来。

 Spring中事务源码解读

数据源  datasource获取到连接内容 。

Spring中事务源码解读

Spring中事务源码解读

拿到事务   这里面就是 为后面的做的处理。

Spring中事务源码解读

 两个service 调用了dao,放到数据库连接上。

最后 走到 开启事务的部分。

Spring中事务源码解读

 这部分做的就是 把事务连接 放到事务对象中。datasourcetrancationmanage中的处理Spring中事务源码解读

 设置 自动连接的东西   以及创建事务前的处理

Spring中事务源码解读

 这里创建好的事务将他绑定到 对应的threadlocal上面去。

Spring中事务源码解读

 其实最终你会发现  在 事务框架中 通过threadlocal将对应的 datasource  和 连接 存到这里面做一个缓存起来。  并且  创建新事务时将 这个存到事务对象中,当然也会包括许多 前置化的处理以及属性的设置。

JdbcTemplate.execute UserDao 中是使用 JdbcTemplate 来进行的操作,找到 JdbcTemplate 的 update 操作的获取 connection 的代码,加断点,然后F8 ,让程序执行到这里,看下调用栈。 Spring中事务源码解读

F8,JDBCTeamplate.execute的调用栈 

Spring中事务源码解读

F5进入DataSourceUtils.getConnection(obtainDataSource()),看它如何获取Connection  

Spring中事务源码解读

 TransactionSynchronizationManager#doGetResource代码,又回到了ThreadLocal的resources

从 ThreadLocal 获得相同的数据库连接,才能进行事务的管理和控制。 第二次进到 getTransaction Spring中事务源码解读 UserService 和 LogService 都用了同一个 Connection ,也处于同一个事务中。 跟代码看已存在事务的处理逻辑, AbstractPlatformTransactionManager#handleExistingTransaction方法的实现。

Spring中事务源码解读

 验证其他两种传播行为,及其他的组合情况

事务传播行为

Spring中事务源码解读

 Spring中事务源码解读

 声明式事务处理过程

标签解析 TxNamespaceHandler
@Override 
public void init() { 
    registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser());     
     registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser()); 
     registerBeanDefinitionParser("jta-transaction-manager", new JtaTransactionManagerBeanDefinitionParser()); 
}
Spring中事务源码解读TxAdviceBeanDefinitionParser <tx:advice> 标签的解析器 TxAdviceBeanDefinitionParser    TxAdviceBeanDefinitionParser的关键方法

Spring中事务源码解读

TransactionInterceptor TransactionInterceptor 就是一个环绕织入 MethodInterceptor 的实现
  • 浏览TransactionInterceptor的代码,重点:invoke(MethodInvocation invocation)方法 invokeWithinTransaction方法

Spring中事务源码解读

  •  浏览TransactionInterceptor的继承体系,事务处理的逻辑都在TransactionAspectSupport中

Spring中事务源码解读

  • 浏览TransactionAspectSupport,它里面有如下属性

 Spring中事务源码解读

它的重点方法是 invokeWithinTransaction TransactionAttributeSource
  • 浏览TransactionAttributeSource接口定义

Spring中事务源码解读

  • 浏览TransactionAttributeSource 的继承体系  

Spring中事务源码解读

 Spring中事务源码解读

 切面增强过程

TransactionAspectSupport.invokeWithinTransaction 方法 从代码中可看出:标准的编程式事务管理流程
  • 获得TransactionAttributeSource
  • 获得事务定义TransactionAttribute
  • 获得TransactionManager
  • 如果有对应的事务定义并且事务管理器是ReactiveTransactionManager类型的,进行响应的 处理
  • 如果没有对应的事务定义,或者事务管理器不是

Spring中事务源码解读

对于编程式事务的一个封装。

事务监听

Data Access (spring.io)

从 Spring 4.2 开始,事件的监听器可以绑定到事务的一个阶段。典型的例子是在事务成功完成时处理事件。当当前事务的结果实际上对侦听器很重要时,这样做可以更灵活地使用事件。

您可以使用 @EventListener 注释注册常规事件侦听器。如果需要将其绑定到事务,请使用@TransactionalEventListener。当您这样做时,默认情况下侦听器绑定到事务的提交阶段。 下一个示例显示了这个概念。假设一个组件发布了一个订单创建的事件,并且我们想要定义一个侦听器,该侦听器仅在发布它的事务成功提交后才处理该事件。以下示例设置了这样一个事件侦听器:

本质上就是一个EventListener,类似继承 @TransactionalEventListener
@Component
public class MyComponent {

    @TransactionalEventListener
    public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) {
        // ...
    }
}

Spring中事务源码解读

 触发事件监听的一个注解

Spring中事务源码解读

 @TransactionalEventListener 注释公开了一个阶段属性,该属性允许您自定义侦听器应绑定到的事务的阶段。有效阶段是 BEFORE_COMMIT、AFTER_COMMIT(默认)、AFTER_ROLLBACK 和 AFTER_COMPLETION,

它们聚合事务完成(无论是提交还是回滚)。 如果没有事务正在运行,则根本不会调用侦听器,因为我们无法遵守所需的语义。但是,您可以通过将注解的 fallbackExecution 属性设置为 true 来覆盖该行为。

Spring 中的事件发布订阅机制 整体而言 Spring 的事件机制是通过发布订阅来达到的。 Spring中事务源码解读

 

它需要注册一个事件监听处理器, EventListenerMethodProcessor 就是用来处理事件方法监听,只不过最终使用 TransactionalEventListenerFactory 生成一个 Adapter 适配器。 Spring中事务源码解读

Spring中事务源码解读 

 注解的扫描起来,进行存储起来。Spring中事务源码解读

ApplicationListenerMethodAdapter 下面有一个 ApplicationListenerMethodTransactionalAdapter 类,用来处理事务监听器的。 Spring中事务源码解读 AbstractPlatformTransactionManager#triggerAfterCommit TransactionSynchronizationUtils#invokeAfterCompletion AbstractPlatformTransactionManager#triggerBeforeCommit TransactionSynchronizationUtils#triggerBeforeCommit after 事件触发

Spring中事务源码解读

 事件发布的堆栈 Spring中事务源码解读

自定义注解

 @Import注解

 通过@Import导入一个或多个@link Configuration类   

4.2后,三种情况:@Configuration、ImportSelector、ImportBeanDefinitionRegistrar 的实现会被IOC注册 Spring中事务源码解读

引入的类都可以作为 component 注册到ioc中

在spring中对于 import 的  解析  是在  ContextNamespaceHandler 中

Spring中事务源码解读

 

 AnnotationConfigBeanDefinitionParser 跟着进去

Spring中事务源码解读

 对于 import 注解来说 ConfigurationClassPostProcessor   是在这里做的处理的 

Spring中事务源码解读

ConfigurationClassPostProcessor#processConfigBeanDefinitions

Spring中事务源码解读

Spring中事务源码解读

 ConfigurationClassParser.processImports(…)方法

Spring中事务源码解读

 Spring中事务源码解读

最后放到configclass 以及 importstack 中进行 导入进去。

注解用来做配置,简化xml的配置信息。

分布式事务JTA 

 分布式事务  具有多个数据源

 Spring中事务源码解读

一个事务包含多个操作,多个操作操作了多个数据源,这样的事务称为分布式事务。 与普通事务的区别 普通事务操作,一个单一数据源事务 Spring中事务源码解读 单一数据源,事务管理可以借助数据源本地事务完成,实现简单! 分布式事务管理之困难:不可简单借助数据源本地事务完成! 一个分布式事务示例 Spring中事务源码解读

 尝试借助本地事务来完成事务管理

分布式事务管理需要什么 分布式事务管理需要的机制 1. 协调各数据源提交、回滚,及应对通信异常的管理机制。 2. 数据源需要支持这种机制。 3. 应对应用故障恢复的机制。

 从上面得出,做分布式事务管理需要的参与者

Spring中事务源码解读

 

XA 规范 什么是 XA 规范

 XA规范是X/Open(The open group)提出的分布式事务处理规范,分布式事务处理的工业标准。

Spring中事务源码解读

 Spring中事务源码解读

 

XA- 两阶段提交 第一阶段:准备阶段 Spring中事务源码解读

第二阶段:提交/回滚

Spring中事务源码解读

 JTA

JTA: Java Transaction API JAVA 根据 XA 规范提出的事务处理 API 标准 目的:统一 API, 简化程序员的学习,简化编程 Spring中事务源码解读

 javax下的jar包

<dependency>
    <groupId>javax.transaction</groupId>
    <artifactId>javax.transaction-api</artifactId>
    <version>1.3</version>
</dependency>

Spring中事务源码解读

JTA-API 构成 面向 TM RM 提供商的 API  TransactionManager  Transaction  XARsource  Xid TM 实现提供商 JavaEE 应用服务器内建 JTA 事务管理( TM ),提供商:
  • Weblogic
  • Websphere

 开源、独立的JTA事务管理器(TM)组件:

  • Java Open Transaction Manager (JOTM)
  • JBoss TS
  • Bitronix Transaction Manager (BTM)
  • Atomikos
  • Narayana
RM 实现提供商 一般数据库厂商会提供实现 在连接池组件中一般也会提供包装实现:

Spring中应用JTA

Spring 中的 JTA
  • Spring自身并不提供jta TM实现,但提供了很好的集成
  • 根据TM的提供者不同,分为两种应用方式:
方式一:使用 javaEE 服务器内建的 TM 方式二:在没有实现 TM 的应用服务器上( Tomcat,jetty ),将独立 TM 组件集成到我们的 spring 应 用中 使用 JavaEE 服务器内建 TM 用法:做如下配置即可 xml 配置
<tx:jta-transaction-manager />
或者 Java 配置
@Bean
public PlatformTransactionManager platformTransactionManager(){
    return new JtaTransactionManager();
}
说明: JtaTransactionManager 通过 JNDI 找到服务器提供的 java:comp/UserTransaction, java:comp/TransactionManager 应用使用的数据源需是支持 xa 的数据源。 使用轻量级服务器 + 集成 TM 组件 操作步骤: 1. 引入 TM 组件的 jar (以 Atomikos 为例) Spring 应用方式
<!-- jta api -->
<dependency>
    <groupId>javax.transaction</groupId>
    <artifactId>javax.transaction-api</artifactId>
    <version>1.3</version>
</dependency>
<!-- atomikos 数据库的TM组件 -->
<dependency>
    <groupId>com.atomikos</groupId>
    <artifactId>transactions-jdbc</artifactId>
    <version>4.0.6</version>
</dependency>
<!-- atomikos JMS 有MQ需要事务管理时加入 -->
<dependency>
    <groupId>com.atomikos</groupId>
    <artifactId>transactions-jms</artifactId>
    <version>4.0.6</version>
</dependency>
Spring Boot Starter 方式
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jta-atomikos</artifactId>
    <version>2.1.1.RELEASE</version>
</dependency>
配置数据源,一定要是 XA 数据源 准备两个数据源 properties 配置数据源
# jdbc properties
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.username=root
jdbc.password=root

db1.jdbc.url=jdbc:mysql://127.0.0.1:3306/test?
useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
db2.jdbc.url=jdbc:mysql://127.0.0.1:3306/logdb?
useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC

Spring XML配置XA DataSource

<!-- 加载配置参数 -->
<context:property-placeholder
location="classpath:com/study/mike/spring/sample/jta/application.properties"/>
<!-- xa数据源1 -->
<bean id="db1DataSource“ class="com.atomikos.jdbc.AtomikosDataSourceBean "init-method="init" destroy-method="close">
<!-- 给数据源取个唯一区分的名称 -->
<property name="uniqueResourceName" value="mysql/db01" />
<!-- 真正使用的 XA DataSource 类名 -->
    <property name="xaDataSourceClassName"value="com.alibaba.druid.pool.xa.DruidXADataSource" />
<!-- 数据源连接相关配置参数 -->
   <property name="xaProperties">
    <props>
        <prop key="url">${db1.jdbc.url}</prop>
        <prop key="username">${jdbc.username}</prop>
        <prop key="password">${jdbc.password}</prop>
    </props>
   </property>
</bean>
<!-- xa数据源2 -->
<bean id="db2DataSource“ class="com.atomikos.jdbc.AtomikosDataSourceBean“
init-method="init" destroy-method="close">
……
</bean>
配置事务管理器 TM 1. TransactionManager 的实现 bean 2. UserTransaction 的实现 bean 3. spring 的 JtaTransactionManager (注入 TM 、 UserTransaction ) 如果是在 spring-boot 中, 用 spring-boot-starter-jta-atomikos ,这步会自动配置好,不需手动配 置! Spring 应用 JTA 使用 jta 需满足: 1. 数据源支持分布式事务 2. 要有 jta 的实现提供者( javaEE 服务器内建的或独立实现组件) 3. Spring 中配置使用 JtaTransactionManager 来处理事务
上一篇:第十一章、Spring配置文件参数化


下一篇:springboot多种数据源配置使用