Spring 声明式事务

Spring 声明式事务

spring中对事务的控制有声明式事务编程式事务

声明式事务是使用 AOP 进行织入, 而编程式事务是使用代码来实现

关于编程式事务, 这里不进行介绍, 可查看官网:http://mybatis.org/spring/zh/transactions.html#programmatic

我主要说声明式事务.

当然, 如果你是用的springboot项目, 你不需要任何配置, 直接使用注解即可: 基于@Transactional注解的实现

图解:

回顾事务

  • 事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!
  • 事务管理是企业级应用程序开发中必备技术,用来确保数据的完整性和一致性。

是否还记得在JDBC中使用事务: 点击跳转到JDBC使用事务

事务四个ACID特性

  • Atomicity: 原子性 事务中所有操作是不可再分割的原子单位。事务中所有操作要么全部执行成功,要么全部执行失败。
  • Consistency: 一致性 事务执行后,数据库状态与其它业务规则保持一致。如转账业务,无论事务执行成功与否,参与转账的两个账户金额之和在事务前后应该是保持不变的。
  • Isolation: 隔离性,隔离性是指在并发操作中,不同事务之间应该隔离开来,使每个并发中的事务不会相互干扰。也就是说,在事中务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
  • Durability: 持久性,一旦事务提交成功,事务中所有的数据操作都必须被持久化到数据库中,即使提交事务后,数据库马上崩溃,在数据库重启时,也必须能保证通过某种机制恢复数据。

下面我们来正式开始使用事务

基于XML配置文件实现

1. 前提准备

spring已经整合了mybatis, 并导入了向关依赖, 如jdbc, mybatis, juntl, spring等

2. service示例(接口略)

我们假设第一条对数据库的插入操作成功, 第二条对数据库的更改失败

@Service
public class UserServiceImpl implements UserService {
	
    // 注入dao层
    @Resource
    private UserDao userDao;

    // 业务方法
    @Override
    public void change() {
        // 使用dao层插入数据
        User user = new User(null, "root", "122", "54627946@qq.com");
        userDao.insertUsers(user);
        
        // 更改一条数据, 这里已经把sql语句故意写错了, 目的是为了执行更改的时候排除异常
        User user2 = new User(3, "sys", "1232", "54665885@qq.com");
        userDao.updateUser(user2);
    }
}

3. 配置文件

3.1 jdbc事务

<bean id="transactionManager"
      	class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

3.2 配置事务的通知

<!--配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <!--
        <tx:method name="insertUser" propagation="REQUIRED"/>
        <tx:method name="updateUser" propagation="REQUIRED"/>
        -->
        <!--这里使用*号是配置service中的所有方法, 就可以省略上面每个方法都要声明了-->
        <!--propagation属性为事务的传播特性, 默认就是REQUIRED, 可省略-->
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

关于<tx:method>标签的详细内容[点击跳转]

3.3 AOP织入事务

<!--AOP织入事务-->
<aop:config>
    <aop:pointcut id="txPointcut" expression="execution(* com.aaron.service.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>

4. 测试

@Test
public void test3() {
    userService.change();
}

测试结果, 抛出异常, 数据库中没有插入数据

假设我们跳过第三步, 直接进行测试, 虽然抛出了异常, 但是数据还是插入了

tx:method标签属性

关于<tx:method>标签的属性, 简介如下

