Spring管理数据库事务

本文详细介绍了Spring框架中的事务管理,包括事务的ACID特性,TransactionDefinition接口如何定义事务属性如隔离级别和传播行为,以及TransactionStatus和PlatformTransactionManager的作用。同时,通过实例分析了不同事务传播模式在并发环境下的行为。
摘要由CSDN通过智能技术生成

一、Spring与事务管理

事务是指数据库中的一个操作序列,它由一系列的SQL指令组成。在Java EE程序开发中,事务管理是一个影响范围较广的领域,在程序与数据库交互时,保证事务的正确执行尤为重要。

事务具备ACID四种特性,ACID是Atomicity(原子性)、Consistency (一致性)、Isolation(隔离性)和Durability(持久性)的英文缩写。
(1)原子性(Atomicity)
        事务最基本的操作单元,要么全部成功,要么全部失败,不会结束在中闻某个环节。事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样。
(2)一致性(Consistency)
事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。
(3)隔离性(Isolation)
事务的隔离性指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
(4)持久性(Durability)
事务的隔离性指的是只要事务成功结束,它对数据库所做的更新就必须永久保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。

对于事务管理,Spring 采用的方式是通过在高层次建立事务抽象,然后在此基础上提供一个统一的编程模型,这意味着,Spring具有在多种环境中配置和使用事务的能力,无论是Spring JDBC,还是以MyBatis为代表的ORM框架,Spring都能够使用统一的编程模型对事务进行管理并为事务管理提供通用的支持。

基于Spring IOC 和Spring AOP,Spring 提供了声明式事务管理的方式,它允许开发人员直接在Spring容器中定义事务的边界和属性,除此之外,Spring还实现了事务管理和数据访问的分离,在这种条件下,开发人员只需关注对当前事务的界定,其余工作将由Spring框架自动完成。

二、事务管理的核心接口

Spring主要通过三个接口实现事务抽象,这三个接口分别是TransactionDefinition、TransactionStatus 和 PlatformTransactionManager,其中,TransactionDefinition用于定义事务的属性TransactionStatus 用于界定事务的状态PlatformTransactionManager是事务管理器接口,根据属性管理事务

1、TransactionDefinition
        TransactionDefinition接口主要用于定义事务的属性,这些属性包括事务的隔离级别、事务的传播行为、事务的超时时间、是否为只读事务等。

(1)事务的隔离级别
        事务的隔离级别是指若干个并发的事务之间的隔离程度。
        在一个典型的应用程序中,多个事务同时运行,经常会为了完成他们的工作而操作同一个数据。并发虽然是必需的,但是会导致以下问题:

  • 脏读(Dirty read):脏读发生在一个事务读取了被另一个事务改写但尚未提交的数据时。如果这些改变在稍后被回滚了,那么第一个事务读取的数据就会是无效的。        
  • 不可重复读(Nonrepeatable read) :不可重复读发生在一个事务执行相同的查询两次或两次以上,但每次查询的值都不相同。这通常是由于另一个并发事务在两次查询之间更新或删除数据。
  • 幻读(Phantom reads):当一个事务读取几行记录后,另一个并发事务插入了一些记录时,幻影读就发生了。在后来的查询中,第一个事务就会发现一些原来没有的额外记录。

        TransactionDefinition定义了五种隔离级别,具体如表所示。

表中列举了TransactionDefinition定义的五种隔离级别,除了ISOLATION_DEFAULT是TransactionDefinition特有的之外,其余四个分别与java.sql.Connection接口定义的隔离级别相对应。
        1)ISOLATION_READ_UNCOMMITTED
        允许读取尚未提交的更改。可能导致脏读、幻影读或不可重复读。该级别下如果一个事务已经开始写数据,则另外一个事务则不允许同时进行写操作,但允许其他事务读此行数据。
        2)ISOLATION_READ_COMMITTED
        允许从已经提交的并发事务读取。可防止脏读,但幻影读和不可重复读仍可能会发生,该级别应用较多。该级别下读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。
        3)ISOLATION_REPEATABLE_READ
        对相同字段的多次读取的结果是一致的,除非数据被当前事务本身改变。可防止脏读和不可重复读,但幻影读仍可能发生。其效果相当于对要读取的数据行保存了备份,每次从备份中读取数据。

(2)事务的传播行为
        TransactionDefinition定义了七种事务传播行为,具体如表所示。
