从源码看Spring事务失效

Spring事务

1 从阿里规约看Spring事务

目标:事务失效引发的灾难
如下图(张三—>李四转账)

在这里插入图片描述

下订单-------订单支付-----减库存(失败)
超卖现象

代码回忆:

public class UserServiceImpl implements UserService {
	@Autowired
	private UserMapper userMapper;
	@Resource
	private LogService logService;

	@Override
	@Transactional
//	@Transactional(rollbackFor = Exception.class)
	public void insert() throws Exception {
		method1_Test();
	}


	//模拟转账@Transactional
	private void method1_Test() throws Exception {
		System.out.println(">>>>>>>>>>>进入到业务方法");
		User user = new User();
		user.setName("张三");
		userMapper.insertUser(user);//张三扣减500元
		addPayment();//模拟李四增加500元(检查异常)

	}

	//正常情况下执行
	private void method2_Test() throws Exception {
		System.out.println(">>>>>>>>>>>进入到业务方法");
		User user = new User();
		user.setName("张三");
		userMapper.insertUser(user);

	}

	//有运行时异常的情况
	private void method3_Test() throws Exception {
		System.out.println(">>>>>>>>>>>进入到业务方法");
		User user = new User();
		user.setName("张三");
		userMapper.insertUser(user);
		throw new RuntimeException();
	}


	//事务失效(解决方案@Transactional(rollbackFor = Exception.class))
	private void method4_Test() throws Exception {
		System.out.println(">>>>>>>>>>>进入到业务方法");
		User user = new User();
		user.setName("张三");
		userMapper.insertUser(user);
		throw  new  Exception();
	}

	//日志数据丢失(解决方案:日志service修改@Transactional(propagation = Propagation.NOT_SUPPORTED))
	private void method5_Test() throws Exception {
		System.out.println(">>>>>>>>>>>进入到业务方法");
		User user = new User();
		user.setName("张三");
		userMapper.insertUser(user);
		logService.insert(getLogEntity());
		throw new RuntimeException();
	}




	private void addPayment() throws FileNotFoundException {
		FileInputStream in = new FileInputStream("a.txt");


	}

	private Log getLogEntity() {
		Log log = new Log();
		log.setName("业务异常日志记录");
		return log;
	}

如果说你从从事务方法中抛出的是检查异常(io、sql),那么这个时候,Spring将不能进行事务回滚。
是不是很恐怖呢??
所以说,阿里规定 1、让检查异常也回滚:你就需要在整个方法前加上
@Transactional(rollbackFor=Exception.class)
2、让非检查异常不回滚: 需要加入@Transactional(notRollbackFor=RunTimeException.class)
3、不需要事务管理(or 日志丢失) 需要加入
@Transactional(propagation=Propagation.NOT_SUPPORTED)

课程目标总结
1、解决事务失效:通过源码学习如何让检查异常也回滚(or 运行异常不回滚);从源码角度深入底层原理
2、解决无需事务控制;查询 or 日志记录;通过传播属性如何控制;底层是如何实现的
3、正常的事务执行流程在源码中是如何实现的

1.1 Spring事务总体介绍

在Spring中,事务有两种实现方式:

  1. 编程式事务管理: 编程式事务管理使用TransactionTemplate可实现更细粒度的事务控制。
  2. 申明式事务管理: 基于Spring AOP实现。
    其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

声明式事务好处
申明式事务管理不需要入侵代码,通过@Transactional就可以进行事务操作,更快捷而且简单,且大部分业务都可以满足,推荐使用。
不管是编程式事务还是申明式事务,最终调用的底层核心代码是一致的

1.1.1 编程式事务实现方式

编程式事务,Spring已经给我们提供好了模板类TransactionTemplate,可以很方便的使用,如下图
在这里插入图片描述
TransactionTemplate全路径名是:
org.springframework.transaction.support.TransactionTemplate。这是spring对事务的模板类
实现的接口
用来执行事务的回调方法,

public interface TransactionOperations {

	//执行事务的回调方法
	@Nullable
	<T> T execute(TransactionCallback<T> action) throws TransactionException;

}

InitializingBean这个是典型的spring bean初始化流程中 ,用来在bean属性加载完毕时执行的方法。

public interface InitializingBean {

