Spring事务

100 阅读9分钟

Spring支持的事务管理

编程式事务管理

声明式事务管理

  • 基于注解方式实现
  • 基于xml配置文件实现

基于注解方式实现

  1. 在spring配置文件配置事务管理器
<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
      destroy-method="close">
    <property name="url" value="jdbc:mysql:///user_db" />
    <property name="username" value="root" />
    <property name="password" value="root" />
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>

<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--注入数据源-->
    <property name="dataSource" ref="dataSource"></property>
</bean>


<!-- JdbcTemplate对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <!--注入dataSource-->
    <property name="dataSource" ref="dataSource"></property>
</bean>

  1. 在spring配置文件开启事务注解
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
                        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
                        
                        
                        ```
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
```
  1. 在service类上面(或者service类里面方法上面)添加事务注解 (1)@Transactional,这个注解添加到类上面,也可以添加方法上面 (2)如果把这个注解添加类上面,这个类里面所有的方法都添加事务 (3)如果把这个注解添加方法上面,为这个方法添加事务
@Service
@Transactional
public class UserService {

基于xml配置文件实现

第一步 配置事务管理器

<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
      destroy-method="close">
    <property name="url" value="jdbc:mysql:///user_db" />
    <property name="username" value="root" />
    <property name="password" value="root" />
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>

<!-- JdbcTemplate对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <!--注入dataSource-->
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!--1 创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--注入数据源-->
    <property name="dataSource" ref="dataSource"></property>
</bean>

第二步 配置通知

<!--2 配置通知-->
<tx:advice id="txadvice">
    <!--配置事务参数-->
    <tx:attributes>
        <!--指定哪种规则的方法上面添加事务-->
        <tx:method name="accountMoney" propagation="REQUIRED"/>
        <!--<tx:method name="account*"/>-->
    </tx:attributes>
</tx:advice>

第三步 配置切入点和切面

<!--3 配置切入点和切面-->
<aop:config>
    <!--配置切入点-->
    <aop:pointcut id="pt" expression="execution(* com.atguigu.spring5.service.UserService.*(..))"/>
    <!--配置切面-->
    <aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>

声明式事务管理(注解方式)参数管理

isolation 事务隔离级别

spring有五大隔离级别:默认值为ISOLATION_DEFAULT(使用数据库的设置),其他四个隔离级别和数据库的隔离级别一样:

  1. ISOLATION_DEFAULT:用底层数据库的设置隔离级别,数据库设置的是什么我就用什么
  2. ISOLATION_READ_UNCOMMITTED:读未提交,最低隔离级别、事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读)
  3. ISOLATION_READ_COMMITTED:读已提交,一个事务提交后才能被其他事务读取到(会造成幻读、不可重复度),SQL server的默认级别
  4. ISOLATION_REPEATABLE_READ:可重复读,一个事务提交后,并且另一个事务提交之后才可以读到上一个事务的值。mysql的默认隔离级别
  5. ISOLATION_SERIALIZABLE:序列化,代价最高最可靠的隔离级别,该隔离级别能防止脏读、不可重复读、幻读。

timeout 事务超时时间

所谓事务超时,就是指一个事务所允许执行地最长时间,如果超过该事件限制但事务还没有完成,则自动回滚事务。

  1. 事务需要在一定时间内进行提交,如果不提交则进行回滚
  2. 默认值是-1(这表示事务的超时时间取决于底层事务系统或者没有超时时间),设置时间以秒为单位进行计算

readOnly 是否只读

  1. 读:查询操作,写:添加修改删除操作
  2. readOnly默认值false,表示可以查询,可以添加修改删除操作
  3. 设置readOnly值是true,设置成true之后,只能查询

rollbackFor 回滚

设置出现哪些异常进行事务回滚,默认遇到运行时异常和Error回滚,遇到检查型异常不回滚

noRollbackFor 不回滚

设置出现哪些异常不进行事务回滚

propagation 传播行为

假设场景:现在有两个方法A和B,方法A执行会在数据库ATable插入一条数据,方法B执行会在数据BTable插入一条数据,伪代码如下:

//将传入参数a存入ATable
pubilc void A(a){
    insertIntoATable(a);    
}
//将传入参数b存入BTable
public void B(b){
    insertIntoBTable(b);
}

方法调用关系如下:

public void testMain(){
    A(a1);  //调用A入参a1
    testB();    //调用testB
}

public void testB(){
    B(b1);  //调用B入参b1
    throw Exception;     //发生异常抛出
    B(b2);  //调用B入参b2
}

REQUIRED Spring默认的事务传播类型

如果当前没有事务,则自己新建一个事务,如果当前存在事务,则加入这个事务

  1. demo1: testMain和testB上声明事务,设置传播行为REQUIRED
@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){
    A(a1);  //调用A入参a1
    testB();    //调用testB
}
@Transactional(propagation = Propagation.REQUIRED)
public void testB(){
    B(b1);  //调用B入参b1
    throw new RuntimeException();     //发生异常抛出
    B(b2);  //调用B入参b2
}

