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

一、事物介绍

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

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

避坑注意事项: 

  1. @Transactional 注解只能加在 public 方法上,这是由 Spring AOP 的本质动态代理决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。 Spring默认使用的是jdk自带的基于接口的代理,而没有使用基于类的代理CGLIB。
  2. @Transactional注解底层使用的是动态代理来进行实现的,如果在调用本类中的方法,此时不添加@Transactional注解,而是在调用类中使用this调用本类中的另外一个添加了@Transactional注解,此时this调用的方法上的@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属性介绍

属性说明如下表:

属性 类型 描述
value String 可选的限定描述符,指定使用的事务管理器
propagation enum: Propagation 定义事务的生命周期,有REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER、NESTED,详细含义可查阅枚举类org.springframework.transaction.annotation.Propagation源码。
isolation enum: Isolation 可选的事务隔离级别设置,决定了事务的完整性
readOnly boolean 读写或只读事务,默认读写
timeout int (in seconds granularity) 事务超时时间设置
rollbackFor Class对象数组,必须继承自Throwable 导致事务回滚的异常类数组
rollbackForClassName 类名数组,必须继承自Throwable 导致事务回滚的异常类名字数组
noRollbackFor Class对象数组,必须继承自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)的方法时,失效 

 该案例转自: https://blog.csdn.net/m0_37779570/article/details/81352587

@Transactional注解底层使用的是动态代理来进行实现的,如果在调用本类中的方法,此时不添加@Transactional注解,而是在调用类中使用thisi调用本类中的另外一个添加了@Transactional注解,此时this调用的方法上的@Transactional注解是不起作用的。

我表示没有明白是为什么。。。。

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

https://blog.csdn.net/ClementAD/article/details/47339519 

https://blog.csdn.net/rainbow702/article/details/53907474 

https://blog.csdn.net/qq_30336433/article/details/83338835

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

发布了69 篇原创文章 · 获赞 43 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/fox_bert/article/details/99460057