参考文献

Spring事务隔离级别

  • Spring提供事5种务隔离级别
    • @Transaction(isolation=Isolation.DEFAULT): 默认的事务隔离级别,即使用数据库的事务隔离级别.
    • @Transaction(isolation=Isolation.READ_UNCOMMITTED): 读未提交,这是最低的事务隔离级别,允许其他事务读取未提交的数据,这种级别的事务隔离会产生脏读,不可重复读和幻读.
    • @Transaction(isolation=Isolation.READ_COMMITTED): 读已提交,这种级别的事务隔离能读取其他事务已修改的数据,不能读取未提交的数据,会产生不可重复读和幻读.
    • @Transaction(isolation=Isolation.REPEATABLE_READ): 可重复读,这种级别的事务隔离可以防止不可重复读和脏读,但是会产生幻读.
    • @Transaction(isolation=Isolation.SERIALIZABLE): 串行化,这是最高级别的事务隔离,会避免脏读,不可重复读和幻读.在这种隔离级别下,事务会按顺序进行.

Spring事务传播行为

  • 事务传播行为是指如果多个事务同事存在,Spring就会处理这些事务的行为.
  • 事务传播行为分为7种(这7种传播行为有个前提,它们的事务管理器是同一个的时候,才会有下面描述的表现行为):
    • REQUIRED: 如果当前存在事务,就加入该事务;如果当前没有事务,就创建一个新的事务,这是Spring默认的事务传播行为.
    • SUPPORTS: 如果当前存在事务,就加入该事务;如果当前没有事务,就以非事务的方式继续运行.
    • MANDATORY: 如果当前事务存在事务,就加入该事务;如果当前事务,就抛出异常.
    • REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,就把当前事务挂起.新建事务和被挂起的事务没有任何关系,是两个独立的事务.外层事务回滚失败时,不能回滚内层事务执行结果,外层事务不能相互干扰.
    • NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,就把当前事务挂起.
      • 事务挂起: 对当前存在的事务现场生成一个快照,然后将事务现场清理干净,然后重新开启一个新事物,新事物执行完毕之后,将事务现场清理干净,然后根据前面的快照恢复旧事物.
    • NEVER: 以非事务方式运行,如果当前存在事务,就抛出异常.
    • NESTED: 如果当前存在事务,就创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,该取值就等价于REQUIRED
      • 若当前存在事务,NESTED创建的事务为当前事务的子事务,有单独的保存点,所以NESTED方法抛出异常被回滚,不会影响到外层事务

声明式事务属性

  • value: 存放String类型的值,主要用来指定不同的事务管理器,满足在同一个系统中存在不同的事务管理器.
  • transactionManager: 与value类似,也是用来选择事务管理器.
  • propagation: 事务传播行为,默认为Propagation.REQUIRED
  • isolation: 事务的隔离级别,默认为Isolation.DEFAULT
  • timeout: 事务的超时时间,默认为-1,如果超过了设置的时间还没有执行完成,就会自动回滚当前事务.
  • readOnly: 当前事务是不是自读事务,默认值是false.通常可以设置读取数据的事务的属性值为true.
  • rollbackFor: 可以设置触发事务的指定异常,允许指定多个类型的异常.
  • noRollbackFor: 与rollbackFor相反,可以设置不触发事务的指定异常,允许指定多个类型的异常.

事务回滚规则

  • Spring的事务回滚通常是根据当前事务抛出异常的时候,Spring事务管理器捕捉到未经处理的异常,然后根据规则来决定当前事务是否回滚.
  • 若捕获的异常正好是noRollbackFor属性的异常,那么将不会被捕获.在默认配置下,Spring只有捕获运行时异常(RuntimeException)的子类才会进行回滚.

