事务注解

一、事务注解一般是在service层进行的。

二、每一个业务方法开始时都会打开一个事务。 Spring默认情况下会对运行期例外(RunTimeException)进行事务回滚。这个例外是unchecked 

如果遇到checked意外就不回滚。 

如何改变默认规则: 

1 让checked例外也回滚:在整个方法前加上 @Transactional(rollbackFor=Exception.class) 

2 让unchecked例外不回滚: @Transactional(notRollbackFor=RunTimeException.class)

三、在 spring 中一共定义了六种事务传播属性

PROPAGATION_REQUIRED -- 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。

PROPAGATION_SUPPORTS -- 支持当前事务,如果当前没有事务,就以非事务方式执行。

PROPAGATION_MANDATORY -- 支持当前事务,如果当前没有事务,就抛出异常。

PROPAGATION_REQUIRES_NEW -- 新建事务,如果当前存在事务,把当前事务挂起。

PROPAGATION_NOT_SUPPORTED -- 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

PROPAGATION_NEVER -- 以非事务方式执行,如果当前存在事务,则抛出异常。

PROPAGATION_NESTED -- 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。


四、属性介绍

isolation:事务隔离性级别设置,Isolation枚举类型

 

    ISOLATION_DEFAULT :使用数据库默认的隔离级别

    ISOLATION_COMMITTED:允许其他事务已经提交的更新(防止脏读取)

    ISOLATION_READ_UNCOMMITTED:允许读取其他事务未提交的更新,会导致三个缺陷发生。执行速度最快

    ISOLATION_REPEATABLE_READ :除非事务自身更改了数据,否则事务多次读取的数据相同(防止脏数据,多次重复读取)

    ISOLATION_SERIALIZABLE:隔离级别最高,可以防止三个缺陷,但是速度最慢,影响性能。


readOnly:读写性事务,只读性事务,布尔型

 

    对数据库的操作中,查询是使用最频繁的操作,每次执行查询时都要从数据库中重新读取数据,有时多次读取的数据都是相同的,这样的数据操作不仅浪费了系统资源,还影响了系统速度。对访问量大的程序来说,节省这部分资源可以大大提    升系统速度。

   将事务声明为只读的,那么数据库可以根据事务的特性优化事务的读取操作


timeout:超时时间,单位秒

 

事务可能因为某种原因很长时间没有反应,这期间可能锁定了数据库表,影响性能。设置超时时间,如果超过该时间,事务自动回滚。

 

rollbackFor:一组异常类的实例,遇到时必须进行回滚

 

rollbackForClassname:一组异常类的名字,遇到时必须进行回滚

 

noRollbackFor:一组异常类的实例,遇到时必须不回滚

 

noRollbackForClassname:一组异常类的名字,遇到时必须不回滚

 实例一:事务的回滚情况

  

  1、默认情况对非运行时异常不进行回滚操作

   

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
  * 运行时异常默认事务回滚
  * @throws Exception
  */
@Transactional
public  void  updateUser_1()  throws  Exception {
     Map<String,String> userMap= new  HashMap<String,String>();
     userMap.put( "id" , "1" );
     userMap.put( "userName" , "测试_01" );
     userMap.put( "password" , "654321" );
     userDao.updateUserByID(userMap);
     throw  new  RuntimeException();  //抛出运行时异常 默认情况下回滚        事务---------------------->回滚
     
}

 

  2、非运行时异常默认事务不回滚

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
  * 非运行时异常默认事务不回滚
  * @throws Exception
  */
@Transactional
public  void  updateUser_3()  throws  Exception {
     Map<String,String> userMap= new  HashMap<String,String>();
     userMap.put( "id" , "1" );
     userMap.put( "userName" , "测试_01" );
     userMap.put( "password" , "654321" );
     userDao.updateUserByID(userMap);
     throw  new  Exception();  //抛出非运行时异常 默认情况下不回滚        事务---------------------->不回滚
     
}

 

   3、对所有异常事务都进行回滚操作

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
  * 对所有异常事务都进行回滚操作
  * @throws Exception
  */
@Transactional (rollbackFor=Exception. class )
public  void  updateUser_6()  throws  Exception {
     Map<String,String> userMap= new  HashMap<String,String>();
     userMap.put( "id" , "1" );
     userMap.put( "userName" , "测试_01" );
     userMap.put( "password" , "654321" );
     userDao.updateUserByID(userMap);
     throw  new  Exception();  //对所有异常事务都进行回滚操作     事务---------------------->回滚
     
}


   4、对所有异常事务都进行回滚操作

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
  * 对所有异常事务都不进行回滚操作
  * @throws Exception
  */