	/**
	 * Invoked by a BeanFactory after it has set all bean properties supplied
	 * (and satisfied BeanFactoryAware and ApplicationContextAware).
	 * <p>This method allows the bean instance to perform initialization only
	 * possible when all bean properties have been set and to throw an
	 * exception in the event of misconfiguration.
	 * @throws Exception in the event of misconfiguration (such
	 * as failure to set an essential property) or if initialization fails.
	 */
	//bean属性加载完毕时执行的方法
	void afterPropertiesSet() throws Exception;

}

TransactionTemplate的2个接口的impl方法做了什么?
afterPropertiesSet如下

//	只是校验了事务管理器不为空
	@Override
	public void afterPropertiesSet() {
		if (this.transactionManager == null) {
			throw new IllegalArgumentException("Property 'transactionManager' is required");
		}
	}

execute方法如下

@Override
	@Nullable
	public <T> T execute(TransactionCallback<T> action) throws TransactionException {
		Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
// 内部封装好的事务管理器
		if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
			return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
		}// 需要手动获取事务,执行方法,提交事务的管理器
		else {
			//TODO  创建事务 (与声明事务调用同一个方法)
			TransactionStatus status = this.transactionManager.getTransaction(this);
			T result;
			try {
				// 2.执行业务逻辑,这里就是用户自定义的业务代码。如果是没有返回值的,就是doInTransactionWithoutResult()。
				result = action.doInTransaction(status);
			} catch (RuntimeException | Error ex) {
				// Transactional code threw application exception -> rollback
				// 应用运行时异常/错误异常 -> 回滚,调用AbstractPlatformTransactionManager的rollback(),事务提交回滚
				//TODO  回滚((与声明事务调用同一个方法)
				rollbackOnException(status, ex);
				throw ex;
			} catch (Throwable ex) {
				// 未知异常 -> 回滚,调用AbstractPlatformTransactionManager的rollback(),事务提交回滚
				// Transactional code threw unexpected exception -> rollback
				rollbackOnException(status, ex);
				throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
			}
			// TODO  事务提交 (与声明事务调用同一个方法)
			this.transactionManager.commit(status);
			return result;
		}
	}

总结
事务模板TransactionTemplateI里面的execute方法【创建事务】【提交事务】【回滚事务】和声明式事务调用的都是同一个底层方法

1.1.2 声明式事务实现方式

声明式事务的用法
@Transactional注解可以加在类或方法上
1、类:在类上时是对该类的所有public方法开启事务。
2、方法:加在方法上时也是只对public方法起作用。
注意
@Transactional注解也可以加在接口上,但只有在设置了基于接口的代理时才会生效,因为注解不能继承。所以该注解最好是加在类的实现上。

1.2 不容忽视的异常体系

目标:Java异常体系(面试常问)与Spring事务存在什么联系
在这里插入图片描述
Spring事务默认只回滚运行时异常和Error

伪代码如下

@Transaction 
public void insert(){ 
m_insert(); 
in_insert();//只有运行时异常 & Error 才可以回滚 
}

Thorwable类是所有异常和错误的超类,有两个子类Error和Exception,分别表示错误和异常。
异常类Exception又分为运行时异常(RuntimeException)和非运行时异常,这两种异常有很大的区别,
也称之为不检查异常(Unchecked Exception)和检查异常(Checked Exception)。
1、Error与Exception
Error是程序无法处理的错误,比如OutOfMemoryError、ThreadDeath等。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
Exception是程序本身可以处理的异常,这种异常分两大类运行时异常和非运行时异常。程序中应当尽可能去处理这些异常。
2、运行时异常和非运行时异常
运行时异常都是RuntimeException类及其子类异常,如NullPointerException、
IndexOutOfBoundsException等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。
这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
非运行时异常是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。

1.3 事务传播行为与隔离级别

1.3.1 事务传播行为

什么是事务传播
事务传播用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的时事务如何传播。
在这里插入图片描述
两大类【有事务的情况】【没事务的情况】

代码理解概念

	//日志数据丢失(解决方案:日志service修改@Transactional(propagation = Propagation.NOT_SUPPORTED))
	private void method5_Test() throws Exception {
		System.out.println(">>>>>>>>>>>进入到业务方法");
		User user = new User();
		user.setName("张三");
		userMapper.insertUser(user);
		logService.insert(getLogEntity());
		throw new RuntimeException();
	}


	@Override