Spring对事务的抽象

  • Spring事务抽象的关键就是事务策略的概念,事务策略是通过TransactionManager接口定义的.TransactionManager本身只是一个标记接口,它有两个直接子接口

    • ReactiveTransactionManager:这个接口主要用于在响应式编程模型下

    • PlatformTransactionManager:命令式编程模型下使用这个接口

      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
      31
      public interface PlatformTransactionManager extends TransactionManager {   
      /**
      * 开启事务
      * Return a currently active transaction or create a new one, according to
      * the specified propagation behavior.
      * <p>Note that parameters like isolation level or timeout will only be applied
      * to new transactions, and thus be ignored when participating in active ones.
      * <p>Furthermore, not all transaction definition settings will be supported
      * by every transaction manager: A proper transaction manager implementation
      * should throw an exception when unsupported settings are encountered.
      * <p>An exception to the above rule is the read-only flag, which should be
      * ignored if no explicit read-only mode is supported. Essentially, the
      * read-only flag is just a hint for potential optimization.
      * @param definition the TransactionDefinition instance (can be {@code null} for defaults),
      * describing propagation behavior, isolation level, timeout etc.
      * @return transaction status object representing the new or current transaction
      * @throws TransactionException in case of lookup, creation, or system errors
      * @throws IllegalTransactionStateException if the given transaction definition
      * cannot be executed (for example, if a currently active transaction is in
      * conflict with the specified propagation behavior)
      * @see TransactionDefinition#getPropagationBehavior
      * @see TransactionDefinition#getIsolationLevel
      * @see TransactionDefinition#getTimeout
      * @see TransactionDefinition#isReadOnly
      */
      TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
      // 提交事务
      void commit(TransactionStatus status) throws TransactionException;
      // 回滚事务
      void rollback(TransactionStatus status) throws TransactionException;
      }

TransactionDefinition

  • 它的主要完成了对事务定义的抽象,这些定义有些是数据库层面本身就有的,例如隔离级别、是否只读、超时时间、名称.

  • 使用申明式事务的时候,会通过@Transactional这个注解去申明某个方法需要进行事务管理,在@Transactional中可以定义事务的属性,这些属性实际上就会被封装到一个TransactionDefinition中,当然封装的时候肯定不是直接使用的接口,而是这个接口的一个实现类RuleBasedTransactionAttribute.

    img

TransactionExecution

  • 这个接口也是用于描述事务的状态,TransactionStatus是在其上做的扩展,内部定义了以下几个方法

    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
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    package org.springframework.transaction;

    /**
    * Common representation of the current state of a transaction.
    * Serves as base interface for {@link TransactionStatus} as well as
    * {@link ReactiveTransaction}.
    *
    * @author Juergen Hoeller
    * @since 5.2
    */
    public interface TransactionExecution {

    /**
    * Return whether the present transaction is new; otherwise participating
    * in an existing transaction, or potentially not running in an actual
    * transaction in the first place.
    * 判断当前事务是否是一个新的事务,不是一个新事务的话,那么需要加入到已经存在的事务中
    */
    boolean isNewTransaction();

    /**
    * Set the transaction rollback-only. This instructs the transaction manager
    * that the only possible outcome of the transaction may be a rollback, as
    * alternative to throwing an exception which would in turn trigger a rollback.
    *
    */
    void setRollbackOnly();

    /**
    * Return whether the transaction has been marked as rollback-only
    * (either by the application or by the transaction infrastructure).
    * 事务是否被标记成RollbackOnly,如果被标记成了RollbackOnly,意味着事务只能被回滚
    */
    boolean isRollbackOnly();

    /**
    * Return whether this transaction is completed, that is,
    * whether it has already been committed or rolled back.
    * 是否事务完成,回滚或提交都意味着事务完成了
    */
    boolean isCompleted();

    }

SavepointManager

  • 定义了管理保存点(Savepoint)的方法,隔离级别为NESTED时就是通过设置回滚点来实现的
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package org.springframework.transaction;