以某一个事务传播行为修饰的方法A(类)被嵌套进另一个方法B(类)的例子说明各事务传播行为。
        1)PROPAGATION_REQUIRED
        默认值,如果当前存在事务,则直接调用方法执行,如果当前没有事务,则创建一个新的事务,再执行方法。场景一,当A有此事务,B没有事务。在A执行之前创建一个事务,当A执行成功,则提交事务,因此就算B中抛出异常,也不会导致A的回滚,因为A的事务已经提交。场景二,A有此事务,B也有此事务。则执行A时,A会加入B的事务中,所以A或者B抛出异常,会导致整个事务的回滚,需要注意的是如果A抛出异常,B对A的异常做了处理,B也会进行回滚,因为A在事务中,事务在A方法中已经感知到异常,所以整个事务都会回滚。
        以下举例说明:

@Service
public class User1ServiceImpl {
    //省略其他...
    @Transactional(propagation = Propagation.REQUIRED)
    public void addRequired(User1 user){
        user1Mapper.insert(user);
    }
}
@Service
public class User2ServiceImpl {
    //省略其他...
    @Transactional(propagation = Propagation.REQUIRED)
    public void addRequired(User2 user){
        user2Mapper.insert(user);
    }
    @Transactional(propagation = Propagation.REQUIRED)
    public void addRequiredException(User2 user){
        user2Mapper.insert(user);
        throw new RuntimeException();
    }
}
public void notransaction_exception_required_required(){
    User1 user1=new User1();
    user1.setName("张三");
    user1Service.addRequired(user1);
    User2 user2=new User2();
    user2.setName("李四");
    user2Service.addRequired(user2);
    throw new RuntimeException();
}

“张三”,“李四”均插入。
外围方法未开启事务,插入“张三”、“李四”方法在自己的事务中独立运行,外围方法异常不影响内部插入“张三”、“李四”方法独立的事务。

public void notransaction_required_required_exception(){
    User1 user1=new User1();
    user1.setName("张三");
    user1Service.addRequired(user1);
    User2 user2=new User2();
    user2.setName("李四");
    user2Service.addRequiredException(user2);
}

 “张三”插入,“李四”未插入。
外围方法没有事务,插入“张三”、“李四”方法都在自己的事务中独立运行,所以插入“李四”方法抛出异常只会回滚插入“李四”方法,插入“张三”方法不受影响。

@Transactional(propagation = Propagation.REQUIRED)
public void transaction_exception_required_required(){
    User1 user1=new User1();
    user1.setName("张三");
    user1Service.addRequired(user1);
    User2 user2=new User2();
    user2.setName("李四");
    user2Service.addRequired(user2);
    throw new RuntimeException();
}

“张三”、“李四”均未插入。
外围方法开启事务,内部方法加入外围方法事务,外围方法回滚内部方法也要回滚。

@Transactional(propagation = Propagation.REQUIRED)
public void transaction_required_required_exception(){
    User1 user1=new User1();
    user1.setName("张三");
    user1Service.addRequired(user1);
    User2 user2=new User2();
    user2.setName("李四");
    user2Service.addRequiredException(user2);
}

“张三”、“李四”均未插入。
外围方法开启事务,内部方法加入外围方法事务,内部方法抛出异常回滚,外围方法感知异常致使整体事务回滚。

@Transactional(propagation = Propagation.REQUIRED)
public void transaction_required_required_exception_try(){
    User1 user1=new User1();
    user1.setName("张三");
    user1Service.addRequired(user1);
    User2 user2=new User2();
    user2.setName("李四");
    try {
        user2Service.addRequiredException(user2);
    } catch (Exception e){
        System.out.println("方法回滚");
    }
}

“张三”、“李四”均未插入。
外围方法开启事务,内部方法加入外围方法事务,内部方法抛出异常回滚,即使方法被catch不被外围方法感知,整个事务依然回滚。

         2)PROPAGATION_REQUIRED_NEW
        方法执行需要创建一个新的事务,如果当前已存在事务,则先暂停该事务。在外围方法未开启事务的情况下REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。在外围方法开启事务的情况下REQUIRES_NEW修饰的内部方法依然会单独开启独立事务,且与外部方法事务也独立,内部方法之间、内部方法和外部方法事务均相互独立,互不干扰。若外部方法捕获内部方法抛出的异常,则外部方法并不会回滚