//	@Transactional(propagation = Propagation.NOT_SUPPORTED)
	@Transactional
	public void insert(Log log) throws Exception {
		System.out.println(">>>>>>>>>>>进入到日志方法");
//		Log log = new Log();
//		log.setName("业务日志记录");
		logMapper.insertLog(log);
	}

}

1.3.2 事务隔离级别

什么是事务的隔离
5大类
隔离性是指多个用户的并发事务访问同一个数据库时,一个用户的事务不应该被其他用户的事务干扰,多个并发事务之间要相互隔
在这里插入图片描述
如果不考虑隔离性,会发生什么事呢?
脏读:
脏读是指一个事务在处理数据的过程中,读取到另一个未提交事务的数据
不可重复读:
不可重复读是指对于数据库中的某个数据,一个事务范围内的多次查询却返回了不同的结果,这是由于在查询过程中,数据被另外一个事务修改并提交了。
幻读
幻读是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读

1.读未提交(Read uncommitted)
这种事务隔离级别下,select语句不加锁。
此时,可能读取到不一致的数据,即“读脏 ”。这是并发最高,一致性最差的隔离级别。
2.读已提交(Read committed)
可避免 脏读 的发生。
在互联网大数据量,高并发量的场景下,几乎 不会使用 上述两种隔离级别。
3.可重复读(Repeatable read)
MySql默认隔离级别。
可避免 脏读 、不可重复读 的发生。
4.串行化(Serializable )
可避免 脏读、不可重复读、幻读 的发生

1.4 Spring事务源码深度剖析

1.4.1 Spring事务环境介绍

在这里插入图片描述

1.4.2 事务是何时被织入的

目标:事务在Spring哪个阶段织入的

程序在运行的时候【userService】为什么是代理对象
在这里插入图片描述
需要解决的问题:
1、代理对象是如何生成的
2、代理对象如何调用到了invoke方法

1.4.3 事务源码入口在哪里

1)事务三大接口介绍
目标:了解非常核心的事务三大接口

Spring事务三大接口介绍
1、PlatformTransactionManager: (平台)事务管理器接口
PlatformTransactionManager 接口是 Spring 提供的平台事务管理器顶级接口,用于管理事务。
在这里插入图片描述
Spring并不直接管理事务,而是提供了多种事务管理器;他们将事务管理的职责委托给Hibernate或者JTA等持久化机制的事务框架来实现
通过这个接口,Spring为各个平台,比如JDBC等都提供了对应的事务管理器;但是具体实现就是下游事务框架了

public interface PlatformTransactionManager {

//	用于获取事务 状态信息。
	TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
    //用于提交事务。
	void commit(TransactionStatus status) throws TransactionException;
    //用于回滚事务.
    void rollback(TransactionStatus status) throws TransactionException;

该接口中提供了三个事务操作方法,具体如下。
TransactionStatus getTransaction(TransactionDefinition definition):用于获取事务状态信息。
void commit(TransactionStatus status):用于提交事务。
void rollback(TransactionStatus status):用于回滚事务。

下面是 PlatformTransactionManager各种实现
在这里插入图片描述
2、TransactionDefinition:事务定义信息接口

比如:事务传播行为、隔离级别、超时、只读、回滚规则)


package org.springframework.transaction;

import java.sql.Connection;

import org.springframework.lang.Nullable;

//7大传播+4大隔离+超时、只读、回滚
public interface TransactionDefinition {

  
    int PROPAGATION_REQUIRED = 0;//默认:如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务
    int PROPAGATION_SUPPORTS = 1;//如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行
    int PROPAGATION_MANDATORY = 2;//如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常
    int PROPAGATION_REQUIRES_NEW = 3;//总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起
    int PROPAGATION_NOT_SUPPORTED = 4;//:总是非事务地执行,并挂起任何存在的事务
    int PROPAGATION_NEVER = 5;//总是非事务地执行,如果存在一个活动事务,则抛出异常
    int PROPAGATION_NESTED = 6;//如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, PROPAGATION_REQUIRED 属性执行

    int ISOLATION_DEFAULT = -1;//PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别   
    //事务最低的隔离级别,它充许别外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻读。
    int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;

