前言
之前的文章是解析spring中ioc源码 以及 aop源码 ,包括核心的bean的生命周期 以及 各个扩展部分,以及 aop源码 如何开启注解时, 解析注解标签时,将 所有 aop所拥有的控件在bean实例化 之前 和实例化之后的一个 扩展 AnnotationAwareAspectJAutoProxyCreator 这个类上 面做的所有的处理和扩展。本篇文章会继续 研究 事务源码部分, 包括事务隔离级别,以及 事务如何实现的。
Spring事务管理
Spring框架的事务支持模型的优势
传统上,Java EE 开发人员对事务管理有两种选择:全局事务或本地事务,这两者都有很大的局限性。接下来的两节将回顾全局和本地事务管理,然后讨论Spring框架的事务管理支持如何解决全局和本地事务模型的局限性。
事务概念
- READ_UNCOMMITTED 读未提交
- READ_COMMITTED 读已提交
- REPEATABLE_READ 可重复读
- SERIALIZABLE 序列化(串行)
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配置方式
<!-- 配置事务管理器 -->
<bean id="txManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
- 不配置事务运行Main类,查看数据库操作结果
- 配置事务运行Main类,查看数据库操作结果
<!-- 配置事务增强的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> -->
以及隔离级别处理两个事务之间的关系,回滚 与不回滚的操作
注解配置方式
<!-- 开启注解方式的事务配置支持( 注解的方式:@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(propagation = Propagation.REQUIRES_NEW)
public void insertLog(Log log) {
this.logDao.insert(log);
}
声明式事务管理
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事务管理API
TransactionDefinition
TransactionDefinition接口的内部
TransactionDefinition的继承体系
PlatformTransactionManager
PlatformTransactionManager的实现
// 下面的三个动作都是由这个类来完成的
getTransaction()
commit()
rollback()
开启新的事务方法
统一控制 dao层的方法 。
prepareSynchronization
同步器加锁
挂起当前的事务操作
判断事务是否活跃。
并 返回 事务的holder 放的是事务挂起信息
对于 事务活跃的情况,把相关属性装起来,作一个切换。
这些挂起的资源 都放到新的状态里面去了
TransactionStatus
控制 事务等等,保存点。
DefaultTransactionStatus
DataSourceTransactionManager
获取事务,获得连接 处理事务。
断点调试看执行过程
使用的地方
数据源 datasource获取到连接内容 。
拿到事务 这里面就是 为后面的做的处理。
两个service 调用了dao,放到数据库连接上。
最后 走到 开启事务的部分。
这部分做的就是 把事务连接 放到事务对象中。datasourcetrancationmanage中的处理
设置 自动连接的东西 以及创建事务前的处理
这里创建好的事务将他绑定到 对应的threadlocal上面去。
其实最终你会发现 在 事务框架中 通过threadlocal将对应的 datasource 和 连接 存到这里面做一个缓存起来。 并且 创建新事务时将 这个存到事务对象中,当然也会包括许多 前置化的处理以及属性的设置。
F8,JDBCTeamplate.execute的调用栈
F5进入DataSourceUtils.getConnection(obtainDataSource()),看它如何获取Connection
TransactionSynchronizationManager#doGetResource代码,又回到了ThreadLocal的resources
验证其他两种传播行为,及其他的组合情况
事务传播行为
声明式事务处理过程
@Override
public void init() {
registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser());
registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
registerBeanDefinitionParser("jta-transaction-manager", new JtaTransactionManagerBeanDefinitionParser());
}
- 浏览TransactionInterceptor的代码,重点:invoke(MethodInvocation invocation)方法 invokeWithinTransaction方法
- 浏览TransactionInterceptor的继承体系,事务处理的逻辑都在TransactionAspectSupport中
- 浏览TransactionAspectSupport,它里面有如下属性
- 浏览TransactionAttributeSource接口定义
- 浏览TransactionAttributeSource 的继承体系
切面增强过程
- 获得TransactionAttributeSource
- 获得事务定义TransactionAttribute
- 获得TransactionManager
- 如果有对应的事务定义并且事务管理器是ReactiveTransactionManager类型的,进行响应的 处理
- 如果没有对应的事务定义,或者事务管理器不是
对于编程式事务的一个封装。
事务监听
从 Spring 4.2 开始,事件的监听器可以绑定到事务的一个阶段。典型的例子是在事务成功完成时处理事件。当当前事务的结果实际上对侦听器很重要时,这样做可以更灵活地使用事件。
您可以使用 @EventListener 注释注册常规事件侦听器。如果需要将其绑定到事务,请使用@TransactionalEventListener。当您这样做时,默认情况下侦听器绑定到事务的提交阶段。 下一个示例显示了这个概念。假设一个组件发布了一个订单创建的事件,并且我们想要定义一个侦听器,该侦听器仅在发布它的事务成功提交后才处理该事件。以下示例设置了这样一个事件侦听器:
@Component
public class MyComponent {
@TransactionalEventListener
public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) {
// ...
}
}
触发事件监听的一个注解
@TransactionalEventListener 注释公开了一个阶段属性,该属性允许您自定义侦听器应绑定到的事务的阶段。有效阶段是 BEFORE_COMMIT、AFTER_COMMIT(默认)、AFTER_ROLLBACK 和 AFTER_COMPLETION,
它们聚合事务完成(无论是提交还是回滚)。 如果没有事务正在运行,则根本不会调用侦听器,因为我们无法遵守所需的语义。但是,您可以通过将注解的 fallbackExecution 属性设置为 true 来覆盖该行为。
注解的扫描起来,进行存储起来。
事件发布的堆栈
自定义注解
@Import注解
通过@Import导入一个或多个@link Configuration类
引入的类都可以作为 component 注册到ioc中
在spring中对于 import 的 解析 是在 ContextNamespaceHandler 中
AnnotationConfigBeanDefinitionParser 跟着进去
对于 import 注解来说 ConfigurationClassPostProcessor 是在这里做的处理的
ConfigurationClassPostProcessor#processConfigBeanDefinitions
ConfigurationClassParser.processImports(…)方法
最后放到configclass 以及 importstack 中进行 导入进去。
注解用来做配置,简化xml的配置信息。
分布式事务JTA
分布式事务 具有多个数据源
尝试借助本地事务来完成事务管理
从上面得出,做分布式事务管理需要的参与者
XA规范是X/Open(The open group)提出的分布式事务处理规范,分布式事务处理的工业标准。
第二阶段:提交/回滚
JTA
javax下的jar包
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>javax.transaction-api</artifactId>
<version>1.3</version>
</dependency>
- Weblogic
- Websphere
开源、独立的JTA事务管理器(TM)组件:
- Java Open Transaction Manager (JOTM)
- JBoss TS
- Bitronix Transaction Manager (BTM)
- Atomikos
- Narayana
Spring中应用JTA
- Spring自身并不提供jta TM实现,但提供了很好的集成
- 根据TM的提供者不同,分为两种应用方式:
<tx:jta-transaction-manager />
@Bean
public PlatformTransactionManager platformTransactionManager(){
return new JtaTransactionManager();
}
<!-- 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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
# 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>