/**
* Interface that specifies an API to programmatically manage transaction
* savepoints in a generic fashion. Extended by TransactionStatus to
* expose savepoint management functionality for a specific transaction.
*
* <p>Note that savepoints can only work within an active transaction.
* Just use this programmatic savepoint handling for advanced needs;
* else, a subtransaction with PROPAGATION_NESTED is preferable.
*
* <p>This interface is inspired by JDBC 3.0's Savepoint mechanism
* but is independent from any specific persistence technology.
*
* @author Juergen Hoeller
* @since 1.1
* @see TransactionStatus
* @see TransactionDefinition#PROPAGATION_NESTED
* @see java.sql.Savepoint
*/
public interface SavepointManager {

/**
* Create a new savepoint. You can roll back to a specific savepoint
* via {@code rollbackToSavepoint}, and explicitly release a savepoint
* that you don't need anymore via {@code releaseSavepoint}.
* <p>Note that most transaction managers will automatically release
* savepoints at transaction completion.
* @return a savepoint object, to be passed into
* {@link #rollbackToSavepoint} or {@link #releaseSavepoint}
* @throws NestedTransactionNotSupportedException if the underlying
* transaction does not support savepoints
* @throws TransactionException if the savepoint could not be created,
* for example because the transaction is not in an appropriate state
* @see java.sql.Connection#setSavepoint
*/
Object createSavepoint() throws TransactionException;

/**
* Roll back to the given savepoint.
* <p>The savepoint will <i>not</i> be automatically released afterwards.
* You may explicitly call {@link #releaseSavepoint(Object)} or rely on
* automatic release on transaction completion.
* @param savepoint the savepoint to roll back to
* @throws NestedTransactionNotSupportedException if the underlying
* transaction does not support savepoints
* @throws TransactionException if the rollback failed
* @see java.sql.Connection#rollback(java.sql.Savepoint)
*/
void rollbackToSavepoint(Object savepoint) throws TransactionException;

/**
* Explicitly release the given savepoint.
* <p>Note that most transaction managers will automatically release
* savepoints on transaction completion.
* <p>Implementations should fail as silently as possible if proper
* resource cleanup will eventually happen at transaction completion.
* @param savepoint the savepoint to release
* @throws NestedTransactionNotSupportedException if the underlying
* transaction does not support savepoints
* @throws TransactionException if the release failed
* @see java.sql.Connection#releaseSavepoint
*/
void releaseSavepoint(Object savepoint) throws TransactionException;

}

TransactionStatus

  • 主要用于描述Spring事务的状态,继承了TransactionExecutionSavepointManager这些接口,额外提供了两个方法
img
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* Representation of the status of a transaction.
*
* <p>Transactional code can use this to retrieve status information,
* and to programmatically request a rollback (instead of throwing
* an exception that causes an implicit rollback).
*
* <p>Includes the {@link SavepointManager} interface to provide access
* to savepoint management facilities. Note that savepoint management
* is only available if supported by the underlying transaction manager.
*
* @author Juergen Hoeller
* @since 27.03.2003
* @see #setRollbackOnly()
* @see PlatformTransactionManager#getTransaction
* @see org.springframework.transaction.support.TransactionCallback#doInTransaction
* @see org.springframework.transaction.interceptor.TransactionInterceptor#currentTransactionStatus()
*/
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {

/**
* Return whether this transaction internally carries a savepoint,
* that is, has been created as nested transaction based on a savepoint.
* <p>This method is mainly here for diagnostic purposes, alongside
* {@link #isNewTransaction()}. For programmatic handling of custom
* savepoints, use the operations provided by {@link SavepointManager}.
* @see #isNewTransaction()
* @see #createSavepoint()
* @see #rollbackToSavepoint(Object)
* @see #releaseSavepoint(Object)
*/
boolean hasSavepoint();

/**
* Flush the underlying session to the datastore, if applicable:
* for example, all affected Hibernate/JPA sessions.
* <p>This is effectively just a hint and may be a no-op if the underlying
* transaction manager does not have a flush concept. A flush signal may
* get applied to the primary resource or to transaction synchronizations,
* depending on the underlying resource.
*/
@Override
void flush();

}

PlatformTransactionManager

