Spring系列.事务管理

Spring提供了一致的事务管理抽象。这个抽象是Spring最重要的抽象之一, 它有如下的优点:

  • 为不同的事务API提供一致的编程模型,如JTA、JDBC、Hibernate和MyBatis数据库层 等;
  • 提供比大多数事务API更简单的,易于使用的编程式事务管理API;
  • 完美整合Spring数据访问抽象;
  • 支持Spring声明式事务管理;

这篇博客就来介绍Spring事务管理相关的内容。

事务简介

什么是事务

事务(Transaction)一般是指对数据库的一个或一组操作单元。

事务的作用

1、为数据库操作提供了一个从失败中恢复到正常状态的方法,同时提供了数据库即使在异常状态下仍能保持一致性的方法。
2、当多个应用程序在并发访问数据库时,可以在这些应用程序之间提供一个隔离方法,以防止彼此的操作互相干扰。

当一个事务被提交给了DBMS(数据库管理系统),则DBMS需要确保该事务中的所有操作都成功完成且其结果被永久保存在数据库中,如果事务中有的操作没有成功完成,则事务中的所有操作都需要被回滚,回到事务执行前的状态(要么全执行,要么全都不执行);同时,该事务对数据库或者其他事务的执行无影响,所有的事务都好像在独立的运行。

事务的特点

事务具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。

原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
持久性(Durability):一个事务一旦提交,他对数据库的修改应该永久保存在数据库中。

事务的隔离级别

在多个事务并发操作的过程中,如果控制不好隔离级别,就有可能产生脏读、不可重复读或者幻读等读现象。数据操作过程中利用数据库的锁机制或者多版本并发控制机制获取更高的隔离等级。但是,随着数据库隔离级别的提高,数据的并发能力也会有所下降。所以,如何在并发性和隔离性之间做一个很好的权衡就成了一个至关重要的问题。

ANSI/ISO SQL定义的标准隔离级别有四种,从高到底依次为:可序列化(Serializable)、可重复读(Repeatable reads)、提交读(Read committed)、未提交读(Read uncommitted)。

  1. 读未提交
    未提交读(READ UNCOMMITTED)是最低的隔离级别。通过名字我们就可以知道,在这种事务隔离级别下,一个事务可以读到另外一个事务未提交的数据。未提交读会导致脏读

事务在读数据的时候并未对数据加锁。

务在修改数据的时候只对数据增加行级共享锁。

  1. 读已提交
    提交读(READ COMMITTED)也可以翻译成读已提交,通过名字也可以分析出,在一个事务修改数据过程中,如果事务还没提交,其他事务不能读该数据。读已提交会导致不可重复读

事务对当前被读取的数据加 行级共享锁(当读到时才加锁),一旦读完该行,立即释放该行级共享锁;
事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加 行级排他锁,直到事务结束才释放。

  1. 可重复读
    可重复读能保障一个事务在事务内读到的某条数据是一致的。但是可重复读不能解决幻读的问题。就是在事务还没结束时,其他事务又插入了一条新的数据。

事务在读取某数据的瞬间(就是开始读取的瞬间),必须先对其加 行级共享锁,直到事务结束才释放;
事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加 行级排他锁,直到事务结束才释放

  1. 序列化
    可序列化(Serializable)是最高的隔离级别,前面提到的所有的隔离级别都无法解决的幻读,在可序列化的隔离级别中可以解决。

事务在读取数据时,必须先对其加 表级共享锁 ,直到事务结束才释放;
事务在更新数据时,必须先对其加 表级排他锁 ,直到事务结束才释放。

下面是脏读、不可重复读和幻读的解释。

脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交(commit)到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。
不可重复读:在一个事务内,多次读同一个数据。在这个事务还没有结束时,另一个事务也访问该同一数据。那么,在第一个事务的两次读数据之间。由于第二个事务的修改,那么第一个事务读到的数据可能不一样,这样就发生了在一个事务内两次读到的数据是不一样的,因此称为不可重复读,即原始读取不可重复。
幻读指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的数据行。幻读专指“新插入的行”是不可重复读(Non-repeatable reads)的一种特殊场景

Spring事务

Spring事务模型的优势

事务可以分为本地事务和全局事务,这两种事务都有一定程度的局限性,Spring框架的事务管理支持解决全局和本地事务模型的局限性。