属性 类型 默认值 说明
propagation Propagation枚举 REQUIRED 事务传播属性
isolation isolation枚举 DEFAULT(所用数据库默认级别) 事务隔离级别
readOnly boolean boolean boolean
timeout int -1 超时(秒), 超时后抛出异常
rollbackFor Class[] {} 需要回滚的异常类
即遇到哪些异常回滚
包括异常的子类
rollbackForClassName String[] {} 需要回滚的异常类名
noRollbackFor Class[] {} 不需要回滚的异常类
即遇到哪些异常不回滚<br?包括异常子类
noRollbackForClassName String[] {} 不需要回滚的异常类名
  • readOnly
    事务属性中的readOnly标志表示对应的事务应该被最优化为只读事务。如果值为true就会告诉Spring我这个方法里面没有insert或者update,你只需要提供只读的数据库Connection就行了,这种执行效率会比read-write的Connection高,所以这是一个最优化提示。在一些情况下,一些事务策略能够起到显著的最优化效果,例如在使用Object/Relational映射工具(如:Hibernate或TopLink)时避免dirty checking(试图“刷新”)。

  • timeout
    在属性中还有定义“timeout”值的选项,指定事务超时为几秒。一般不会使用这个属性。在JTA中,这将被简单地传递到J2EE服务器的事务协调程序,并据此得到相应的解释。

  • Isolation Level(事务隔离等级)的5个枚举值
    为什么事务要有Isolation Level这个属性?先回顾下数据库事务的知识:

    1. 第一类丢失更新(lost update):在完全未隔离事务的情况下,两个事物更新同一条数据资源,某一事物异常终止,回滚造成第一个完成的更新也同时丢失。
    2. 第二类丢失更新(second lost updates):是不可重复读的特殊情况,如果两个事务都读取同一行,然后两个都进行写操作,并提交,第一个事务所做的改变就会丢失。
    3. 脏读(dirty read):如果第二个事务查询到第一个事务还未提交的更新数据,形成脏读。因为第一个事务你还不知道是否提交,所以数据不一定是正确的。
    4. 虚读(phantom read):一个事务执行两次查询,第二次结果集包含第一次中没有或者某些行已被删除,造成两次结果不一致,只是另一个事务在这两次查询中间插入或者删除了数据造成的。
    5. 不可重复读(unrepeated read):一个事务两次读取同一行数据,结果得到不同状态结果,如中间正好另一个事务更新了该数据,两次结果相异,不可信任。

    当遇到以上这些情况时我们可以设置isolation下面这些枚举值:
    DEFAULT:采用数据库默认隔离级别
    SERIALIZABLE:最严格的级别,事务串行执行,资源消耗最大;
    REPEATABLE_READ:保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。
    READ_COMMITTED:大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”。该级别适用于大多数系统。
    READ_UNCOMMITTED:保证了读取过程中不会读取到非法数据。隔离级别在于处理多事务的并发问题。

  • propagation属性, 事务传播特性

    • 取值:(REQUIRED)propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择, 也是spring中默认的选择

    • 取值:(SUPPORTS)propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。

    • 取值:(MANDATORY)propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。

    • 取值:(REQUIRES_NEW)propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。

    • 取值:(NOT_SUPPORTED)propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

    • 取值:(NEVER)propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。

    • 取值:(NESTED)propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作

基于@Transactional注解的实现

注解的实现非常的简单, 我们不需要任何的配置, 只需要开启驱动, 然后在在service类上加个@Transactional注解即可

例如:

@Transactional(readOnly = false,
               rollbackFor =Throwable.class,
               timeout =-1,
               isolation = Isolation.READ_COMMITTED)
// service实现类...

开启驱动

配置文件 springboot可不写

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

给service类加注解

@Service
@Transactional
public class UserServiceImpl implements UserService {
	
    // 注入dao层
    @Resource
    private UserDao userDao;

    // 业务方法
    @Override
    public void change() {
        // 使用dao层插入数据
        User user = new User(null, "root", "122", "54627946@qq.com");
        userDao.insertUsers(user);
        
        // 更改一条数据, 这里已经把sql语句故意写错了, 目的是为了执行更改的时候排除异常
        User user2 = new User(3, "sys", "1232", "54665885@qq.com");
        userDao.updateUser(user2);
    }
}

进行测试后以后抛出事务已经回滚, 就说明成功了, 即数据抛异常后没有插入

注解属性

属性也可参考上面的 tx:method标签属性

属性名 说明
name 当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。
propagation 事务的传播行为,默认值为 REQUIRED。
isolation 事务的隔离度,默认值采用 DEFAULT。
timeout 事务的超时时间,默认值为-1(永不超时)。
如果超过该时间限制但事务还没有完成,则自动回滚事务。
read-only 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
rollback-for 用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。
no-rollback- for 抛出 no-rollback-for 指定的异常类型,不回滚事务。
posted @ 2020-06-24 14:52  zpk-aaron  阅读(220)  评论(1编辑  收藏  举报