Spring 声名式事务@Transactional注解详解

一、事物介绍

@Transactional加在类上:说明该事务作用于类中的所有方法

@Transactional加载方法上:说明该事务只作用域该方法,只能加在public方法上

避坑注意事项: 

  1. @Transactional 注解只能加在 public 方法上,这是由 Spring AOP 的本质动态代理决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。 Spring默认使用的是jdk自带的基于接口的代理,而没有使用基于类的代理CGLIB。
  2. @Transactional注解底层使用的是动态代理来进行实现的,如果在调用本类中的方法,此时不添加@Transactional注解,而是在调用类中使用this调用本类中的另外一个添加了@Transactional注解,此时this调用的方法上的@Transactional注解是不起作用的,因为 @Transactional其实是为你的类做了一个代理,既然是代理类那么只有调用代理类才能真正的执行到增强的方法,如果是在类的内部调用的化意味着没有调用增强的方法,所以声明式事务是不生效的。
  3. 【重点中的重点】 spring 在扫描 bean 的时候会扫描方法上是否包含 @Transactional 注解,如果包含,spring 会为这个bean动态地生成一个子类(即代理类,proxy),代理类是继承原来那个bean的。此时,当这个有注解的方法被调用的时候,实际上是由代理类来调用的,代理类在调用之前就会启动transaction。然而,如果这个有注解的方法是被同一个类中的其他方法调用的,那么该方法的调用并没有通过代理类,而是直接通过原来的那个bean,所以就不会启动transaction,我们看到的现象就是@Transactional注解无效。(总结来说就是:同类中,无事务的方法,调用添加事务的方法,这个方法的事务并不生效)
  4. 如果异常被try{}catch{}了,事务就不回滚了,如果想让事务回滚必须再往外抛try{}catch{throw Exception}。
  5. @Transactional 注解标识的方法,处理过程尽量的简单。尤其是带锁的事务方法,能不放在事务里面的最好不要放在事务里面。可以将常规的数据库查询操作放在事务前面进行,而事务内进行增、删、改、加锁查询等操作。
  6. @Transactional既可以作用于接口,接口方法上以及类已经类的方法上。但是Spring官方不建议接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。
  7. 你当然可以在接口上使用 @Transactional 注解,但是这将只能当你设置了基于接口的代理时它才生效。因为注解是不能继承的,这就意味着如果你正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装(将被确认为严重的)。因此,请接受Spring团队的建议并且在具体的类上使用 @Transactional 注解

二、Spring @Transactional的配置

1、在Spring配置文件中引入命名空间

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">

2、xml配置文件中,添加事务管理器bean配置

<!-- 事务管理器配置,单数据源事务 -->
    <bean id="pkgouTransactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="pkGouDataSource" />
    </bean>
<!-- 使用annotation定义事务 -->
    <tx:annotation-driven transaction-manager="pkgouTransactionManager" />

或者 

<tx:annotation-driven />
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>

3、在使用事务的方法或者类上添加 @Transactional(“pkgouTransactionManager”)注解 

 三、@Transactional属性介绍

属性说明如下表:

属性类型描述
valueString可选的限定描述符,指定使用的事务管理器
propagationenum: Propagation定义事务的生命周期,有REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER、NESTED,详细含义可查阅枚举类org.springframework.transaction.annotation.Propagation源码。
isolationenum: Isolation可选的事务隔离级别设置,决定了事务的完整性
readOnlyboolean读写或只读事务,默认读写
timeoutint (in seconds granularity)事务超时时间设置
rollbackForClass对象数组,必须继承自Throwable导致事务回滚的异常类数组
rollbackForClassName类名数组,必须继承自Throwable导致事务回滚的异常类名字数组
noRollbackForClass对象数组,必须继承自Throwable不会导致事务回滚的异常类数组
noRollbackForClassName类名数组,必须继承自Throwable不会导致事务回滚的异常类名字数组

1、propagation属性(事务传播行为)

spring事务有7种传播行为

使用 propagation 指定事务的传播行为, 即当前的事务方法被另外一个事务方法调用时如何使用事务, 常用属性值有: 
- SUPPORTS :对于只包含查询的方法一般使用这个。

@Transactional(propagation = Propagation.REQUIRED)

Propagation.REQUIRED(默认值)如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置
Propagation.SUPPORTS支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行
Propagation.MANDATORY支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常
Propagation.REQUIRES_NEW创建新事务,无论当前存不存在事务,都创建新事务
Propagation.NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起(不需要事务管理的(只查询的))
Propagation.NEVER以非事务方式执行,如果当前存在事务,则抛出异常
Propagation.NESTED如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作

2、isolation属性(事务隔离级别)

使用 isolation 指定事务的隔离级别, 属性值为:  

@Transactional(isolation = Isolation.READ_COMMITTED)

DEFAULT(默认值)使用底层数据库的默认隔离级别,对大多数据库来说默认隔离级别都是READ_COMMITTED
READ_UNCOMMITTED允许事务读取其它事务未提交的数据变更 (会出现脏读, 不可重复读) 基本不使用
READ_COMMITTED只允许事务读取已提交的数据 (可以避免脏读,但可能出现不可重复读和幻读)
REPEATABLE_READ可重复读,确保事务可以多次从一个字段中读取相同的值,在这个事务延续期间,禁止其他事务对这个字段进行更新 (可以避免脏读和不可重复读,但可能出现幻读)
SERIALIZABLE串行化,确保事务可以从一个表中读取相同的行,在这个事务延续期间,不允许其他事务对该表执行插入、修改、删除的操作。(所有并发问题都可避免,但效率十分低下)