img

  • AbstractPlatformTransactionManager,Spring提供的一个事务管理的基类,提供了事务管理的模板,实现了Spring事务管理的一个标准流程

    • 判断当前是否已经存在一个事务

    • 应用合适的事务传播行为

    • 在必要的时候挂起/恢复事务

    • 提交时检查事务是否被标记成为rollback-only

    • 在回滚时做适当的修改(是执行真实的回滚/还是将事务标记成rollback-only

  • AbstractPlatformTransactionManager提供了三个常见的子类

    完整类路径 说明
    org.springframework.jdbc.datasource.DataSourceTransactionManager 使用Spring JDBC或者MyBatis进行持久化数据时使用
    org.springframework.orm.hibernate5.HibernateTransactionManager 使用Hibernate进行持久化数据使用
    org.springframework.orm.jpa.JpaTransactionManager 使用JPA进行持久化数据使用

编程式事务

通过PlatfromTransactionManager控制事务

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
31
32
33
@Test
public void test1() throws Exception {
//定义一个数据源
org.apache.tomcat.jdbc.pool.DataSource dataSource = new
org.apache.tomcat.jdbc.pool.DataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8");
dataSource.setUsername("root");
dataSource.setPassword("root");
dataSource.setInitialSize(5);
//定义一个JdbcTemplate,用来方便执行数据库增删改查
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
//1.定义事务管理器,给其指定一个数据源(可以把事务管理器想象为一个人,这个人来负责事务的控制操作)
PlatformTransactionManager platformTransactionManager = new
DataSourceTransactionManager(dataSource);
//2.定义事务属性: TransactionDefinition,TransactionDefinition可以用来配置事务的属性信息,比如事务隔离级别、事务超时时间、事务传播方式、是否是只读事务等等。
TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
//3.开启事务: 调用platformTransactionManager.getTransaction开启事务操作,得到事务状态(TransactionStatus) 对象
TransactionStatus transactionStatus =
platformTransactionManager.getTransaction(transactionDefinition);
//4.执行业务操作,下面就执行2个插入操作
try {
System.out.println("before:" + jdbcTemplate.queryForList("SELECT * from t_user"));
jdbcTemplate.update("insert into t_user (name) values (?)", "test1-1");
jdbcTemplate.update("insert into t_user (name) values (?)", "test1-2");
//5.提交事务: platformTransactionManager.commit
platformTransactionManager.commit(transactionStatus);
} catch (Exception e) {
//6.回滚事务: platformTransactionManager.rollback
platformTransactionManager.rollback(transactionStatus);
}
System.out.println("after:" + jdbcTemplate.queryForList("SELECT * from t_user"));
}
定义事务管理器PlatformTransactionManager
  • 事务管理器相当于一个管理员,这个管理员就是用来控制事务的,比如开启事务,提交事务,回滚事务等等
1
2
3
4
5
6
7
8
9
10
11
12
13
public interface PlatformTransactionManager extends TransactionManager {

// 获取一个事务(开启事务)
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException;

// 提交事务
void commit(TransactionStatus status) throws TransactionException;

// 回滚事务
void rollback(TransactionStatus status) throws TransactionException;

}
  • JpaTransactionManager: 如果用Jpa来操作db,那么需要用这个管理器来帮控制事务。
  • DataSourceTransactionManager: 如果用是指定数据源的方式,比如操作数据库用的是: JdbcTemplate、mybatis、ibatis,那么需要用这个管理器来控制事务。
  • HibernateTransactionManager: 如果用Hibernate来操作db,那么需要用这个管理器帮控制事务。
  • JtaTransactionManager: 如果用的是Java中的Jta来操作db,这种通常是分布式事务,此时需要用这种管理器来控制事务。
定义事务属性DefaultTransactionDefinition
  • 定义事务属性,比如事务隔离级别、事务超时时间、事务传播方式、是否是只读事务等等。
  • Spring中使用TransactionDefinition接口来表示事务的定义信息,有个子类比较常用: DefaultTransactionDefinition
开启事务
  • 调用事务管理器的getTransaction方法,即可以开启一个事务

通过TransactionTemplate控制事务

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
31
32
33
34
35
36
37
38
39
40
41
42
@Test
public void test1() throws Exception {
//定义一个数据源
org.apache.tomcat.jdbc.pool.DataSource dataSource = new
org.apache.tomcat.jdbc.pool.DataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8");
dataSource.setUsername("root");
dataSource.setPassword("root");
dataSource.setInitialSize(5);
//定义一个JdbcTemplate,用来方便执行数据库增删改查
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
//1.定义事务管理器,给其指定一个数据源(可以把事务管理器想象为一个人,这个人来负责事务的控制操作)
PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(dataSource);
//2.定义事务属性:TransactionDefinition,TransactionDefinition可以用来配置事务的属性信息,比如事务隔离级别、事务超时时间、事务传播方式、是否是只读事务等等。
DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
transactionDefinition.setTimeout(10);//如:设置超时时间10s
//3.创建TransactionTemplate对象
TransactionTemplate transactionTemplate = new TransactionTemplate(platformTransactionManager, transactionDefinition);
/**
* 4.通过TransactionTemplate提供的方法执行业务操作
* 主要有2个方法:
* (1).executeWithoutResult(Consumer<TransactionStatus> action):没有返回值的,需传递一个Consumer对象,在accept方法中做业务操作
* (2).<T> T execute(TransactionCallback<T> action):有返回值的,需要传递一个TransactionCallback对象,在doInTransaction方法中做业务操作
* 调用execute方法或者executeWithoutResult方法执行完毕之后,事务管理器会自动提交事务或者回滚事务。
* 那么什么时候事务会回滚,有2种方式:
* (1)transactionStatus.setRollbackOnly();将事务状态标注为回滚状态
* (2)execute方法或者executeWithoutResult方法内部抛出异常
* 什么时候事务会提交?
* 方法没有异常 && 未调用过transactionStatus.setRollbackOnly();
*/
transactionTemplate.executeWithoutResult(new Consumer<TransactionStatus>() {
@Override
public void accept(TransactionStatus transactionStatus) {
jdbcTemplate.update("insert into t_user (name) values (?)",
"transactionTemplate-1");
jdbcTemplate.update("insert into t_user (name) values (?)",
"transactionTemplate-2");
}
});
System.out.println("after:" + jdbcTemplate.queryForList("SELECT * from t_user"));
}

声明式事务

  • 启用Spring的注解驱动事务管理功能,添加@EnableTransactionManagement

    • 当spring容器启动的时候,发现有@EnableTransactionManagement注解,此时会拦截所有bean的创建,扫描看一下bean上是否有@Transaction注解(类,或者父类,或者接口,或者方法中有这个注解都可以),如果有这个注解,Spring会通过Aop的方式给bean生成代理对象,代理对象中会增加一个拦截器,拦截器会拦截bean中public方法执行,会在方法执行之前启动事务,方法执行完毕之后提交或者回滚事务.

      1
      org.springframework.transaction.interceptor.TransactionInterceptor#invoke
  • 定义事务管理器

    • JpaTransactionManager: 如果用Jpa来操作db,那么需要用这个管理器来帮控制事务。
    • DataSourceTransactionManager: 如果用是指定数据源的方式,比如操作数据库用的是: JdbcTemplate、mybatis、ibatis,那么需要用这个管理器来控制事务。
    • HibernateTransactionManager: 如果用Hibernate来操作db,那么需要用这个管理器帮控制事务。
    • JtaTransactionManager: 如果用的是Java中的Jta来操作db,这种通常是分布式事务,此时需要用这种管理器来控制事务。
  • 需要使用事务的目标上加@Transaction注解

    • @Transaction放在接口上,那么接口的实现类中所有public都被Spring自动加上事务
    • @Transaction放在类上,那么当前类以及其下无限级子类中所有pubilc方法将被Spring自动加上事务
    • @Transaction放在public方法上,那么该方法将被Spring自动加上事务
    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
    31
    32
    33
    34
    35
    36
    37
    38
    39
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    public @interface Transactional {

    // 指定事务管理器的bean名称,如果容器中有多个事务管理器PlatformTransactionManager,需要告诉Spring当前配置使用哪个事务管理器
    @AliasFor("transactionManager")
    String value() default "";

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

    String[] label() default {};

    // 事务的传播属性
    Propagation propagation() default Propagation.REQUIRED;

    // 事务隔离级别
    Isolation isolation() default Isolation.DEFAULT;

    // 事务执行的超时时间(秒)
    int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;

    String timeoutString() default "";

    // 是否是只读事务
    boolean readOnly() default false;

    // 定义0个或者更多异常类,这个异常类必须是Throwable的子类,当方法抛出这些异常及其子类异常的时候,Spring会让事务回滚
    Class<? extends Throwable>[] rollbackFor() default {};

    String[] rollbackForClassName() default {};

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

    String[] noRollbackForClassName() default {};

    }

Spring@Transactional注解控制事务哪些场景下不生效?

数据库引擎是否支持事务(Mysql 的 MyIsam引擎不支持事务)

注解所在的类是否被加载为 Bean(是否被spring 管理)

所用数据源是否加载了事务管理器

  • 如果没有启用事务管理,则@Transactional注解将无效.在Spring中,需要通过@EnableTransactionManagement注解启用事务管理.

注解所在的方法是否为 public 修饰的

Method visibility and @TransactionalWhen you use transactional proxies with Spring’s standard configuration, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private, or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. If you need to annotate non-public methods, consider the tip in the following paragraph for class-based proxies or consider using AspectJ compile-time or load-time weaving (described later).When using @EnableTransactionManagement in a @Configuration class, protected or package-visible methods can also be made transactional for class-based proxies by registering a custom transactionAttributeSource bean like in the following example. Note, however, that transactional methods in interface-based proxies must always be public and defined in the proxied interface.

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Register a custom AnnotationTransactionAttributeSource with the
* publicMethodsOnly flag set to false to enable support for
* protected and package-private @Transactional methods in
* class-based proxies.
*
* @see ProxyTransactionManagementConfiguration#transactionAttributeSource()
*/
@Bean
TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource(false);
}