public class User1ServiceImpl {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void addRequiresNew(User1 user){
        user1Mapper.insert(user);
    }
    @Transactional(propagation = Propagation.REQUIRED)
    public void addRequired(User1 user){
        user1Mapper.insert(user);
    }
}
public class User2ServiceImpl {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void addRequiresNew(User2 user){
        user2Mapper.insert(user);
    }
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void addRequiresNewException(User2 user){
        user2Mapper.insert(user);
        throw new RuntimeException();
    }
}
public void notransaction_exception _requiresNew_requiresNew(){
    User1 user1=new User1();
    user1.setName("张三");
    user1Service.addRequiresNew(user1);
    User2 user2=new User2();
    user2.setName("李四");
    user2Service.addRequiresNew(user2);
    throw new RuntimeException();
}

“张三”插入,“李四”插入。
外围方法没有事务,插入“张三”、“李四”方法都在自己的事务中独立运行,外围方法抛出异常回滚不会影响内部方法。

public void notransaction_requiresNew_requiresNew_exception(){
    User1 user1=new User1();
    user1.setName("张三");
    user1Service.addRequiresNew(user1);
    User2 user2=new User2();
    user2.setName("李四");
    user2Service.addRequiresNewException(user2);
}

 “张三”插入,“李四”未插入。
外围方法没有开启事务,插入“张三”方法和插入“李四”方法分别开启自己的事务,插入“李四”方法抛出异常回滚,其他事务不受影响。

@Transactional(propagation = Propagation.REQUIRED)
public void transaction_exception_required_requiresNew_requiresNew(){
    User1 user1=new User1();
    user1.setName("张三");
    user1Service.addRequired(user1);
    User2 user2=new User2();
    user2.setName("李四");
    user2Service.addRequiresNew(user2);
    User2 user3=new User2();
    user3.setName("王五");
    user2Service.addRequiresNew(user3);
    throw new RuntimeException();
}

 “张三”未插入,“李四”插入,“王五”插入。
外围方法开启事务,插入“张三”方法和外围方法一个事务,插入“李四”方法、插入“王五”方法分别在独立的新建事务中,外围方法抛出异常只回滚和外围方法同一事务的方法,故插入“张三”的方法回滚。

@Transactional(propagation = Propagation.REQUIRED)
public void transaction_required_requiresNew_requiresNew_exception(){
    User1 user1=new User1();
    user1.setName("张三");
    user1Service.addRequired(user1);
    User2 user2=new User2();
    user2.setName("李四");
    user2Service.addRequiresNew(user2);
    User2 user3=new User2();
    user3.setName("王五");
    user2Service.addRequiresNewException(user3);
}

“张三”未插入,“李四”插入,“王五”未插入。
外围方法开启事务,插入“张三”方法和外围方法一个事务,插入“李四”方法、插入“王五”方法分别在独立的新建事务中。插入“王五”方法抛出异常,首先插入“王五”方法的事务被回滚,异常继续抛出被外围方法感知,外围方法事务亦被回滚,故插入“张三”方法也被回滚。

@Transactional(propagation = Propagation.REQUIRED)
public void transaction_required_requiresNew_requiresNew_exception_try(){
    User1 user1=new User1();
    user1.setName("张三");
    user1Service.addRequired(user1);
    User2 user2=new User2();
    user2.setName("李四");
    user2Service.addRequiresNew(user2);
    User2 user3=new User2();
    user3.setName("王五");
    try {
        user2Service.addRequiresNewException(user3);
    } catch (Exception e){
        System.out.println("回滚");
    }
}

“张三”插入,“李四”插入,“王五”未插入。
外围方法开启事务,插入“张三”方法和外围方法一个事务,插入“李四”方法、插入“王五”方法分别在独立的新建事务中。插入“王五”方法抛出异常,首先插入“王五”方法的事务被回滚,异常被catch不会被外围方法感知,外围方法事务不回滚,故插入“张三”方法插入成功。

         3 )PROPAGATION_NESTED
        如果执行方法的线程没有处于事务中,那么与REQUIRED相同。如果已经处于事务中,则创建一个新事务作为当前事务的嵌套事务来运行该方法。在外围方法未开启事务的情况下NESTED和REQUIRED作用相同,修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干扰。在外围方法开启事务的情况下NESTED修饰的内部方法属于外部事务的子事务,外围主事务回滚,子事务一定回滚,而内部子事务可以单独回滚而不影响外围主事务和其他子事务