最常用的取值为 READ_COMMITTED 
事务的隔离级别要得到底层数据库引擎的支持, 而不是应用程序或者框架的支持. 
Oracle 支持的 2 种事务隔离级别:READ_COMMITED , SERIALIZABLE 
Mysql 支持 4 中事务隔离级别. 

3、rollbackFor属性(事务回滚设置)

Spring框架的事务管理默认地只在发生不受控异常(RuntimeException 和 Error)时才进行事务回滚。

也就是说,当事务方法抛出受控异常( Exception 中除了 RuntimeException 及其子类以外的)时不会进行事务回滚。

受控异常(checked exceptions):就是非运行时异常,即Exception中除了RuntimeException及其子类以外的。

不受控异常(unchecked exceptions):RuntimeException和Error。

rollbackFor 属性在这里就可以发挥它的作用了

 @Transactional(rollbackFor = Exception.class

 这里你可以使用 java 已声明的异常;也可以使用自定义异常;也可同时设置多个异常类,中间用逗号间隔

 @Transactional(rollbackFor = {SQLException.class,UserAccountException.class}

3、noRollbackFor属性(事务不回滚设置)

默认情况下 Spring 的声明式事务对所有的运行时异常进行回滚. 可以通过noRollbackFor属性进行设置例外异常. 
如: 
@Transactional(noRollbackFor = {UserAccountException.class}
上面设置了遇到UserAccountException异常不回滚。 
一般不建议设置这个属性,通常情况下默认即可.
 

4、readOnly 属性

@Transactional(readOnly = false 

默认为 :false 

指定事务是否为只读. 表示这个事务只读取数据但不更新数据, 
这样可以帮助数据库引擎优化事务. 
如果只有查询数据操作, 应设置 readOnly=true 

5、timeout 属性

@Transactional(timeout = 5  

指定强制回滚之前事务可以占用的时间. 单位:秒 
如果执行时间草果这个时间就强制回滚。

案例

/**
 * @Transactional默认只有遇到 RuntimeException 和 error 的时候才回滚
 * 可以通过 rollbackFor 的设置来增加回滚的条件
 */
@Transactional(propagation = Propagation.REQUIRED,
        isolation = Isolation.DEFAULT,
        rollbackFor = {Exception.class},
        timeout = 5)
@Service
public class UsersServiceImpl implements UsersService{

    @Resource
    UsersBeanMapper usersBeanMapper;

    @Transactional(readOnly = true)
    @Override
    public UsersBean getUserByLogin(String username, String pwd) throws Exception{
        UsersBean UsersBean=usersBeanMapper.selectByUsersBean(new UsersBean(username, pwd));
        return UsersBean;
    }
	

    @Override
    public int deleteById(Integer id) throws Exception{
        try{
            int row=usersBeanMapper.deleteByPrimaryKey(id);
            if(row<1){
                throw new MyException("我没有找到对应的ID,无法进行删除");
            }
            return row;
        }catch(Exception e){
            throw new RuntimeException("我执行的时候出错了");
            //throw new SQLException("我执行sql的时候出错了");
        }
    }

	......
}


四、容易踩的坑

1、 在业务层捕捉异常后,发现事务不生效。

这是许多新手都会犯的一个错误,在业务层手工捕捉并处理了异常,你都把异常“吃”掉了,Spring自然不知道这里有错,更不会主动去回滚数据。例如:下面这段代码直接导致增加余额的事务回滚没有生效。

推荐做法:若非实际业务要求,则在业务层统一抛出异常,然后在控制层统一处理

2、同一个类中的某个方法调用另一个有注解(@Transactional)的方法时,失效 

 该案例转自: Spring之@Transactional注解原理以及走过的坑_m0_37779570的博客-CSDN博客_transactional注解原理

@Transactional注解底层使用的是动态代理来进行实现的,如果在调用本类中的首个方法A并没有添加@Transactional注解,继而该方法A内部,再调用本类另外一个添加了@Transactional注解的方法B,此时被调用的B方法上@Transactional注解是不起作用的。

下面我查到了一些解释:请参考以下链接

在同一个类中,一个方法调用另外一个有注解(比如@Async,@Transational)的方法,注解失效的原因和解决方法_Clement-Xu的博客-CSDN博客_同一个类中一个方法调用另一个方法

Spring 从同一个类中的某个方法调用另一个有注解(@Transactional)的方法时,失效的解决方案_rainbow702的博客-CSDN博客

spring事务@Transactional在同一个类中的方法调用不生效_改变ing的博客-CSDN博客_同一个类事务不生效

Transactional是Spring提供的事务管理注解。
  重点在于,Spring采用动态代理(AOP)实现对bean的管理和切片,它为我们的每个class生成一个代理对象。只有在代理对象之间进行调用时,可以触发切面逻辑。
  而在同一个class中,方法A调用方法B,调用的是原对象的方法,而不通过代理对象。所以Spring无法切到这次调用,也就无法通过注解保证事务性了。
   也就是说,在同一个类中的方法调用,则不会被方法拦截器拦截到,因此事务不会起作用

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值