The Spring TestContext Framework supports non-private @Transactional test methods by default. See Transaction Management in the testing chapter for examples.

  • @Transactional注解添加在private、static、final方法上,这些方法不会被 Spring 代理,因此不会进行事务管理.
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
* Same signature as {@link #getTransactionAttribute}, but doesn't cache the result.
* {@link #getTransactionAttribute} is effectively a caching decorator for this method.
* <p>As of 4.1.8, this method can be overridden.
* @since 4.1.8
* @see #getTransactionAttribute
*/
@Nullable
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
// 大致流程: 类中的方法-->方法所在类-->接口中的方法-->接口的类
// 如果方法中存在事务属性,则使用方法上的属性,否则使用方法所在类上的属性;如果方法所在类上还是没有寻找到对应的事务属性,那么再寻找接口中的方法,再没有的话,最后尝试寻找接口的类上面的声明.
// Don't allow non-public methods, as configured.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}

// method代表接口的方法, specificMethod代表实现类中的方法
// The method may be on an interface, but we need attributes from the target class.
// If the target class is null, the method will be unchanged.
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);

// 查看方法中是否存在事务声明
// First try is the method in the target class.
TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
if (txAttr != null) {
return txAttr;
}

// 查看方法所在类中是否存在事务声明
// Second try is the transaction attribute on the target class.
txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}