testMain方法执行结果:数据库没有插入新的数据,数据库还是保持着执行testMain方法之前的状态,没有发生改变。testMain上声明了事务,在执行testB方法时就加入了testMain的事务(当前存在事务,则加入这个事务),在执行testB方法抛出异常后事务会发生回滚,因为testMain和testB使用的同一个事务,所以事务回滚后testMain和testB中的操作都会回滚,也就使得数据库仍然保持初始状态

  1. demo2:只在testB上声明事务,设置传播行为REQUIRED
public void testMain(){
    A(a1);  //调用A入参a1
    testB();    //调用testB
}
@Transactional(propagation = Propagation.REQUIRED)
public void testB(){
    B(b1);  //调用B入参b1
    throw new RuntimeException();     //发生异常抛出
    B(b2);  //调用B入参b2
}

testMain方法执行结果:数据a1存储成功,数据b1和b2没有存储。由于testMai没有声明事务,testB有声明事务且传播行为是REQUIRED,所以在执行testB时会自己新建一个事务(如果当前没有事务,则自己新建一个事务),testB抛出异常则只有testB中的操作发生了回滚,也就是b1的存储会发生回滚,但a1数据不会回滚,所以最终a1数据存储成功,b1和b2数据没有存储成功

SUPPORTS

当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行

  1. demo1: 当前存在事务,则加入当前事务REQUIRED 中的demo1
  2. demo2: 只在testB上声明事务,设置传播行为SUPPORTS
public void testMain(){
    A(a1);  //调用A入参a1
    testB();    //调用testB
}
@Transactional(propagation = Propagation.SUPPORTS)
public void testB(){
    B(b1);  //调用B入参b1
    throw new RumtimeException();     //发生异常抛出
    B(b2);  //调用B入参b2
}

testMain方法执行结果:a1,b1存入数据库,b2没有存储数据库。由于testMain没有声明事务,且testB的事务传播行为是SUPPORTS,所以执行testB时就是没有事务的(如果当前没有事务,就以非事务方法执行),因此在testB抛出异常时也不会发生回滚,所以最终结果就是a1和b1存储成功,b2没有存储。

MANDATORY

当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常

  1. demo1: 当前存在事务,则加入当前事务REQUIRED 中的demo1
  2. demo2: 只在testB上声明事务,设置传播行为MANDATORY
public void testMain(){
    A(a1);  //调用A入参a1
    testB();    //调用testB
}
@Transactional(propagation = Propagation.MANDATORY)
public void testB(){
    B(b1);  //调用B入参b1
    throw new RuntimeException();     //发生异常抛出
    B(b2);  //调用B入参b2
}

testMain方法执行结果:a1存储成功,而b1和b2没有存储。b1和b2没有存储,并不是事务回滚的原因,而是因为testMain方法没有声明事务,在去执行testB方法时就直接抛出事务要求的异常(如果当前事务不存在,则抛出异常),所以testB方法里面的内容就没有执行。

REQUIRES_NEW

创建一个新事务,如果存在当前事务,则挂起事务 可以理解为设置事务传播类型为REQUIRES_NEW的方法,在执行时,不论当前是否存在事务,总是会新建一个事务。

@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){
    A(a1);  //调用A入参a1
    testB();    //调用testB
    throw Exception;     //发生异常抛出
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void testB(){
    B(b1);  //调用B入参b1
    B(b2);  //调用B入参b2
}

testMain方法执行结果:a1没有存储,而b1和b2存储成功,因为testB的事务传播设置为REQUIRES_NEW,所以在执行testB时会开启一个新的事务,testMain中发生的异常是在testMain所开启的事务中,所以这个异常不会影响testB的事务提交,testMain中的事务会发生回滚,所以最终a1没有存储,而b1和b2存储成功了。

NOT_SUPPORTED

始终以非事务方式执行,如果当前存在事务,则挂起当前事务 testMain传播类型设置为REQUIRED,testB传播类型设置为NOT_SUPPORTED,且异常抛出位置在testB中

@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){
    A(a1);  //调用A入参a1
    testB();    //调用testB
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void testB(){
    B(b1);  //调用B入参b1
    throw Exception;     //发生异常抛出
    B(b2);  //调用B入参b2
}

结果:a1和b2没有存储,而b1存储成功。testMain事务,而testB不使用事务,所以执行testB存储b1成功,然后抛出异常,此时testMain检测到异常事务发生回滚,但是由于testB不在事务中,所以只有testMain的存储a1发生了回滚,最终只有b1存储成功,而a1和b1都没有存储。

NEVER

不使用事务,如果当前存在事务,则抛出异常 testMain设置传播类型为REQUIRED,testB传播类型设置为NEVER

@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){
    A(a1);  //调用A入参a1
    testB();    //调用testB
}
@Transactional(propagation = Propagation.NEVER)
public void testB(){
    B(b1);  //调用B入参b1
    B(b2);  //调用B入参b2
}

结果:直接抛出事务异常,且不会有数据存储到数据库。由于testMain事务传播类型为Required,所以testMain是运行在事务中,而testB事务传播类型为NEVER,所以testB不会执行而是直接抛出事务异常,此时testMain检测到异常就发生了回滚,所以最终数据库不会有数据存入。

spring事务传播行为18个示例

18个示例详解 Spring 事务传播机制-腾讯云开发者社区-腾讯云 (tencent.com)

spring事务原理

spring中事务的默认实现使用的是AOP,也就是代理的方式。