@Transactional (noRollbackFor=Exception. class )
public  void  updateUser_7()  throws  Exception {
     Map<String,String> userMap= new  HashMap<String,String>();
     userMap.put( "id" , "1" );
     userMap.put( "userName" , "测试_01" );
     userMap.put( "password" , "654321" );
     userDao.updateUserByID(userMap);
     throw  new  RuntimeException();  //对所有异常,事务都不进行回滚操作        事务---------------------->不回滚
     
}

 

   实例二:对try{} catch{}的异常不回滚

    

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
  * 对于try{} catch{}的异常不进行回滚
  * @throws Exception
  */
@Transactional
public  void  updateUser_5()  throws  Exception {
     Map<String,String> userMap= new  HashMap<String,String>();
     userMap.put( "id" , "1" );
     userMap.put( "userName" , "测试_01" );
     userMap.put( "password" , "654321" );
     userDao.updateUserByID(userMap);
     try {
     throw  new  RuntimeException();  //运行时异常被捕获,事务不回滚
     }
     catch (Exception e){
         System.err.println( "运行时异常被捕获,事务不回滚" );
     }      
}
六、关于 PROPAGATION_NESTED

假如有两个业务接口 ServiceA 和 ServiceB, 其中 ServiceA 中有一个方法实现如下

/**

* 事务属性配置为 PROPAGATION_REQUIRED

*/

void methodA() {

// 调用 ServiceB 的方法

ServiceB.methodB();

}

那么如果 ServiceB 的 methodB 如果配置了事务, 就必须配置为 PROPAGATION_NESTED

这种想法可能害了不少人, 认为 Service 之间应该避免互相调用, 其实根本不用担心这点,PROPAGATION_REQUIRED 已经说得很明白,如果当前线程中已经存在事务, 方法调用会加入此事务, 果当前没有事务,就新建一个事务, 所以 ServiceB#methodB() 的事务只要遵循最普通的规则配置为 PROPAGATION_REQUIRED 即可, 如果 ServiceB#methodB (我们称之为内部事务, 为下文打下基础) 抛了异常, 那么 ServiceA#methodA(我们称之为外部事务) 如果没有特殊配置此异常时事务提交 (即 +MyCheckedException的用法), 那么整个事务是一定要 rollback 的, 什么 Service 只能调 Dao 之类的言论纯属无稽之谈, spring 只负责配置了事务属性方法的拦截, 它怎么知道你这个方法是在 Service 还是 Dao 里 ?

说了这么半天, 那到底什么是真正的事务嵌套呢, 解释之前我们来看一下 Juergen Hoeller 的原话

Juergen Hoeller 写道

PROPAGATION_REQUIRES_NEW starts a new, independent "inner" transaction for the given scope. This transaction will be committed or rolled back completely independent from the outer transaction, having its own isolation scope, its own set of locks, etc. The outer transaction will get suspended at the beginning of the inner one, and resumed once the inner one has completed.

Such independent inner transactions are for example used for id generation through manual sequences, where the access to the sequence table should happen in its own transactions, to keep the lock there as short as possible. The goal there is to avoid tying the sequence locks to the (potentially much longer running) outer transaction, with the sequence lock not getting released before completion of the outer transaction.

PROPAGATION_NESTED on the other hand starts a "nested" transaction, which is a true subtransaction of the existing one. What will happen is that a savepoint will be taken at the start of the nested transaction. if the nested transaction fails, we will roll back to that savepoint. The nested transaction is part of the outer transaction, so it will only be committed at the end of the outer transaction.

Nested transactions essentially allow to try some execution subpaths as subtransactions: rolling back to the state at the beginning of the failed subpath, continuing with another subpath or with the main execution path there - all within one isolated transaction, and not losing any previous work done within the outer transaction.

For example, consider parsing a very large input file consisting of account transfer blocks: The entire file should essentially be parsed within one transaction, with one single commit at the end. But if a block fails, its transfers need to be rolled back, writing a failure marker somewhere. You could either start over the entire transaction every time a block fails, remembering which blocks to skip - or you mark each block as a nested transaction, only rolling back that specific set of operations, keeping the previous work of the outer transaction. The latter is of course much more efficient, in particular when a block at the end of the file fails.