// 如果存在接口,则到接口中去寻找
if (specificMethod != method) {
// 查找接口方法
// Fallback is to look at the original method.
txAttr = findTransactionAttribute(method);
if (txAttr != null) {
return txAttr;
}
// 到接口中的类中去寻找
// Last fallback is the class of the original method.
txAttr = findTransactionAttribute(method.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}
}

return null;
}

是否存在自身调用的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//示例1

@Service
public class OrderServiceImpl implements OrderService {

public void update(Order order) {
updateOrder(order);
}

@Transactional
public void updateOrder(Order order) {
// update order
}

}
  • 事务都会失效,因为发生了自身调用,就调该类自己的方法,而没有经过 Spring 的代理类,默认只有在外部调用事务才会生效,这也是老生常谈的经典问题了.

@Transactional的扩展配置propagation是否正确

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Service
public class OrderServiceImpl implements OrderService {

@Transactional
public void update(Order order) {
updateOrder(order);
}

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void updateOrder(Order order) {
// update order
}

}
  • 如果不同事务的 propagation 属性不一致,可能会导致注解失效的问题,具体表现为子事务的操作不会受到父事务的控制,或者出现死锁或数据不一致等问题.
  • 例如,如果父事务的 propagation 属性为 REQUIRED,而子事务的 propagation 属性为 REQUIRES_NEW,那么子事务会开启一个新的事务,不受父事务的控制,可能会导致数据不一致的问题.
  • 为了避免注解失效的问题,可以根据具体的业务需求和场景选择合适的 propagation 属性,确保嵌套事务的正确性和一致性.例如,如果需要子事务受父事务的控制,可以将子事务的 propagation 属性设置为 REQUIRED;如果需要子事务独立于父事务,可以将子事务的 propagation 属性设置为 REQUIRES_NEW.
  • 需要注意的是,使用 propagation 属性时,需要根据具体的业务需求和场景进行相应的配置,避免出现注解失效的问题.同时,在进行嵌套事务时,也需要注意事务的传播行为和隔离级别等问题,避免出现死锁或数据不一致等问题.

