一、前言
事务管理对于系统应用来说至关重要,它保证了数据的完整性和安全性。特别是针对金融服务而言,更是不可或缺。经典的场景是转账操作,A账户向B账户转账5000元,首先A余额减少5000元,然后B余额增加5000元。通常情况下,都能正常完成交易。但也难免会遇到故障,这时候不能出现A的余额减少了,B的余额却没有增加的情况。
在分析源码之前,我们先来了解下Spring中的一些事务属性。
二、事务属性
1、事务隔离级别
事务隔离级别,定义了一个事务可能受其他并发事务活动影响的程度。
在应用程序中,多个事务同时运行,经常会为了完成相同的工作而操作同一数据。并发是必然的,但可能会导致以下问题。
脏读(Dirty read)。有T1、T2两个事务,T1改写了数据但尚未提交,T2却可以读取到改写后的数据。
不可重复读(Nonrepeatable read)。有T1、T2两个事务。T1多次执行相同的查询,但得到的结果却不相同。通常是因为T2在T1查询期间对数据做了更新。
幻读(Phantom reads)。有T1、T2两个事务。当T1正在读取记录时,T2并发插入了记录,幻读发生了。其实跟不可重复读类似。
理想状态下,所有的事务都应该隔离,从而防止以上情况出现。然而,完全将事务隔离,将大大降低性能。因为隔离要锁定数据行或者数据表,会阻碍并发,要求事务相互等待来完成工作。所以,就区分了几种隔离级别,来灵活应对不同场景下的数据要求。
隔离级别 | 含义 |
---|---|
DEFAULT | 这是Spring中的事务隔离级别默认值。它代表使用底层数据库的默认隔离级别。MySQL默认是“可重复读”,Oracle默认是“提交读”。 |
READ_UNCOMMITTED | 未提交读,允许读取到尚未提交的数据。会导致脏读、不可重复读和幻读。 |
READ_COMMITTED | 已提交读,允许读取到已经提交的事务数据。可以防止脏读,但仍会出现不可重复读和幻读。 |
REPEATABLE READ | 可重复读,对相同字段的多次读取的结果是一致的,除非数据被当前事务本身改变。可以避免脏读和不可重复读,但幻读仍会发生。 |
SERIALIZABLE | 串行化,所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰。事实上,基本不会使用到这个级别。 |
2、事务传播行为
所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。Spring中定义7种传播行为。
传播行为 | 含义 |
---|---|
PROPAGATION_MANDATORY | 表示该方法必须在一个事务中运行。如果当前没有事务,则抛出异常。 |
PROPAGATION_NEVER | 表示该方法不应当在一个事务中运行。如果一个事务正在运行,则抛出异常。 |
PROPAGATION_NOT_SUPPORTED | 表示该方法不应该在一个事务中运行。如果一个现有事务正在进行中,它将在该方法的运行期间被挂起。 |
PROPAGATION_SUPPORTS | 表示当前方法不需要事务性上下文,但是如果有一个事务已经在运行的话,它也可以在这个事务里运行。 |
PROPAGATION_REQUIRES_NEW | 表示当前方法必须在它自己的事务里运行。一个新的事务将被启动,而且如果有一个现有事务在运行的话,则将在这个方法运行期间被挂起。 |
PROPAGATION_REQUIRES | 表示当前方法必须在一个事务中运行。如果一个现有事务正在进行中,该方法将在那个事务中运行,否则就要开始一个新事务。 |
PROPAGATION_NESTED | 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED。 |
3、事务超时
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。
4、只读属性
事务的只读属性是指,对事务性资源进行只读操作或者是读写操作。如果一个事务只对后端数据库执行读操作,那么该数据库就可能利用那个事务的只读特性,采取某些优化 措施。通过把一个事务声明为只读,可以给后端数据库一个机会来应用那些它认为合适的优化措施。
5、回滚规则
通常情况下,如果在事务中抛出了未检查异常(继承自 RuntimeException 的异常),则默认将回滚事务。如果没有抛出任何异常,或者抛出了已检查异常,则仍然提交事务。这通常也是大多数开发者希望的处理方式,也是 EJB 中的默认处理方式。但是,我们可以根据需要人为控制事务在抛出某些未检查异常时任然提交事务,或者在抛出某些已检查异常时回滚事务。
三、Spring事务的三大接口
1、 PlatformTransactionManager
PlatformTransactionManager是事务管理的抽象层,Spring根据这个抽象层提供许多不同的具体实现。比如DataSourceTransactionManager、JpaTransactionManager、HibernateTransactionManager
等。
public interface PlatformTransactionManager {
//返回当前活动的事务或创建一个新的事务。
//参数definition描述了事务的属性,比如传播行为,隔离级别,超时等
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException
//根据给定事务的状态提交给定事务
void commit(TransactionStatus status) throws TransactionException;
//执行给定事务的回滚
void rollback(TransactionStatus status) throws TransactionException;
}
2、 TransactionDefinition
定义了事务属性
public interface TransactionDefinition {
//事务的7个传播行为
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;
//事务的5个隔离级别
int ISOLATION_DEFAULT = -1;
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();
//返回事务的名称
String getName();
}
3、 TransactionStatus
代表当前事务的状态,也可以对当前事务进行控制。
public interface TransactionStatus extends SavepointManager, Flushable {
//当前事务状态是否是新事务
boolean isNewTransaction();
//当前事务是否有保存点
boolean hasSavepoint();
//设置当前事务应该回滚,如果设置这个,则commit不起作用
void setRollbackOnly();
//当前事务是否应该回滚
boolean isRollbackOnly();
//用于刷新底层会话中的修改到数据库,一般用于刷新如Hibernate/JPA的会话,
//可能对如JDBC类型的事务无任何影响
void flush();
//当前事务否已经完成
boolean isCompleted();
}
四、事务的实现方式
Spring中的事务实现方式,总体可以分为两类:编程式事务和声明式事务。
1、编程式事务
顾名思义,编程式事务就是以代码编程的方式来控制事务的运行。我们来看一个事例。
PlatformTransactionManager采用Spring JDBC的事务管理器。数据源是一个数据库连接池,先看下XML配置。
在代码中直接通过事务管理器即可控制事务的运行。我们看一个Service中的方法。
public void insertUser(User user) {
TransactionStatus txStatus = transactionManager.getTransaction(transactionDefinition);
try {
System.out.println("----------新增用户信息------------");
transactionManager.commit(txStatus);
} catch (Exception e) {
System.out.println("保存用户信息发送异常,"+e);
transactionManager.rollback(txStatus);
}
}
通过以上方式就完成了以编程式事务对业务方法的管理。当然了,它的缺点也很明显,事务代码和业务代码糅杂在一起,破坏了业务代码条理性,而且也不利于维护和扩展。
有没有更好的实现方法呢?结合我们上一节学习的Spring AOP知识,应该怎么做呢?
2、声明式事务
没错,利用Spring AOP对方法进行拦截。在方法开始之前创建或者加入一个事务,在方法执行完毕之后根据情况提交或回滚事务。这个就是声明式事务。我们来看一个基于
首先,还是先配置一个事务管理器。
其次,通过AOP标签配置一个advisor,它包含一个advice和一个pointcut。
最后,通过tx标签定义一个advice。它本身是一个事务的通知,当前要包含事务的管理器和事务的属性。
通过这种方式,我们的业务代码不需要添加任何关于事务的代码,就可以完成事务的操作。在实际开发中,我们大部分也都是使用这种方式或者通过Annotation的方式来配置事务,而不大可能使用编程式事务。
五、源码解析
啰嗦了这么多,是为了先把事务的运行规则、属性讲清楚,不然上来就是源码,容易晕车哈。源码以tx标签为例的配置方式进行分析,编程式事务和Annotation注解方式的事务本章节暂不涉及。
1、tx标签的解析
tx标签的解析,在Spring扫描XML的时候就被加载到了,具体会定位到org.springframework.transaction.config.TxAdviceBeanDefinitionParser
类。然后调用类的parse()方法,但是我们发现此类并没有parse方法,往上找最后调用到父类的父类AbstractBeanDefinitionParser.parse()
。最后返回一个构建完毕的BeanDefinition对象,并注册到bean容器中,等待下一步的实例化。整个过程我们可以分为三个步骤来看。
- 1、创建TransactionInterceptor
TransactionInterceptor类就是事务处理的拦截器类,它实现了MethodInterceptor接口。在调用代理类的invoke方法时,实际调用的就是这个类的invoke。
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
//getBeanClass调用到子类的方法,这个子类就是TxAdviceBeanDefinitionParser
//它返回的就是return TransactionInterceptor.class;
Class> beanClass = getBeanClass(element);
if (beanClass != null) {
builder.getRawBeanDefinition().setBeanClass(beanClass);
}
builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
//调用子类TxAdviceBeanDefinitionParser方法
doParse(element, parserContext, builder);
return builder.getBeanDefinition();
}
- 2、解析事务属性
第一步创建了TransactionInterceptor的BeanDefinition对象,然后调用子类的doParse方法解析子节点进行事务属性的添加。
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
//将配置的事务管理器添加到属性
builder.addPropertyReference("transactionManager", TxNamespaceHandler.getTransactionManagerName(element));
//获取tx的子标签 。这里明确规定了只能有一个子标签
List txAttributes = DomUtils.getChildElementsByTagName(element, ATTRIBUTES_ELEMENT);
if (txAttributes.size() > 1) {
parserContext.getReaderContext().error(
"Element is allowed at most once inside element ", element);
}
else if (txAttributes.size() == 1) {
Element attributeSourceElement = txAttributes.get(0);
//解析事务方法的属性。也就是标签
RootBeanDefinition attributeSourceDefinition = parseAttributeSource(attributeSourceElement, parserContext);
//将NameMatchTransactionAttributeSource类的实例添加到属性中。
//NameMatchTransactionAttributeSource是一个包含了所有的事务属性的实例
builder.addPropertyValue("transactionAttributeSource", attributeSourceDefinition);
}
else {
builder.addPropertyValue("transactionAttributeSource",
new RootBeanDefinition("org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"));
}
}
private RootBeanDefinition parseAttributeSource(Element attrEle, ParserContext parserContext) {
//获取所有的子标签tx:method
//比如
List methods = DomUtils.getChildElementsByTagName(attrEle, METHOD_ELEMENT);
//事务属性的Map
ManagedMap transactionAttributeMap =
new ManagedMap(methods.size());
transactionAttributeMap.setSource(parserContext.extractSource(attrEle));
//将method标签里面的name为key,其他的事务属性为value加入Map
for (Element methodEle : methods) {
String name = methodEle.getAttribute(METHOD_NAME_ATTRIBUTE);
TypedStringValue nameHolder = new TypedStringValue(name);
nameHolder.setSource(parserContext.extractSource(methodEle));
RuleBasedTransactionAttribute attribute = new RuleBasedTransactionAttribute();
String propagation = methodEle.getAttribute(PROPAGATION_ATTRIBUTE);
String isolation = methodEle.getAttribute(ISOLATION_ATTRIBUTE);
String timeout = methodEle.getAttribute(TIMEOUT_ATTRIBUTE);
String readOnly = methodEle.getAttribute(READ_ONLY_ATTRIBUTE);
if (StringUtils.hasText(propagation)) {
attribute.setPropagationBehaviorName(RuleBasedTransactionAttribute.PREFIX_PROPAGATION + propagation);
}
if (StringUtils.hasText(isolation)) {
attribute.setIsolationLevelName(RuleBasedTransactionAttribute.PREFIX_ISOLATION + isolation);
}
if (StringUtils.hasText(timeout)) {
try {
attribute.setTimeout(Integer.parseInt(timeout));
}
catch (NumberFormatException ex) {
parserContext.getReaderContext().error("Timeout must be an integer value: [" + timeout + "]", methodEle);
}
}
if (StringUtils.hasText(readOnly)) {
attribute.setReadOnly(Boolean.valueOf(methodEle.getAttribute(READ_ONLY_ATTRIBUTE)));
}
List rollbackRules = new LinkedList();
if (methodEle.hasAttribute(ROLLBACK_FOR_ATTRIBUTE)) {
String rollbackForValue = methodEle.getAttribute(ROLLBACK_FOR_ATTRIBUTE);
addRollbackRuleAttributesTo(rollbackRules,rollbackForValue);
}
if (methodEle.hasAttribute(NO_ROLLBACK_FOR_ATTRIBUTE)) {
String noRollbackForValue = methodEle.getAttribute(NO_ROLLBACK_FOR_ATTRIBUTE);
addNoRollbackRuleAttributesTo(rollbackRules,noRollbackForValue);
}
attribute.setRollbackRules(rollbackRules);
transactionAttributeMap.put(nameHolder, attribute);
}
//创建NameMatchTransactionAttributeSource类,将上面的事务属性map放入自己的属性nameMap
RootBeanDefinition attributeSourceDefinition = new RootBeanDefinition(NameMatchTransactionAttributeSource.class);
attributeSourceDefinition.setSource(parserContext.extractSource(attrEle));
attributeSourceDefinition.getPropertyValues().add("nameMap", transactionAttributeMap);
return attributeSourceDefinition;
}
然后将TransactionInterceptor的对象返回,此时这个对象的属性集合中已经包含了事务管理器和所有的事务属性。
- 3、注册TransactionInterceptor
将这个类注册到bean容器。它的名字就是XML中配置的txAdvice。
this.beanDefinitionNames.add(beanName);
this.beanDefinitionMap.put(beanName, beanDefinition);
2、advisor的解析
advisor的解析我们在上一章节已经深入分析了,这里就不过多展开。总之,它最后返回一个DefaultBeanFactoryPointcutAdvisor实例的BeanDefinition对象,重点是它有一个属性adviceBeanName就是上面已经注册到容器的txAdvice。
剩下的就是AOP生成代理的流程,实际调用Service方法来到AopProxy的invoke方法。还是以JDK动态代理为例,在调用Service方法时候,调用到JdkDynamicAopProxy.invoke()
。或许大家还有印象,它先获取方法的拦截链,也就是通知方法的集合,然后链式调用它们的invoke。在这里,通知只有一个,那就是org.springframework.transaction.interceptor.TransactionInterceptor
。
3、事务控制
TransactionInterceptor.invoke()就是实际处理事务的地方。先来看一下这个方法的全貌。
public class TransactionInterceptor{
public Object invoke(final MethodInvocation invocation) throws Throwable {
//目标类的Class对象
//class com.viewscenes.netsupervisor.service.impl.UserServiceImpl
Class> targetClass = AopUtils.getTargetClass(invocation.getThis());
//事务处理
return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
//回调方法,就是目标类的方法调用
public Object proceedWithInvocation() throws Throwable {
return invocation.proceed();
}
});
}
}
可以看出,invokeWithinTransaction方法才是重点。同样的,我们也先来看一下它的内部实现。
protected Object invokeWithinTransaction(Method method, Class> targetClass,
final InvocationCallback invocation)throws Throwable {
//获取事务的属性(传播行为、隔离级别等)
final TransactionAttribute txAttr = getTransactionAttributeSource().
getTransactionAttribute(method, targetClass);
//获取事务管理器
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
//执行的方法
//com.viewscenes.netsupervisor.service.impl.UserServiceImpl.insertUser
final String joinpointIdentification = methodIdentification(method, targetClass);
//这个if就是声明式事务,else就是编程式事务,暂不涉及。
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification)
Object retVal = null;
try {
//回调方法,调用下一个拦截链。
//但实际上只有这一个通知,所以会调用目标对象实际方法。
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
//rollback 回滚事务
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
//commit 提交事务
commitTransactionAfterReturning(txInfo);
return retVal;
}
}
看到上面的源码,思路就比较清晰了。
- 获取事务管理器和事务属性
- 执行业务方法
- 根据try、catch决定回滚还是提交事务
所以这也解答了为什么业务方法里不能catch异常,否则事务不会回滚。如果一定要catch异常并且保持事务,那么在catch之后手动再throw一下异常也是可以的。如下所示:
public void insertUser(User user) {
try {
int i = 1/0;
System.out.println("----------新增用户信息------------");
} catch (Exception e) {
System.out.println("UserServiceImpl.insertUser()"+e.getMessage());
throw new RuntimeException();
}
}
不过在thorw的时候还需要注意,Spring在回滚的时候还有个判断。也就是说,只要这两种异常才会回滚。
public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error);
}
那么,除了这种手动throw的方式,有没有其他的呢?当然,还记不记得在TransactionStatus接口中有个方法setRollbackOnly。我们可以设置它,控制事务只可以回滚而不能提交。即便走到commit方法也没关系,它是有一个判断的。
if (defStatus.isLocalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Transactional code has requested rollback");
}
processRollback(defStatus);
return;
}
在业务代码中调用即可,就像这样。也可以保证事务会回滚。
public void insertUser(User user) {
try {
int i = 1/0;
System.out.println("----------新增用户信息------------");
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
看完了整个处理流程,我们的目光再回到createTransactionIfNecessary
方法。因为我们想了解事务到底是怎么被创建的。
- 从数据源中获取一个事务管理器
package org.springframework.jdbc.datasource;
public class DataSourceTransactionManager{
//创建一个数据源事务管理器,从数据源获取一个底层数据库连接
//conHolder此时还是为空
protected Object doGetTransaction() {
DataSourceTransactionObject txObject = new DataSourceTransactionObject();
txObject.setSavepointAllowed(isNestedTransactionAllowed());
ConnectionHolder conHolder =
(ConnectionHolder) TransactionSynchronizationManager.getResource(this.dataSource);
txObject.setConnectionHolder(conHolder, false);
return txObject;
}
}
- 判断事务属性
判断事务属性,是否超时、传播行为
//事务超时
if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
}
//说明此方法必须在一个事务中运行。但此时还未开启事务,所以要抛出异常。
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
- 开启事务
事务是与底层数据库连接绑定的。
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try {
//从数据源中获取一个连接,并放到事务管理器中
//newCon就是ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@62e9f76b]]
//这就说明一个事务对应一个数据库连接
Connection newCon = this.dataSource.getConnection();
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();
//设置事务提交方式为手动提交
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
con.setAutoCommit(false);
}
//设置事务的连接状态
txObject.getConnectionHolder().setTransactionActive(true);
//超时时间
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
// 将会话绑定到线程 threadlocal
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());
}
}
}
- 封装事务对象
将事务管理器,事务属性和执行方法封装成TransactionInfo对象,并设置事务的状态,绑定到当前线程,最后返回。
protected TransactionInfo prepareTransactionInfo(PlatformTransactionManager tm,
TransactionAttribute txAttr, String joinpointIdentification, TransactionStatus status) {
//将事务管理器、事务属性和方法封装成TransactionInfo对象
TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);
if (txAttr != null) {
//设置事务的状态
txInfo.newTransactionStatus(status);
}
//将事务绑定到当前线程,即ThreadLocal。
//因为在commit的时候,会判断当前线程是否有事务存在,否则不会提交
txInfo.bindToThread();
return txInfo;
}
六、总结
总的来说,Spring的事务管理分为编程式事务和声明式事务。
基于 TransactionDefinition、PlatformTransactionManager、TransactionStatus 编程式事务管理是 Spring 提供的最原始的方式,通常我们不会这么写,但是了解这种方式对理解 Spring 事务管理的本质有很大作用。
基于
和 命名空间的声明式事务管理是目前推荐的方式,其最大特点是与 Spring AOP 结合紧密,可以充分利用切点表达式的强大支持,使得管理事务更加灵活。
我们说基于tx标签的声明式事务是与AOP紧密结合的产物,通过对方法的拦截,它实际处理的时候调用的其实还是编程式事务里的那几个接口方法。