Juergen Hoeller 写道

Rolling back the entire transaction is the choice of the demarcation code/config that started the outer transaction.

So if an inner transaction throws an exception and is supposed to be rolled back (according to the rollback rules), the transaction will get rolled back to the savepoint taken at the start of the inner transaction. The immediate calling code can then decide to catch the exception and proceed down some other path within the outer transaction.

If the code that called the inner transaction lets the exception propagate up the call chain, the exception will eventually reach the demarcation code of the outer transaction. At that point, the rollback rules of the outer transaction decide whether to trigger a rollback. That would be a rollback of the entire outer transaction then.

So essentially, it depends on your exception handling. If you catch the exception thrown by the inner transaction, you can proceed down some other path within the outer transaction. If you let the exception propagate up the call chain, it's eventually gonna cause a rollback of the entire outer transaction.

也就是说, 最容易弄混淆的其实是 PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED, 那么这两种方式又有何区别呢? 我简单的翻译一下 Juergen Hoeller 的话 :

PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 "内部" 事务. 这个事务将被完全 committed 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行.

另一方面, PROPAGATION_NESTED 开始一个 "嵌套的" 事务, 它是已经存在事务的一个真正的子事务. 嵌套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 嵌套事务是外部事务的一部分, 只有外部事务结束后它才会被提交.

由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 嵌套事务也会被 commit, 这个规则同样适用于 roll back.

那么外部事务如何利用嵌套事务的 savepoint 特性呢, 我们用代码来说话

代码

ServiceA {

    /**  

     * 事务属性配置为 PROPAGATION_REQUIRED  

     */  

    void methodA() {   

        ServiceB.methodB();   

    }   

}   

  

ServiceB {

    /**  

     * 事务属性配置为 PROPAGATION_REQUIRES_NEW  

     */    

    void methodB() {   

    }  

}      

这种情况下, 因为 ServiceB#methodB 的事务属性为 PROPAGATION_REQUIRES_NEW, 所以两者不会发生任何关系, ServiceA#methodA 和 ServiceB#methodB 不会因为对方的执行情况而影响事务的结果, 因为它们根本就是两个事务, 在 ServiceB#methodB 执行时 ServiceA#methodA 的事务已经挂起了 (关于事务挂起的内容已经超出了本文的讨论范围, 有时间我会再写一些挂起的文章) .

那么 PROPAGATION_NESTED 又是怎么回事呢? 继续看代码

代码

ServiceA {

    /**  

     * 事务属性配置为 PROPAGATION_REQUIRED  

     */  

    void methodA() {   

        ServiceB.methodB();   

    }  

}   

  

ServiceB {

    /**  

     * 事务属性配置为 PROPAGATION_NESTED  

     */    

    void methodB() {   

    }   

}      

现在的情况就变得比较复杂了, ServiceB#methodB 的事务属性被配置为 PROPAGATION_NESTED, 此时两者之间又将如何协作呢? 从 Juergen Hoeller 的原话中我们可以找到答案, ServiceB#methodB 如果 rollback, 那么内部事务(即 ServiceB#methodB) 将回滚到它执行前的 SavePoint(注意, 这是本文中第一次提到它, 嵌套事务中最核心的概念), 而外部事务(即 ServiceA#methodA) 可以有以下两种处理方式:

1. 改写 ServiceA 如下

代码

ServiceA {   

       

    /**  

     * 事务属性配置为 PROPAGATION_REQUIRED  

     */  

    void methodA() {   

        try {   

            ServiceB.methodB();   

        } catch (SomeException) {   

            // 执行其他业务, 如 ServiceC.methodC();   

        }   

    }   

  

}   

  

这种方式也是嵌套事务最有价值的地方, 它起到了分支执行的效果, 如果 ServiceB.methodB 失败, 那么执行 ServiceC.methodC(), 而 ServiceB.methodB 已经回滚到它执行之前的 SavePoint, 所以不会产生脏数据(相当于此方法从未执行过), 这种特性可以用在某些特殊的业务中, 而 PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 都没有办法做到这一点. (题外话 : 看到这种代码, 似乎似曾相识, 想起了 prototype.js 中的 Try 函数 )