    //保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。
    // 这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。
    int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;

    //这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。
    int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;

    //这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻读。
    int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;

    int TIMEOUT_DEFAULT = -1;//默认事务的超时时间

    int getPropagationBehavior();//获取事务的传播行为
    int getIsolationLevel();  //获取事务的隔离级别
    int getTimeout();//获取超时时间
    boolean isReadOnly();//是否只读

    @Nullable
    String getName();//事务名称

}


3、TransactionStatus:事务运行状态接口

public interface TransactionStatus extends SavepointManager, Flushable {

	boolean isNewTransaction();//获取是否是新事务


	boolean hasSavepoint();//获取是否存在保存点


	void setRollbackOnly();//获取是否存在保存点


	boolean isRollbackOnly();//是否回滚


	@Override
	void flush();//刷新事务


	boolean isCompleted();//事务是否完成

}

TransactionStatus 接口是事务的状态,它描述了某一时间点上事务的状态信息。其中包含六个操作
在这里插入图片描述
2)事务源码调用入口
目标:找到Spring事务的调用入口
在这里插入图片描述
入口拦截器org.springframework.transaction.interceptor.TransactionInterceptor#invoke

TransactionInterceptor源码如下

// 获取目标类
	@Override
	@Nullable
	public Object invoke(final MethodInvocation invocation) throws Throwable {
		// Work out the target class: may be {@code null}.
		// The TransactionAttributeSource should be passed the target class
		// as well as the method, which may be from an interface.
		Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

		// 开始调用父类方法
		return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
	}

Transactional 注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
	@AliasFor("transactionManager")
	String value() default "";
	@AliasFor("value")
	String transactionManager() default "";
	//	传播行为
// 一个开启了事务的方法A,调用了另一个开启了事务的方法B,此时会出现什么情况?这就要看传播行为的设置了
	Propagation propagation() default Propagation.REQUIRED;
	//isolation属性是用来设置事务的隔离级别,数据库有四种隔离级别:
	//读未提交、读已提交、可重复读、可串行化。MySQL的默认隔离级别是可重复读
	Isolation isolation() default Isolation.DEFAULT;
	//timtout是用来设置事务的超时时间,可以看到默认为-1,不会超时。
	int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
	//​ readOnly属性用来设置该属性是否是只读事务,只读事务要从两方面来理解:
// 它的功能是设置了只读事务后在整个事务的过程中,其他事务提交的内容对当前事务是不可见的
//	只读事务中只能有读操作,不能含有写操作,否则会报错
	boolean readOnly() default false;
	//当方法内抛出指定的异常时,进行事务回滚。默认情况下只对RuntimeException回滚。
	Class<? extends Throwable>[] rollbackFor() default {};
	String[] rollbackForClassName() default {};
//	 用来设置出现指定的异常时,不进行回滚。
	Class<? extends Throwable>[] noRollbackFor() default {};
	String[] noRollbackForClassName() default {};

}

1.4.4 Spring事务源码深入剖析

1)事务正常执行流程
目标:
1、正常流程测试(不抛运行时异常)
2、有运行时异常的情况
测试代码

  @Override
    @Transactional
//	@Transactional(rollbackFor = Exception.class)
    public void insert() throws Exception {
        method2_Test();//正常情况下执行
        method3_Test();//有运行时异常的情况
    }

找到Spring事务拦截器入口
org.springframework.transaction.interceptor.TransactionInterceptor#invoke

@Override
	@Nullable
	public Object invoke(final MethodInvocation invocation) throws Throwable {
		// Work out the target class: may be {@code null}.
		// The TransactionAttributeSource should be passed the target class
		// as well as the method, which may be from an interface.
		Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

		// 开始调用父类方法
		return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
	}

进入到invokeWithinTransaction
tips:具体流程
1、获取事务属性
2、创建事务
3、调用目标方法
4、回滚 事务 or 提交事务