异常被catch捕获导致@Transactional失效

  • 这个也是出现比较多的场景:把异常吃了,然后又不抛出来,事务也不会回滚!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Service
    public class OrderServiceImpl implements OrderService {

    @Transactional
    public void updateOrder(Order order) {
    try {
    // update order
    } catch {

    }
    }

    }

异常类型错误

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class OrderServiceImpl implements OrderService {

@Transactional
public void updateOrder(Order order) {
try {
// update order
} catch {
throw new Exception("更新错误");
}
}

}
  • 这样事务也是不生效的,因为默认回滚的是:RuntimeException,如果想触发其他异常的回滚,需要在注解上配置一下,如:@Transactional(rollbackFor = Exception.class)这个配置仅限于 Throwable异常类及其子类.
1
2
3
4
5
// org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn
@Override
public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error);
}

错误的调用方式

  • 在一个类中,一个没有被@Transactional注解标注的方法调用了被 @Transactional注解标注的方法,被调用的方法的事务会失效.
  • 跨方法调用,如果在一个事务中调用了另一个被 @Transactional 注解标注的方法,并且被调用方法使用了不同的事务传播行为,那么事务可能会失效.

使用@Transactional注解注意点

  • @Transactional 注解,是使用 AOP 实现的,本质就是在目标方法执行前后进行拦截.在目标方法执行前加入或创建一个事务,在执行方法执行后,根据实际情况选择提交或是回滚事务.
  • 当 Spring 遇到该注解时,会自动从数据库连接池中获取 connection,并开启事务然后绑定到 ThreadLocal 上,对于@Transactional注解包裹的整个方法都是使用同一个connection连接.如果我们出现了耗时的操作,比如第三方接口调用,业务逻辑复杂,大批量数据处理等就会导致我们我们占用这个connection的时间会很长,数据库连接一直被占用不释放.一旦类似操作过多,就会导致数据库连接池耗尽.

