Dawn's Blogs

分享技术 记录成长

0%

SSM学习之Spring (4) 事务

事务的 ACID 特性中,只有保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障。也就是说 A、I、D 是手段,C 是目的。

Spring 事务

事务管理方式

编程式事务管理

编程式事务管理,通过 TransactionTemplate 或者 TransactionManager 手动管理事务。

  • 使用 TransactionTemplate,调用 execute 方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Autowired
private TransactionTemplate transactionTemplate;
public void testTransaction() {

transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {

try {

// .... 业务代码
} catch (Exception e){
//回滚
transactionStatus.setRollbackOnly();
}

}
});
}
  • 使用 TransactionManager,调用 getTransaction 获取一个新的事务。
1
2
3
4
5
6
7
8
9
10
11
12
13
@Autowired
private PlatformTransactionManager transactionManager;

public void testTransaction() {

TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
// .... 业务代码
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
}
}

声明式事务管理

通过 @Transaction 注解,代码的侵入性最小,原理是 AOP。

事务管理接口

Spring 框架中,事务管理相关最重要的 3 个接口如下:

  • **PlatformTransactionManager**:(平台)事务管理器,Spring 事务策略的核心。
  • **TransactionDefinition**:事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)。
  • **TransactionStatus**:事务运行状态。

可以把 PlatformTransactionManager 接口可以被看作是事务上层的管理者,而 TransactionDefinitionTransactionStatus 这两个接口可以看作是事务的描述。

PlatformTransactionManager 会根据 TransactionDefinition 的定义比如事务超时时间、隔离级别、传播行为等来进行事务管理 ,而 TransactionStatus 接口则提供了一些方法来获取事务相应的状态比如是否新事务、是否可以回滚等等。

事务管理接口

Spring 并不直接管理事务,而是提供了多种事务管理器 。Spring 事务管理器的接口是 PlatformTransactionManager,通过这个接口 Spring 可以为各个平台如 JDBC(DataSourceTransactionManager)、Hibernate(HibernateTransactionManager)等提供了对应的事务管理器。

img

PlatformTransactionManager 接口中定义了三个方法:

1
2
3
4
5
6
7
8
9
10
11
12
package org.springframework.transaction;

import org.springframework.lang.Nullable;

public interface PlatformTransactionManager {
//获得事务
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
//提交事务
void commit(TransactionStatus var1) throws TransactionException;
//回滚事务
void rollback(TransactionStatus var1) throws TransactionException;
}

事务属性

事务属性 TransactionDefinition,定义了基本的事务属性,包括五个方面:

  • 隔离级别。
  • 传播行为。
  • 回滚规则。
  • 是否只读。
  • 事务超时。

TransactionDefinition 接口定义如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package org.springframework.transaction;

import org.springframework.lang.Nullable;

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;
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = 1;
int ISOLATION_READ_COMMITTED = 2;
int ISOLATION_REPEATABLE_READ = 4;
int ISOLATION_SERIALIZABLE = 8;
int TIMEOUT_DEFAULT = -1;
// 返回事务的传播行为,默认值为 REQUIRED。
int getPropagationBehavior();
//返回事务的隔离级别,默认值是 DEFAULT
int getIsolationLevel();
// 返回事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
int getTimeout();
// 返回是否为只读事务,默认值为 false
boolean isReadOnly();

@Nullable
String getName();
}

事务状态

TransactionStatus 用于描述事务状态,接口定义如下:

1
2
3
4
5
6
7
public interface TransactionStatus{
boolean isNewTransaction(); // 是否是新的事务
boolean hasSavepoint(); // 是否有恢复点
void setRollbackOnly(); // 设置为只回滚
boolean isRollbackOnly(); // 是否为只回滚
boolean isCompleted; // 是否已完成
}

事务属性详解

事务传播行为

事务传播行为是为了解决业务层方法之间互相调用的事务问题。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。在 TransactionDefinition 定义中包括了如下几个表示传播行为的常量,Spring 也相应地定义了一个枚举类 Propagation。

传播行为如下:

  • PROPAGATION_REQUIRED:默认的传播行为,如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  • PROPAGATION_REQUIRES_NEW:不管外部是否有事务,修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
  • PROPAGATION_NESTED:在外部方法开启事务时的情况下,在内部开启一个新的事务,作为嵌套事务存在。如果外部方法无事务,则单独开启一个事务。
  • PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