//模板方法目标
//1、获取事务属性
//2、创建事务
//3、调用目标方法
//4、回滚 事务 or  提交事务
	@Nullable
	protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
											 final InvocationCallback invocation) throws Throwable {

		// 获取事务属性,如果事务属性为空,则没有事务
		TransactionAttributeSource tas = getTransactionAttributeSource();
		//继承TransactionDefinition,返回默认的传播、隔离级别
		final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
		// 获取DataSourceTransactionManager 用于管理 JDBC 的 Connection。
		final PlatformTransactionManager tm = determineTransactionManager(txAttr);
		// 切点标识-->com.tx.test.impl.UserServiceImpl.insert
		final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
		//txAttr(传播属性&隔离级别)
		if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
			// TODO: 创建(开启)事务(根据事务的传播行为属性去判断是否创建一个事务)
			TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
			Object retVal = null;
			try {
				// TODO: 调用目标方法  org.springframework.aop.framework.ReflectiveMethodInvocation.proceed
				retVal = invocation.proceedWithInvocation();
			} catch (Throwable ex) {
				// TODO: 回滚事务  目标方法调用发生了异常(后置增强),
				completeTransactionAfterThrowing(txInfo, ex);
				throw ex;
			} finally {
				// 执行成功  清理信息
				cleanupTransactionInfo(txInfo);
			}
			//TODO: 提交事务
			commitTransactionAfterReturning(txInfo);
			return retVal;
		} else {
			final ThrowableHolder throwableHolder = new ThrowableHolder();

			// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
			try {
				Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {
					TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
					try {
						return invocation.proceedWithInvocation();
					} catch (Throwable ex) {
						if (txAttr.rollbackOn(ex)) {
							// A RuntimeException: will lead to a rollback.
							if (ex instanceof RuntimeException) {
								throw (RuntimeException) ex;
							} else {
								throw new ThrowableHolderException(ex);
							}
						} else {
							// A normal return value: will lead to a commit.
							throwableHolder.throwable = ex;
							return null;
						}
					} finally {
						cleanupTransactionInfo(txInfo);
					}
				});

				// Check result state: It might indicate a Throwable to rethrow.
				if (throwableHolder.throwable != null) {
					throw throwableHolder.throwable;
				}
				return result;
			} catch (ThrowableHolderException ex) {
				throw ex.getCause();
			} catch (TransactionSystemException ex2) {
				if (throwableHolder.throwable != null) {
					logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
					ex2.initApplicationException(throwableHolder.throwable);
				}
				throw ex2;
			} catch (Throwable ex2) {
				if (throwableHolder.throwable != null) {
					logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
				}
				throw ex2;
			}
		}
	}

在这里插入图片描述
TransactionAttribute继承了TransactionDefinition
提交

@Override
	protected void doCommit(DefaultTransactionStatus status) {
		DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
		Connection con = txObject.getConnectionHolder().getConnection();
		if (status.isDebug()) {
			logger.debug("Committing JDBC transaction on Connection [" + con + "]");
		}
		try {
			//TODO :jdbc链接提交事务
			con.commit();
		}
		catch (SQLException ex) {
			throw new TransactionSystemException("Could not commit JDBC transaction", ex);
		}
	}

回滚


	@Override
	protected void doRollback(DefaultTransactionStatus status) {
		DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
		Connection con = txObject.getConnectionHolder().getConnection();
		if (status.isDebug()) {
			logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
		}
		try {
			//TODO  jdbc回滚事务
			con.rollback();
		}
		catch (SQLException ex) {
			throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
		}
	}

总结
1、获取事务属性
2、创建事务

1、获取事务管理器DataSourceTransactionManager(通过数据源、拿到链接、在设置事务管理器) 
2、判断是否存在事务,如果有就去判断传播属性!!!(第一次不存在) 
3、不存在事务的情况 
    如果事务超时时间小于默认(-1)或者没有事务,则抛出异常 
    新建一个事务(doBegin开启事务) 

    注意:第一次进入则是新建一个事务

3、调用目标方法
4、回滚 事务 or 提交事务

提交事务 
1、调用doCommit提交事务 
   通过事务管理器对象DataSourceTransactionObject拿到con执行con.commit()
 
回滚事务 
1、调用doRollback回滚 
   通过事务管理器对象DataSourceTransactionObject拿到con执行con.rollback()

2)生产事务失效之谜
目标:
1、事务失效的原因是什么
2、如何解决事务失效
3、在源码中什么地方判断的
测试代码

@Override 
@Transactional 
public void insert() throws Exception { 
method4_Test();//事务失效 
}

解决方案
@Transactional(rollbackFor = Exception.class)