并发场景下使用@Transactional

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
31
32
@Transactional(transactionManager = "mybatisTransactionManager")
fun test(request: RequestMessage): ResponseMessage {
val policyNo = policyInformation.policy.policyNo
if (reentrantLock.tryLock()) {
try {
val informationOptional = apper.queryInfoByPolicyNo(policyNo)
if (Objects.nonNull(informationOptional)) {
responseBody.msg = ORDER_ALREADY_EXISTS
responseBody.code = SUCCESS_CODE
return ResponseMessage().apply {
this.sign = request.sign
this.head = request.head
this.body = responseBody
}
}
createOrderAndSavePolicyInfo(policyInformation, responseBody)
LOG.debug("处理成功")
} catch (e: Exception) {
LOG.error(e.message)
} finally {
reentrantLock.unlock()
}
} else {
responseBody.msg = CREATE_ORDER_FAILURE
responseBody.code = Response.ERROR_CODE.toString()
}
return ResponseMessage().apply {
this.sign = request.sign
this.head = request.head
this.body = responseBody
}
}
  • 上述代码会出现并发问题,具体现象为:

    线程A 线程B
    获取锁
    开启事务
    判断数据是否存在
    数据不存在,插入数据
    解锁 获取锁
    提交事务 判断数据是否存在(此时线程A尚未提交事务,插入的数据尚未进入数据库)
    数据不存在,插入数据(唯一索引校验,重复插入报错)

    img

  • 主要原因为: 事务范围大于锁的范围,即事务范围不在锁范围内

  • 解决方法:

    • 将锁的范围设置大于事务范围

    • 具体方法有:

      • 采用编程式事务,使用TransactionTemplateTransactionManager来控制事务,再在外层使用锁进行锁定.

        1
        2
        3
        4
        5
        6
        transactionTemplate.execute(new TransactionCallback<Object>() {
        @Override
        public Object doInTransaction(TransactionStatus status) {
        return null;
        }
        });
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        @Slf4j
        @Component
        public class TransactionUtil {
        @Autowired
        private PlatformTransactionManager transactionManager;

        public <T> boolean transactional(Consumer<T> consumer) {
        // 定义事务
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setName("SomeTxName");
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        TransactionStatus status = transactionManager.getTransaction(def);
        try {
        consumer.accept(null);

        transactionManager.commit(status);
        return true;
        } catch (Exception e) {
        transactionManager.rollback(status);
        log.error("编程式事务业务异常回滚", e);
        return false;
        }
        }
        }
        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
        fun preventRepeat(
        policyInformation: PolicyInformation,
        responseBody: GatewayResponseMessage.GatewayResponseBody
        ) {
        val policyNo = policyInformation.policy.policyNo
        val lock = redissonClient.getLock(policyNo)
        if (lock.tryLock()) {
        try {
        transactionTemplate.execute(object : TransactionCallbackWithoutResult() {
        override fun doInTransactionWithoutResult(status: TransactionStatus) {
        val informationOptional = policyWithOrderInformationMapper.queryInfoByPolicyNo(policyNo)
        if (informationOptional.isPresent) {
        responseBody.msg = ORDER_ALREADY_EXISTS
        responseBody.code = SUCCESS_CODE
        } else {
        createOrderAndSavePolicyInfo(policyInformation, responseBody)
        }
        }
        })
        } catch (e: Exception) {
        LOG.error(e.message)
        } finally {
        lock.unlock()
        }
        } else {
        responseBody.msg = CREATE_ORDER_FAILURE
        responseBody.code = Response.ERROR_CODE.toString()
        }
        }
      • 使用事务的传播机制

        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
        31
        32
        33
        34
        35
        36
        @Transactional(transactionManager = "mybatisTransactionManager")
        fun test(request: RequestMessage): ResponseMessage {
        val policyNo = policyInformation.policy.policyNo
        if (reentrantLock.tryLock()) {
        try {
        lock(policyNo, responseBody, policyInformation)
        } catch (e: Exception) {
        LOG.error(e.message)
        } finally {
        reentrantLock.unlock()
        }
        } else {
        responseBody.msg = CREATE_ORDER_FAILURE
        responseBody.code = Response.ERROR_CODE.toString()
        }
        return ResponseMessage().apply {
        this.sign = request.sign
        this.head = request.head
        this.body = responseBody
        }
        }

        @Transactional(transactionManager = "mybatisTransactionManager",propagation = Propagation.REQUIRES_NEW)
        private fun test2(
        policyNo: String,
        responseBody: GatewayResponseMessage.GatewayResponseBody,
        policyInformation: PolicyInformation
        ) {
        val informationOptional = policyWithOrderInformationMapper.queryInfoByPolicyNo(policyNo)
        if (informationOptional.isPresent) {
        responseBody.msg = ORDER_ALREADY_EXISTS
        responseBody.code = SUCCESS_CODE
        } else {
        createOrderAndSavePolicyInfo(policyInformation, responseBody)
        }
        }

避免长事务

  • 长事务: 运行时间比较长,长时间未提交的事务

长事务会引发哪些问题?

  • 数据库连接池被占满,应用无法获取连接资源;
  • 容易引发数据库死锁;
  • 数据库回滚时间长;
  • 在主从架构中会导致主从延时变大.

如何避免长事务

  • 对事务方法进行拆分,尽量爱让事务变小,变快,减小事务的颗粒度.

  • Spring进行事务管理的方式:

    • 声明式事务: 在方法上使用@Transactional注解进行事务管理的操作叫声明式事务.

      • 优点: 使用简单
      • 缺点: 事务的颗粒度是整个方法,无法进行精细化控制.
    • 编程式事务:基于底层的API,开发者在代码中手动的管理事务的开启、提交、回滚等操作

      • 在Spring项目中可以使用TransactionTemplate类的对象,手动控制事务.

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        @Autowired 
        private TransactionTemplate transactionTemplate;

        public void xxx(TempBean tempBean) {
        transactionTemplate.execute(transactionStatus -> {
        tempDao.save(tempBean);
        //保存明细表
        tempDetailDao.save(tempBean.getDetail());
        return Boolean.TRUE;
        });
        }
      • 优点: 可以精细化控制事务的范围.

  • 避免长事务的最简单的方法就是不要使用声明式事务@Transactional,而是使用编程式事务手动控制事务范围.