public class User1ServiceImpl implements User1Service {
@Transactional(propagation = Propagation.NESTED)
public void addNested(User1 user){
    user1Mapper.insert(user);
    }
}
public class User2ServiceImpl {
    @Transactional(propagation = Propagation.NESTED)
    public void addNested(User2 user){
        user2Mapper.insert(user);
    }
    @Transactional(propagation = Propagation.NESTED)
    public void addNestedException(User2 user){
        user2Mapper.insert(user);
        throw new RuntimeException();
    }
}
public void notransaction_exception_nested_nested(){
    User1 user1=new User1();
    user1.setName("张三");
    user1Service.addNested(user1);
    User2 user2=new User2();
    user2.setName("李四");
    user2Service.addNested(user2);
    throw new RuntimeException();
}

张三"、“李四”均插入。
外围方法未开启事务,插入“张三”、“李四”方法在自己的事务中独立运行,外围方法异常不影响内部插入“张三”、“李四”方法独立的事务。

public void notransaction_nested_nested_exception(){
    User1 user1=new User1();
    user1.setName("张三");
    user1Service.addNested(user1);
    User2 user2=new User2();
    user2.setName("李四");
    user2Service.addNestedException(user2);
}

“张三”插入,“李四”未插入。
外围方法没有事务,插入“张三”、“李四”方法都在自己的事务中独立运行,所以插入“李四”方法抛出异常只会回滚插入“李四”方法,插入“张三”方法不受影响。

@Transactional
public void transaction_exception_nested_nested(){
    User1 user1=new User1();
    user1.setName("张三");
    user1Service.addNested(user1);
    User2 user2=new User2();
    user2.setName("李四");
    user2Service.addNested(user2);
    throw new RuntimeException();
}

张三"、“李四”均未插入。
外围方法开启事务,内部事务为外围事务的子事务,外围方法回滚,内部方法也要回滚。

@Transactional
public void transaction_nested_nested_exception(){
    User1 user1=new User1();
    user1.setName("张三");
    user1Service.addNested(user1);
    User2 user2=new User2();
    user2.setName("李四");
    user2Service.addNestedException(user2);
}

“张三”、“李四”均未插入。
外围方法开启事务,内部事务为外围事务的子事务,内部方法抛出异常回滚,且外围方法感知异常致使整体事务回滚。

@Transactional
public void transaction_nested_nested_exception_try(){
    User1 user1=new User1();
    user1.setName("张三");
    user1Service.addNested(user1);
    User2 user2=new User2();
    user2.setName("李四");
    try {
        user2Service.addNestedException(user2);
    } catch (Exception e){
        System.out.println("方法回滚");
    }
}

 “张三”插入、“李四”未插入。
外围方法开启事务,内部事务为外围事务的子事务,插入“李四”内部方法抛出异常,可以单独对子事务回滚。

(3)事务的超时时间和是否只读
        事务的超时时间是指事务执行的时间界限,超过这个时间界限,事务将会回滚。TransactionDefinition接口提供了TIMEOUT_DEFAULT 的常量定义,用来指定事务的超时时间。
当事务的属性为只读时,该事务不修改任何数据,只读事务有助于提升性能,如果在只读事务中修改数据,可能会引发异常。

 (4) TransactionDefinition接口的方法
        TransactionDefinition接口提供了一系列方法来获取事务的属性,具体如表所示。

2、TransactionStatus
        TransactionStatus接口主要用于界定事务的状态,通常情况下,编程式事务中使用该接口较多。
        TransactionStatus接口中提供了一系列返回事务状态信息的方法,具体如所示。

3、PlatformTransactionManager
        PlatformTransactionManager接口是Spring 事务管理的中心接口,它真正执行了事务管理的职能,并针对不同的持久化技术封装了对应的实现类。
        PlatformTransactionManager接口提供了一系列方法用于管理事务,具体如表所示。

在实际应用中,Spring事务管理实际是由具体的持久化技术来来完成的,而PlatformTransactionManager接口只提供统一的抽象方法。为了应对不同持久化技术的差异性,Spring为它们提供了具体的实现类,例如,Spring为SpringJDBC或 MyBatis等依赖于DataSource的持久化技术提供了实现类DataSourceTransactionManager,该类位于org.springframework.jdbc.datasource包中,如此一来,Spring JDBC或 MyBatis等持久化技术的事务管理由DataSourceTransactionManager来实现,而且 Spring可以通过PlatformTransactionManager接口实现统一管理。
 

  • 9
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值