重点关注源码
org.springframework.transaction.interceptor.TransactionAspectSupport#completeTransactionAfterThrowing中的rollbackOn方法

protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
		if (txInfo != null && txInfo.getTransactionStatus() != null) {
			if (logger.isTraceEnabled()) {
				logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
						"] after exception: " + ex);
			}
			//TODO rollbackOn获取回滚规则;可以自定义设置回滚规则,默认会判断RuntimeException和Error,!!!!!!!
			if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
				try {
					// TODO 回滚事务
					txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
				} catch (TransactionSystemException ex2) {
					logger.error("Application exception overridden by rollback exception", ex);
					ex2.initApplicationException(ex);
					throw ex2;
				} catch (RuntimeException | Error ex2) {
					logger.error("Application exception overridden by rollback exception", ex);
					throw ex2;
				}
			} else {    // 下面是不满足回滚条件的,会照样提交

				// We don't roll back on this exception.
				// Will still roll back if TransactionStatus.isRollbackOnly() is true.
				try {
					txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
				} catch (TransactionSystemException ex2) {
					logger.error("Application exception overridden by commit exception", ex);
					ex2.initApplicationException(ex);
					throw ex2;
				} catch (RuntimeException | Error ex2) {
					logger.error("Application exception overridden by commit exception", ex);
					throw ex2;
				}
			}
		}
	}

3)线上日志数据丢失
目标:通过传播属性解决业务中日志丢失问题

在这里插入图片描述
测试代码入口

public class Main {


	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("classpath*:application.xml");
		UserService userService = (UserService) context.getBean("userService");


//		LogService logService = (LogService) context.getBean("logService");

		try {
			userService.insert();//用户
//			logService.insert();//日志
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

日志插入插入失败
使用默认的传播属性和隔离级别

//实现类
public class LogServiceImpl implements LogService {
	@Autowired
	private LogMapper logMapper;
	@Override
//	@Transactional(propagation = Propagation.NOT_SUPPORTED)
	@Transactional
	public void insert(Log log) throws Exception {
		System.out.println(">>>>>>>>>>>进入到日志方法");
//		Log log = new Log();
//		log.setName("业务日志记录");
		logMapper.insertLog(log);
	}

}//实现类
public class LogServiceImpl implements LogService {
	@Autowired
	private LogMapper logMapper;
	@Override
//	@Transactional(propagation = Propagation.NOT_SUPPORTED)
	@Transactional
	public void insert(Log log) throws Exception {
		System.out.println(">>>>>>>>>>>进入到日志方法");
//		Log log = new Log();
//		log.setName("业务日志记录");
		logMapper.insertLog(log);
	}

}

@Transactional(propagation = Propagation.NOT_SUPPORTED)
这样修改则修改成功

用户信息插入,调用日志插入

  //日志数据丢失(解决方案:日志service修改@Transactional(propagation = Propagation.NOT_SUPPORTED))
    private void method5_Test() throws Exception {
        System.out.println(">>>>>>>>>>>进入到业务方法");
        User user = new User();
        user.setName("张三");
        userMapper.insertUser(user);
        logService.insert(getLogEntity());
        throw new RuntimeException();
    }

总结
1、现象

用户插入的时候;调用log的service插入;如果出现异常;两者全部回滚
也就是用户插入失败、日志插入失败
2、需求
正常的业务情况;都是在事务失败的时候;同时会要求日志也要插入成功
3、过程
目前;用户和日志的service使用的都是默认的传播属性和隔离级别
4、改进
将日志的传播属性修改成Propagation.NOT_SUPPORTED【如果当前存在事务;就挂起当前事务】

总结

1.获取事务属性
2.创建事务(第一次是没有事务会新建,默认属性)
3.调用目标方法
4.回滚或者提交事务

事务失效是因为回滚规则只会判断RuntimeExecption和Error的异常,才会进行回滚

办法

如果要不失效就需要自定义回滚规则
@Transactional(rollbackFor = Exception.class)

线上日志丢失的问题
是因为下订单和记录日志在一直事务中,使用默认的传播属性和隔离级别,也就是使用当前事务,如果下订单失败日志也会跟着回滚

办法

在记录日志的地方加入,会挂起事务,以非事务的方式进行执行
@Transactional(propagation = Propagation.NOT_SUPPORTED)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值