2. 代码不做任何修改, 那么如果内部事务(即 ServiceB#methodB) rollback, 那么首先 ServiceB.methodB 回滚到它执行之前的 SavePoint(在任何情况下都会如此), 外部事务(即 ServiceA#methodA) 将根据具体的配置决定自己是 commit 还是 rollback (+MyCheckedException).

上面大致讲述了嵌套事务的使用场景, 下面我们来看如何在 spring 中使用 PROPAGATION_NESTED, 首先来看AbstractPlatformTransactionManager

代码

/**  

 * Create a TransactionStatus for an existing transaction.  

 */  

private TransactionStatus handleExistingTransaction(   

        TransactionDefinition definition, Object transaction, boolean debugEnabled)   

        throws TransactionException {   

  

   ... 省略   

  

    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {   

        if (!isNestedTransactionAllowed()) {   

            throw new NestedTransactionNotSupportedException(   

                    "Transaction manager does not allow nested transactions by default - " +   

                    "specify 'nestedTransactionAllowed' property with value 'true'");   

        }   

        if (debugEnabled) {   

            logger.debug("Creating nested transaction with name [" + definition.getName() + "]");   

        }   

        if (useSavepointForNestedTransaction()) {   

            // Create savepoint within existing Spring-managed transaction,   

            // through the SavepointManager API implemented by TransactionStatus.   

            // Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.   

            DefaultTransactionStatus status =   

                    newTransactionStatus(definition, transaction, false, false, debugEnabled, null);   

            status.createAndHoldSavepoint();   

            return status;   

        }   

        else {   

            // nested transaction through nested begin and commit/rollback calls.   

            // Usually only for JTA: Spring synchronization might get activated here   

            // in case of a pre-existing JTA transaction.   

            doBegin(transaction, definition);   

            boolean newSynchronization = (this.transactionSynchronization != SYNCHRONIZATION_NEVER);   

            return newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, null);   

        }   

    }   

}   

 

一目了然

1. 我们要设置 transactionManager 的 nestedTransactionAllowed 属性为 true, 注意, 此属性默认为 false!!!再看 AbstractTransactionStatus#createAndHoldSavepoint() 方法

代码

/**  

 * Create a savepoint and hold it for the transaction.  

 * @throws org.springframework.transaction.NestedTransactionNotSupportedException  

 * if the underlying transaction does not support savepoints  

 */  

public void createAndHoldSavepoint() throws TransactionException {   

    setSavepoint(getSavepointManager().createSavepoint());   

}   

可以看到 Savepoint 是 SavepointManager.createSavepoint 实现的, 再看 SavepointManager 的层次结构, 发现其 Template 实现是 JdbcTransactionObjectSupport, 常用的 DatasourceTransactionManager, HibernateTransactionManager 中的 TransactonObject 都是它的子类 :

 

1.      JdbcTransactionObjectSupport 告诉我们必须要满足两个条件才能 createSavepoint :

2.      java.sql.Savepoint 必须存在, 即 jdk 版本要 1.4+

3.      Connection.getMetaData().supportsSavepoints() 必须为 true, 即 jdbc drive 必须支持 JDBC 3.0

确保以上条件都满足后, 你就可以尝试使用 PROPAGATION_NESTED 了. (全文完)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring框架提供了多种方式来管理事务,其中最常用的是使用事务注解事务注解是通过在方法或类上添加注解来实现事务管理的。常用的事务注解有以下几种: 1. `@Transactional`:将该注解添加到方法或类上,表示该方法或类需要进行事务管理。可以设置一些属性来定义事务的隔离级别、传播行为、回滚规则等。 2. `@EnableTransactionManagement`:将该注解添加到配置类上,启用Spring的事务管理功能。 3. `@TransactionalEventListener`:将该注解添加到方法上,用于处理事务相关的事件。 使用事务注解的步骤如下: 1. 在Spring配置文件中添加 `<tx:annotation-driven />` 或者在配置类上添加 `@EnableTransactionManagement` 注解,启用Spring的事务管理功能。 2. 在需要进行事务管理的方法或类上添加 `@Transactional` 注解,指定事务的属性。 示例代码如下: ```java @Transactional public void doSomething() { // 事务处理逻辑 } ``` 需要注意的是,事务注解只对公有方法起作用,默认只对运行时异常进行回滚。如果想对其他类型的异常进行回滚,可以使用 `rollbackFor` 属性。 除了使用事务注解,还可以通过编程方式来管理事务,即使用编程式事务管理。这种方式需要手动在代码中开启、提交、回滚事务,相对来说比较繁琐。 希望以上信息对你有帮助!如果还有其他问题,请继续提问。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值