1. 全局事务
全局事务可以让你跨多个事务进行工作,比如你的事务同事包含多个关系型数据库,也可以包含关系型数据库和JMS事务。一般情况下都是通过JTA来实现全局事务,但是JTA一般需要具体的应用容器来支持,这就导致代码的通用性较低。

下面举个全局事务的列子,方便理解。

在电商网站上,在消费者点击购买按钮后,交易后台会进行库存检查、下单、减库存、更新订单状态等一连串的服务调用,每一个操作对应一个独立的服务,服务一般会有独立的数据库,因此会产生分布式事务问题。分布式事务就是一种比较常见的全局事务。

2. 本地事务

本地事务和具体的某个事务关联,比如说JDBC事务。本地事务比较简单,但是不能实现分布式事务的功能。

Spring提供了统一方便的事务编程模型,可以解决上面本地事务和全局事务的局限。使用Spring的事务API进行事务管理,底层可以适应各种事务资源。

Spring事务抽象

Spring为提供统一的事务编程模型,提供相关的接口。主要接口如下:

public interface PlatformTransactionManager {

    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

    void rollback(TransactionStatus status) throws TransactionException;
}

上面接口的getTransaction方法接收一个TransactionDefinition参数,返回一个TransactionStatus 值。其中TransactionStatus 可能代表一个新的事务,或者返回一个已经存在本次调用栈中的事务。(TransactionStatus 和具体的线程绑定。可以自己写代码测试下)

TransactionStatus接口定义如下。

public interface TransactionStatus extends SavepointManager {

    boolean isNewTransaction();

    boolean hasSavepoint();

    void setRollbackOnly();

    boolean isRollbackOnly();

    void flush();

    boolean isCompleted();
}

声明式事务管理

使用Spring的事务管理,推荐使用声明式事务管理。Spring的声明式事务管理是通过Spring的AOP功能实现的。

tx

因为平时在开发过程中都是使用注解的方式使用声明式事务。下面就介绍注解的方式。

step1:添加@EnableTransactionManagement注解

@Configuration
@EnableTransactionManagement
@MapperScan("com.csx.demo.spring.boot.dao")
public class MyBatisConfig {

}

step2:添加@Transactional注解到接口的实现。

@Service
@Transactional(readOnly = true,rollbackFor = Exception.class)
public class SysUserServiceImpl implements SysUserService {

    @Autowired
    private SysUserMapper sysUserMapper;

    @Override
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public int saveSysUser(SysUser user) {
        int i = sysUserMapper.insert(user);
        return i;
    }
}

使用Spring的声明式事务就这么简单。

当你使用Spring的AOP方式来使用事务的话,你添加@Transactional注解的方法一定要是public的,不然事务不会生效。

假如你需要让非public的方法生效,你需要使用AspectJ 的AOP实现。(说明:Spring的AOP功能有两种实现方式,一种是Spring自己实现的AOP功能,主要是通过JDK动态代理或者CGLIB动态代理实现的。还有一种方式是整合AspectJ 这个第三方AOP框架实现的)

另外,@Transactional注解可以添加到接口、接口中的方法定义、类和类里面的方法。Spring团队建议将注解加到具体的类和方法实现上,而不是加到接口定义上(原因见下面英文描述)。当然,您可以将@Transactional注释放在接口(或接口方法)上,但是只有在使用基于接口的代理时,才会像您期望的那样工作。

The fact that Java annotations are not inherited from interfaces means that, if you use class-based proxies (proxy-target-class="true") or the weaving-based aspect (mode="aspectj"), the transaction settings are not recognized by the proxying and weaving infrastructure, and the object is not wrapped in a transactional proxy.

@Transactional注解的配置

@Transactional注解可以进行以下配置。

Property Type Description
value String 一个项目中可以存在多个事务管理器,这个值用于指定具体使用哪个事务管理器。
propagation enum: Propagation 设置传播机制
isolation enum: Isolation 设置隔离级别(只有当传播机制设置成 REQUIRED or REQUIRES_NEW时这个配置才生效)
timeout int (in seconds of granularity) 设置超时时间(以秒为单位,只有当传播机制设置成 REQUIRED or REQUIRES_NEW时这个配置才生效)
readOnly boolean 只读事务配置(只有当传播机制设置成 REQUIRED or REQUIRES_NEW时这个配置才生效)
rollbackFor Array of Class objects, which must be derived from Throwable. 回滚的异常
rollbackForClassName Array of class names. The classes must be derived from Throwable.
noRollbackFor Array of Class objects, which must be derived from Throwable. 不回滚的异常
noRollbackForClassName Array of String class names, which must be derived from Throwable.

