Spring声明式事务管理介绍

为了减少手动编写管理事务的语句,Spring利用AOP为我们提供了一个数据库事务的约定流程。

spring 框架为我们提供了一组事务控制的接口。这组接口是在spring-tx-5.0.2.RELEASE.jar 中。

对于事务,需要通过标注告诉Spring在什么地方启用数据库事务功能。对于声明式事务,是使用@Transactional进行标注的。这个注解可以标注在类或者方法上,当它标注在类上时,代表这个类所有公共(public)非静态的方法都将启用事务功能。@Transactional中,还允许配置许多的属性,如事务的隔离级别和传播行为、异常类型,从而确定方法发生什么异常下回滚事务或者发生什么异常下不回滚事务等。这些配置内容,是在 Spring IoC容器在加载时就会将这些配置信息解析出来,然后把这些信息存到事务定义器(TransactionDefinition接口的实现类)里,并且记录哪些类或者方法需要启动事务功能,采取什么策略去执行事务。这个过程中,我们所需要做的只是给需要事务的类或者方法标注@Transactional和配置其属性而已,并不是很复杂。

Spring中的事务控制API介绍

maven依赖:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.2.3.RELEASE</version>
</dependency>

如果使用springboot可以配合jpa、mybatis,或者导入jdbc-starter,都会包含:

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.1</version>
</dependency>

PlatformTransactionManager

此接口是 spring 的事务管理器,它里面提供了我们常用的操作事务的方法

public interface PlatformTransactionManager extends TransactionManager {
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;

    /*提交事务*/
    void commit(TransactionStatus status) throws TransactionException;
    /*回滚事务*/
    void rollback(TransactionStatus status) throws TransactionException;
}

我们在开发中都是使用它的实现类,对于Mybatis,我们用的就是org.springframework.jdbc.datasource.DataSourceTransactionManager

TransactionDefinition

它是事务的定义信息对象:

public interface TransactionDefinition {
    //传播行为
    int PROPAGATION_REQUIRED = 0;
    int PROPAGATION_SUPPORTS = 1;
    int PROPAGATION_MANDATORY = 2;
    int PROPAGATION_REQUIRES_NEW = 3;
    int PROPAGATION_NOT_SUPPORTED = 4;
    int PROPAGATION_NEVER = 5;
    int PROPAGATION_NESTED = 6;
    //事务隔离级别
    //默认级别,可能是四个隔离级别中的一种
    int ISOLATION_DEFAULT = -1;
    //读未提交
    int ISOLATION_READ_UNCOMMITTED = 1;
    //读已提交
    int ISOLATION_READ_COMMITTED = 2;
    //重复读
    int ISOLATION_REPEATABLE_READ = 4;
    //串行化
    int ISOLATION_SERIALIZABLE = 8;
    int TIMEOUT_DEFAULT = -1;

    //获取事务传播行为
    default int getPropagationBehavior() {
        return 0;
    }

    //获取事务隔离级别
    default int getIsolationLevel() {
        return -1;
    }

    //获取事务超时时间
    default int getTimeout() {
        return -1;
    }

    //获取事务是否只读
    default boolean isReadOnly() {
        return false;
    }

    //获取事务对象名称
    @Nullable
    default String getName() {
        return null;
    }

    static TransactionDefinition withDefaults() {
        return StaticTransactionDefinition.INSTANCE;
    }
}

具体事务的隔离级别介绍见:数据库理论之事务与恢复技术

对于事务的传播行为,有:

  • REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)
  • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
  • MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
  • REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
  • NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
  • NEVER:以非事务方式运行,如果当前存在事务,抛出异常
  • NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作。

TransactionStatus

public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
    //是否有存储点
    boolean hasSavepoint();

    //刷新事务
    @Override
    void flush();
}

对于TransactionExecution,还具有isNewTransaction():是不是新的事务,setRollbackOnly():设置事务回滚,isRollbackOnly():事务是否回滚等方法。

XML配置事务管理

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">


<!-- 省去DataSource配置 -->

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

<!-- 事务的配置 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
</tx:advice>

<tx:attributes>
    <!-- 指定方法名称:是业务核心方法
    read-only:是否是只读事务。默认 false,不只读。
    isolation:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。
    propagation:指定事务的传播行为。
    timeout:指定超时时间。默认值为:-1。永不超时。
    rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。
    没有默认值,任何异常都回滚。
    no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回
    滚。没有默认值,任何异常都回滚。
    -->
    <tx:method name="*" read-only="false" propagation="REQUIRED"/>
    <tx:method name="find*" read-only="true" propagation="SUPPORTS"/>
</tx:attributes>

<!-- 配置 aop -->
<aop:config>
<!-- 配置切入点表达式 -->
<aop:pointcut expression="execution(* com.rhett.service.impl.*.*(..))"
id="pt1"/>
</aop:config>

<!-- 在 aop:config 标签内部:建立事务的通知和切入点表达式的关系 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>

注解配置事务管理

  1. 首先注入事务管理器bean,可以通过注解@Bean,如果使用spring-boot,无论是mybatis-spring-boot-starter还是jpa-starter,都会自动帮我们注入一个相关的实例对象,我们只需要使用注解@Transactional即可。
  2. 在配置类上开启注解事务支持:@EnableTransactionManagement,当然也得开启@EnableAspectJAutoProxyAOP支持

  3. 在业务层使用@Transactional注解,属性和xml中的类似。

@Service("accountService")
@Transactional(readOnly=true,propagation=Propagation.SUPPORTS)
public class AccountServiceImpl implements IAccountService {

    @Autowired
    private IAccountDao accountDao;

    @Override
    public Account findAccountById(Integer id) {
        return accountDao.findAccountById(id);
    }

    @Override
    @Transactional(readOnly=false,propagation=Propagation.REQUIRED)
    public void transfer(String sourceName, String targeName, Float money) {
        //1.根据名称查询两个账户
        Account source = accountDao.findAccountByName(sourceName);
        Account target = accountDao.findAccountByName(targeName);
        //2.修改两个账户的金额
        source.setMoney(source.getMoney()-money);//转出账户减钱
        target.setMoney(target.getMoney()+money);//转入账户加钱
        //3.更新两个账户
        accountDao.updateAccount(source);
        //int i=1/0;
        accountDao.updateAccount(target);
    }
}

注意

因为Spring是通过AOP来实现管理事务的,如果某个类使用了@Transactional,但如果在这个类中的其他方法调用了该方法,称为"自调用",没有经过代理对象,这种情况@Transactional就失效了。

原创文章,作者:彭晨涛,如若转载,请注明出处:https://www.codetool.top/article/spring%e5%a3%b0%e6%98%8e%e5%bc%8f%e4%ba%8b%e5%8a%a1%e7%ae%a1%e7%90%86%e4%bb%8b%e7%bb%8d/