以下事务传播行为不常用,导致事务不会发生回滚:

  • PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

隔离级别

隔离级别与数据库中定义的隔离级别一致(读未提交,读已提交,可重复读,穿行化),这里不再赘述。

事务超时

所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒,默认值为 -1,这表示事务的超时时间取决于底层事务系统或者没有超时时间。

只读属性

MySQL 默认对每一个新建立的连接都启用了 autocommit 模式。在该模式下,每一个发送到 MySQL 服务器的 SQL 语句都会在一个单独的事务中进行处理,执行结束后会自动提交事务,并开启一个新的事务。

对于只有读取数据查询的事务,可以指定事务类型为 readonly,即只读事务。只读事务不涉及数据的修改,数据库会提供一些优化手段,适合用在有多条数据库查询操作的方法中。对于更新操作,只读事务中会抛出异常。

如果给方法加上了 Transactional 注解的话,这个方法执行的所有 SQL 会被放在一个事务中。如果声明了只读事务的话,数据库或者 ORM 框架就会去优化它的执行。

例如 Oracle 对于只读事务,不启动回滚段,不记录回滚log。

事务回滚规则

定义了哪些异常会导致事务回滚而哪些不会,默认情况下,事务只有遇到运行异常以及 Error 才会回滚;遇到受检异常时,不会回滚。可以使用 rollbackFor 属性定义需要回滚的异常类型。

@Transaction

@Transaction 的作用范围:

方法:推荐将注解使用于方法上,不过需要注意的是:该注解只能应用到 public 方法上,否则不生效。

:如果这个注解使用在类上的话,表明该注解对该类中所有的 public 方法都生效。

接口:不推荐在接口上使用。

定义

@Transaction 的定义如下,常用配置有:

  • propagation:传播行为,默认为 REQUIRED。
  • isolation:隔离级别,默认为 DEFAULT。
  • timeout:事务超时事件,默认为 -1(不会超时)。
  • readOnly:是否是只读事务,默认为 false。
  • rollbackFor:用于指定能够触发事务回滚的异常类型,并且可以指定多个异常类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {

@AliasFor("transactionManager")
String value() default "";

@AliasFor("value")
String transactionManager() default "";

Propagation propagation() default Propagation.REQUIRED;

Isolation isolation() default Isolation.DEFAULT;

int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;

boolean readOnly() default false;

Class<? extends Throwable>[] rollbackFor() default {};

String[] rollbackForClassName() default {};

Class<? extends Throwable>[] noRollbackFor() default {};

String[] noRollbackForClassName() default {};

}

原理

@Transaction 基于 AOP,AOP 又是基于动态代理实现的。如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理,如果目标对象没有实现了接口,会使用 CGLIB 动态代理。

如果一个类或者一个类中的 public 方法上被标注 @Transactional 注解的话,Spring 容器就会在启动的时候为其创建一个代理类,在调用被 @Transactional 注解的 public 方法的时候,实际调用的是,TransactionInterceptor 类中的 invoke() 方法。这个方法的作用就是在目标方法之前开启事务,方法执行过程中如果遇到异常的时候回滚事务,方法调用完成之后提交事务。

自调用问题

当一个方法被标记了 @Transactional 注解的时候,Spring 事务管理器只会在被其他类方法调用的时候生效,而不会在一个类中方法调用生效。

这是因为 Spring AOP 工作原理决定的。因为 Spring AOP 使用动态代理来实现事务的管理,它会在运行的时候为带有 @Transactional 注解的方法生成代理对象,并在方法调用的前后应用事物逻辑。如果该方法被其他类调用代理对象就会拦截方法调用并处理事务。但是在一个类中的其他方法内部调用的时候,代理对象就无法拦截到这个内部调用,因此事务也就失效了。

解决办法就是避免同一类中自调用或者使用 AspectJ 取代 Spring AOP 代理

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class MyService {

private void method1() {
((MyService)AopContext.currentProxy()).method2(); // 先获取该类的代理对象,然后通过代理对象调用method2。
//......
}
@Transactional
public void method2() {
//......
}
}