假如我们没有配置上面的属性,这些属性也都是有默认值的

  • The propagation setting is PROPAGATION_REQUIRED.
  • The isolation level is ISOLATION_DEFAULT.
  • The transaction is read-write.
  • The transaction timeout defaults to the default timeout of the underlying transaction system, or to none if timeouts are not supported.
  • Any RuntimeException triggers rollback, and any checked Exception does not.(默认回滚RuntimeException )

多事务管理器

有时候项目中可能会存在多个事务管理器,比如JDBC事务,比如JMS事务。这时候我们可以通过transactionManager属性指定。

public class TransactionalService {

    @Transactional("jdbc")
    public void setSomething(String name) { ... }

    @Transactional("jms")
    public void doSomething() { ... }
}

上面的jdbc和jms是指两个事务管理器在Spring容器中Bean的名字。

事务的传播机制

在TransactionDefinition这个类中定义了6中传播机制的类型。

1. PROPAGATION_REQUIRED

2. PROPAGATION_REQUIRES_NEW

3. PROPAGATION_NESTED

只支持JDBC事务。

编程式事务管理

Spring框架提供两种方式来进行编程式事务管理:

  • The TransactionTemplate.
  • PlatformTransactionManager 的实现。

Spring团队推荐使用第一种方式进行编程式事务管理。

1. 使用TransactionTemplate进行事务管理

下面是使用TransactionTemplate进行事务管理的一个例子。

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = TxApp.class)
public class TxTest {

    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    @Autowired
    private TransactionTemplate transactionTemplate;

    @Test
    public void selectUserTest() {

        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        //同一个sqlSession创建的Mapper
        SysUserMapper mapper = sqlSession1.getMapper(SysUserMapper.class);
        SysUser sysUser = new SysUser();
        sysUser.setUsername("zyzl");
        sysUser.setPassword("11");

        //有返回值的操作
        transactionTemplate.execute(new TransactionCallback() {
            @Override
            public Object doInTransaction(TransactionStatus status) {
                try {
                    return mapper.insert(sysUser);
                } catch (Exception e) {
                    status.setRollbackOnly();
                    throw e;
                }
            }
        });

        //没返回值的操作
        transactionTemplate.executeWithoutResult(new Consumer<TransactionStatus>() {
            @Override
            public void accept(TransactionStatus transactionStatus) {
                try {
                    mapper.insert(sysUser);
                } catch (Exception e) {
                    transactionStatus.setRollbackOnly();
                    throw e;
                }
            }
        });
    }

}

我们也可以通过TransactionTemplate来设定事务的隔离级别等属性。

//设置隔离级别
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
//设置超时时间
transactionTemplate.setTimeout(30);
//设置传播机制
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

对于不同的事务操作,如果需要不同的隔离级别和传播机制的话,请使用不同的transactionTemplate。也就是说,你要创建不同的transactionTemplate对象来进行操作。

2. 使用PlatformTransactionManager进行事务管理

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// explicitly setting the transaction name is something that can be done only programmatically
def.setName("SomeTxName");
// 设置传播机制
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
//开启事务
TransactionStatus status = txManager.getTransaction(def);
try {
    // execute your business logic here
}
catch (MyException ex) {
    txManager.rollback(status);
    throw ex;
}
//提交事务
txManager.commit(status);

事务绑定事件

使用@TransactionalEventListener可以在事务提交前后,回滚后等阶段触发某些操作。但是这个功能暂时还没想到很好的使用场景。后续有需要再来用。

@Component
public class MyComponent {

    @TransactionalEventListener
    public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) {
        // ...
    }
}

重要类和接口

  • PlatformTransactionManager:事务管理器,用于获取事务,提交回滚事务;
  • TransactionDefinition:
  • TransactionStatus:代表一个事务

进一步阅读

Distributed transactions in Spring, with and without XA is a JavaWorld presentation in which Spring’s David Syer guides you through seven patterns for distributed transactions in Spring applications, three of them with XA and four without.(Spring实现分布式事务的介绍)

Tags: