了解整个事务的执行过程,那么就必须要得先了解事务的传播特性和隔离级别
传播特性有以下七种,传播属性默认值为 REQUIRED:当前存在事务,就使用当前事务,否则创建一个新的事务

事务隔离级别指的是一个事务对数据的修改与另一个并行的事务的隔离程度,当多个事务同时访问相同数据时,如果没有采取必要的隔离机制,就可能发生以下问题👇
Spring 中支持的隔离级别

->ORACLE(读已提交) MySQL(可重复读)再必须强调一遍,不是事务隔离级别设置得越高越好,事务隔离级别设置得越高,意味着势必要花手段去加锁用以保证事务的正确性,那么效率就要降低,因此实际开发中往往要在效率和并发正确性之间做一个取舍,一般情况下会设置为 READ_COMMITED,此时避免了脏读,并发性也还不错,之后再通过一些别的手段去解决不可重复读和幻读的问题就好了

如果交给我们自己来设计一个事务系统时,一般分以下几个批次进行处理👇
在执行程序业务逻辑前,如果其被事务代理所修饰,会代理执行 DynamicAdvisedInterceptor#intercept 方法,获取到拦截器责任链 advisor 后,链式调用 proceed 方法;整个事务处理过程中,包含了以下 advisor
事务核心类
- protected static final class TransactionInfo {
-
- @Nullable
- private final PlatformTransactionManager transactionManager; // 事务管理器
-
- @Nullable
- private final TransactionAttribute transactionAttribute; // 事务属性:传播机制、隔离级别、超时时间、是否只读
-
- private final String joinpointIdentification;
-
- @Nullable
- private TransactionStatus transactionStatus; // 事务状态:是否为新事务、是否是需要新同步、挂起的连接资源
-
- @Nullable
- private TransactionInfo oldTransactionInfo; // 同一个线程内旧的事务信息
-
- public TransactionInfo(@Nullable PlatformTransactionManager transactionManager,
- @Nullable TransactionAttribute transactionAttribute, String joinpointIdentification) {
-
- this.transactionManager = transactionManager;
- this.transactionAttribute = transactionAttribute;
- this.joinpointIdentification = joinpointIdentification;
- }
-
- public PlatformTransactionManager getTransactionManager() {
- Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
- return this.transactionManager;
- }
-
- @Nullable
- public TransactionAttribute getTransactionAttribute() {
- return this.transactionAttribute;
- }
-
- // 获取方法的全限定名称
- public String getJoinpointIdentification() {
- return this.joinpointIdentification;
- }
-
- public void newTransactionStatus(@Nullable TransactionStatus status) {
- this.transactionStatus = status;
- }
-
- @Nullable
- public TransactionStatus getTransactionStatus() {
- return this.transactionStatus;
- }
-
- // 是否存在事务
- public boolean hasTransaction() {
- return (this.transactionStatus != null);
- }
-
- private void bindToThread() {
- // 暴露当前的事务状态,将存在的事务进行挂起,等待当前事务完成后再将已存在的事务进行恢复
- this.oldTransactionInfo = transactionInfoHolder.get();
- transactionInfoHolder.set(this);
- }
-
- private void restoreThreadLocalStatus() {
- // 将老的事务状态进行重新设定
- transactionInfoHolder.set(this.oldTransactionInfo);
- }
-
- @Override
- public String toString() {
- return (this.transactionAttribute != null ? this.transactionAttribute.toString() : "No transaction");
- }
- }复制代码

调用事务方法的入口会到达 TransactionInterceptor#invoke#invokeWithinTransaction(以事务的方式调用目标方法,在这埋了一个钩子函数,用来回调目标方法的)事务的整个处理逻辑如下:
PlatformTransactionManager 类型「其提供三种能力:1、获取事务状态对象;2、提交事务;3、回滚事务」,最后获取该方法的全限定方法名newConnectionHolder=false 并返回数据源事务对象NERVER(从不使用事务,则存在抛出异常),则抛出异常结束NOT_SUPPORTED(不支持事务),挂起当前事务:将外层事务相关的连接持有器和属性封装为 SuspendedResourcesHolder 返回,并且创建的一个新的非事务的状态,同时将外层事务的挂起资源持有器作为参数一起进行实例化REQURES_NEW(挂起已经存在的事务,开启一个新的事务):挂起当前事务后「1.清空线程本地的连接持有器;2.清空线程本地资源的所有资源信息;3.将之前的事务属性和连接器这些信息保存到 oldTransaction 变量里面;4.返回挂起的事务信息」,startTransaction:开启一个新的事务状态,同时将外层事务的挂起资源作为参数一起实例化,newTransaction、newSynchronization 属性值都为 true,开辟一个新的连接、设置好事务同步管理器中的线程本地变量「事务是否激活状态、事务的隔离级别、是否为只读事务、事务名称」NESTED(存在事务就使用,不存在就创建一个新的,并且设置一个保存点):通过当前的事务再次构建一个 DefaultTransactionStatus 对象,newTransaction、newSynchronization 属性值都改为 false,并为当前创建好的事务状态对象设置一个保存点.MANDATORY(不存在事务则抛出异常),则抛出异常结束REQUIRED、REQUIRES_NEW、NESTED 的话,开启创建事务信息的过程「1、挂起一个空的事务,因为当前事务是属于最外层的;2、如上的:startTransaction」SUPPORTED、NERVER、NOT_SUPPORTED 类型传播机制的话,那么就创建一个空的事务
如果整个事务正常执行完成,事务就需要正常提交了,执行方法:commitTransactionAfterReturning~AbstractPlatformTransactionManage#commit「处理提交,先处理保存点,然后处理新事务,如果不是新事务不会真正提交,要等外层是新事务的才提交,最后根据条件执行数据清除、线程的私有资源解绑,重置连接自动提交、隔离级别、是否只读、释放连接、恢复挂起事务等」,如下是处理提交的源码
- private void processCommit(DefaultTransactionStatus status) throws TransactionException {
- try {
- boolean beforeCompletionInvoked = false;
- try {
- boolean unexpectedRollback = false;
- // 预留
- prepareForCommit(status);
- // 添加 TransactionSynchronization 中的对应方法的调用
- triggerBeforeCommit(status);
- // 提交完成前回调
- triggerBeforeCompletion(status);
- beforeCompletionInvoked = true;
- // 有保存点
- if (status.hasSavepoint()) {
- if (status.isDebug()) {
- logger.debug("Releasing transaction savepoint");
- }
- // 是否有全局回滚标记
- unexpectedRollback = status.isGlobalRollbackOnly();
- // 如果存在保存点则清除保存点信息
- status.releaseHeldSavepoint();
- }
- // 当前状态是新事务
- else if (status.isNewTransaction()) {
- if (status.isDebug()) {
- logger.debug("Initiating transaction commit");
- }
- unexpectedRollback = status.isGlobalRollbackOnly();
- // 如果是独立的事务则直接提交
- doCommit(status);
- }
- else if (isFailEarlyOnGlobalRollbackOnly()) {
- unexpectedRollback = status.isGlobalRollbackOnly();
- }
- // 有全局回滚标记就报异常
- if (unexpectedRollback) {
- throw new UnexpectedRollbackException(
- "Transaction silently rolled back because it has been marked as rollback-only");
- }
- }
- catch (UnexpectedRollbackException ex) {
- // can only be caused by doCommit
- triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
- throw ex;
- }
- catch (TransactionException ex) {
- // can only be caused by doCommit
- if (isRollbackOnCommitFailure()) {
- doRollbackOnCommitException(status, ex);
- }
- else {
- triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
- }
- throw ex;
- }
- catch (RuntimeException | Error ex) {
- if (!beforeCompletionInvoked) {
- triggerBeforeCompletion(status);
- }
- // 提交过程中出现异常则回滚
- doRollbackOnCommitException(status, ex);
- throw ex;
- }
-
- try {
- // 提交后回调
- triggerAfterCommit(status);
- }
- finally {
- // 提交后清除线程私有同步状态
- triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
- }
-
- }
- finally {
- //根据条件,完成后数据清除,和线程的私有资源解绑,重置连接自动提交,隔离级别,是否只读,释放连接,恢复挂起事务等
- cleanupAfterCompletion(status);
- }
- }复制代码
无论是正常提交或者发生了异常,一些基本的资源应该被释放,清除当前的事务信息,恢复线程本地老的事务信息
- private void restoreThreadLocalStatus() {
- transactionInfoHolder.set(this.oldTransactionInfo);
- }复制代码

如果在执行过程中,发生了异常,执行方法:completeTransactionAfterThrowing~AbstractPlatformTransactionManager#rollback
unexpected 这个一般是 false,除非是设置 rollback-only=true 才是 true 或者是内层异常了给予了回滚标记,如外层 REQUIRED 内层也是 REQUIRED,表示是全局的回滚标记。首先会进行回滚前回调,然后判断是否设置了保存点,比如 NESTED 会设置,要先回滚到保存点。如果状态是新的事务,那就进行回滚,如果不是新的,就设置一个回滚标记,内部是设置连接持有器回滚标记。然后回滚完成回调,根据事务状态信息,完成后数据清除和线程的私有资源解绑,重置连接自动提交,隔离级别,是否只读,释放连接,恢复挂起事务等
如下代码是具体处理异常时回滚的源码
- private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
- try {
- // 意外的回滚
- boolean unexpectedRollback = unexpected;
- try {
- // 回滚完成前回调
- triggerBeforeCompletion(status);
- // 有保存点回滚到保存点
- if (status.hasSavepoint()) {
- if (status.isDebug()) {
- logger.debug("Rolling back transaction to savepoint");
- }
- status.rollbackToHeldSavepoint();
- }
- // 当前状态是一个新事务
- else if (status.isNewTransaction()) {
- if (status.isDebug()) {
- logger.debug("Initiating transaction rollback");
- }
- // 进行回滚
- doRollback(status);
- }
- else {
- // 内层事务为 REQUIRED 传播机制时会走这里,设置全局的回滚标记
- if (status.hasTransaction()) {
- if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
- if (status.isDebug()) {
- logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
- }
- //设置连接要回滚标记,也就是全局回滚
- doSetRollbackOnly(status);
- }
- else {
- if (status.isDebug()) {
- logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
- }
- }
- }
- else {
- logger.debug("Should roll back transaction but cannot - no transaction available");
- }
- if (!isFailEarlyOnGlobalRollbackOnly()) {
- unexpectedRollback = false;
- }
- }
- }
- catch (RuntimeException | Error ex) {
- triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
- throw ex;
- }
- // 回滚完成后回调
- triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
- // 存在全局的回滚标记导致抛出如下异常
- if (unexpectedRollback) {
- throw new UnexpectedRollbackException(
- "Transaction rolled back because it has been marked as rollback-only");
- }
- }
- finally {
- // 根据事务状态信息,完成后数据清除,和线程的私有资源解绑,重置连接自动提交,隔离级别,是否只读,释放连接,恢复挂起事务等
- cleanupAfterCompletion(status);
- }
- }复制代码
代码部分
- public class BookDao {
- JdbcTemplate jdbcTemplate;
- public JdbcTemplate getJdbcTemplate() {
- return jdbcTemplate;
- }
- public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
- this.jdbcTemplate = jdbcTemplate;
- }
- /**
- * 减库存,减去某本书的库存
- *
- * @param id
- */
- public void updateStock(int id) {
- try {
- String sql = "update book_stock set stock=stock-1 where id=?";
- jdbcTemplate.update(sql, id);
- for (int i = 1; i >= 0; i--)
- System.out.println(10 / i);
- }catch (Exception e) {
- }
- }
- }复制代码
- public class BookService {
- BookDao bookDao;
- public BookDao getBookDao() {
- return bookDao;
- }
- public void setBookDao(BookDao bookDao) {
- this.bookDao = bookDao;
- }
- /**
- * 结账:传入哪个用户买了哪本书
- */
- public void checkout(String username, int id) {
- try {
- bookDao.updateStock(id);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }复制代码
- <context:property-placeholder location="classpath:dbconfig.properties"/>
- <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
- <property name="username" value="${jdbc.username}"/>
- <property name="password" value="${jdbc.password}"/>
- <property name="url" value="${jdbc.url}"/>
- <property name="driverClassName" value="${jdbc.driverClassName}"/>
- </bean>
- <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" >
- <constructor-arg name="dataSource" ref="dataSource"/>
- </bean>
- <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
- <property name="dataSource" ref="dataSource"/>
- </bean>
- <bean id="bookService" class="com.vnjohn.tx.xml.service.BookService">
- <property name="bookDao" ref="bookDao"/>
- </bean>
- <bean id="bookDao" class="com.vnjohn.tx.xml.dao.BookDao">
- <property name="jdbcTemplate" ref="jdbcTemplate"/>
- </bean>
- <aop:config>
- <aop:pointcut id="txPoint" expression="execution(* com.vnjohn.tx.xml.*.*.*(..))"/>
- <aop:advisor advice-ref="myAdvice" pointcut-ref="txPoint"/>
- </aop:config>
- <tx:advice id="myAdvice" transaction-manager="transactionManager">
- <tx:attributes>
- <tx:method name="checkout" propagation="REQUIRED"/>
- <tx:method name="updateStock" propagation="REQUIRED"/>
- </tx:attributes>
- </tx:advice>复制代码
- public class TxTest {
- public static void main(String[] args) throws SQLException {
- System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"d:\\code");
- ApplicationContext context = new ClassPathXmlApplicationContext("tx.xml");
- BookService bookService = context.getBean("bookService", BookService.class);
- bookService.checkout("vnjohn",1);
- }
- }复制代码
外层事务:REQUIRED
外层事务:MANDATORY
内层事务不管是那种传播机制,其执行结果都是一样的,MANDATORY 不可以作为外层事务,在运行的时候必须需要一个事务,如果没有事务,会抛出异常
外层事务:SUPPORTS
外层事务:NEVER
外层事务:NOT_SUPPORTED
外层事务:REQUIRED_NEW
外层事务:NESTED
如果自己动手把每一种情况都演示了,其实挺好理解的,关键是大家舍不舍得花费时间一个一个去验证,在面试过程中,可能会经常问一下两个问题
REQUIRED 和 NESTED 回滚的区别
在回答两种方式区别的时候,最大的问题在于保存点的设置,很多同学会认为内部设置 REQUIRED 和 NESTED 效果是一样的,其实在外层方法对内层方法的异常情况在进行捕获的时候区别很大,两者报的异常信息都不同,使用 REQUIRED 的时候,会报 Transaction rolled back because it has been marked as rollback-only 信息,因为内部异常了,设置了回滚标记,外部捕获之后,要进行事务的提交,此时发现有回滚标记,那么意味着要回滚,所以会报异常,而 NESTED 不会发生这种情况,因为在回滚的时候把回滚标记清除了,外部捕获异常后去提交,没发现回滚标记,就可以正常提交了
REQUIRED_NEW 和 REQUIRED 区别
这两种方式产生的效果是一样的,但是 REQUIRED_NEW 会有新的连接生成,而 NESTED 使用的是当前事务的连接,而且 NESTED 还可以回滚到保存点,REQUIRED_NEW 每次都是一个新的事务,没有办法控制其他事务的回滚,但 NESTED 其实是一个事务,外层事务可以控制内层事务的回滚,内层就算没有异常,外层出现异常,也可以全部回滚