Spring事务详解

Posted 林在闪闪发光

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring事务详解相关的知识,希望对你有一定的参考价值。

 

🏆今日学习目标:

🍀Spring事务详解
创作者:林在闪闪发光
⏰预计时间:30分钟
🎉个人主页:林在闪闪发光的个人主页

 🍁林在闪闪发光的个人社区,欢迎你的加入: 林在闪闪发光的社区

目录

事务简介

Spring的事务支持

编程式事务

声明式事务

事务管理模型

事务传播行为

PROPAGATION_REQUIRED

PROPAGATION_REQUIRES_NEW

PROPAGATION_NESTED

PROPAGATION_SUPPORTS

PROPAGATION_NOT_SUPPORTED

PROPAGATION_MANDATORY

PROPAGATION_NEVER

事务隔离级别

事务的超时时间

事务的只读属性

事务的回滚策略

事务的不回滚策略

@Transaction失效场景


射箭如果没有箭靶子那也就没有了拉弓的意义 

事务简介

事务在逻辑上是一组操作,要么执行,要不都不执行。主要是针对数据库而言的,比如说 mysql
为了保证事务是正确可靠的,在数据库进行写入或者更新操作时,就必须得表现出 ACID 的 4 个重要特性:

  • 原子性(Atomicity):一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
  • 一致性(Consistency):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。
  • 事务隔离(Isolation):数据库允许多个并发事务同时对其数据进行读写和修改,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
  • 持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

其中,事务隔离又分为 4 种不同的级别,包括:

  • 未提交读(Read uncommitted),最低的隔离级别,允许“脏读”(dirty reads),事务可以看到其他事务“尚未提交”的修改。如果另一个事务回滚,那么当前事务读到的数据就是脏数据。
  • 提交读(read committed),一个事务可能会遇到不可重复读(Non Repeatable Read)的问题。不可重复读是指,在一个事务内,多次读同一数据,在这个事务还没有结束时,如果另一个事务恰好修改了这个数据,那么,在第一个事务中,两次读取的数据就可能不一致。
  • 可重复读(repeatable read),一个事务可能会遇到幻读(Phantom Read)的问题。幻读是指,在一个事务中,第一次查询某条记录,发现没有,但是,当试图更新这条不存在的记录时,竟然能成功,并且,再次读取同一条记录,它就神奇地出现了。
  • 串行化(Serializable),最严格的隔离级别,所有事务按照次序依次执行,因此,脏读、不可重复读、幻读都不会出现。虽然 Serializable 隔离级别下的事务具有最高的安全性,但是,由于事务是串行执行,所以效率会大大下降,应用程序的性能会急剧降低。如果没有特别重要的情景,一般都不会使用 Serializable 隔离级别。

需要格外注意的是:事务能否生效,取决于数据库引擎是否支持事务,MySQL 的 InnoDB 引擎是支持事务的,但 MyISAM 就不支持。

 

Spring的事务支持

Spring 支持两种事务方式,分别是编程式事务和声明式事务,后者最常见,通常情况下只需要一个 **@Transactional **就搞定了(代码侵入性降到了最低),就像这样:

/**
 * 模拟转账
 */
@Transactional
public void handle() 
 // 转账
 transfer(double money);
 // 减自己的钱
  Reduce(double money);

编程式事务

编程式事务是指将事务管理代码嵌入嵌入到业务代码中,来控制事务的提交和回滚。
你比如说,使用 TransactionTemplate 来管理事务:

@Autowired
private TransactionTemplate transactionTemplate;
public void testTransaction() 

        transactionTemplate.execute(new TransactionCallbackWithoutResult() 
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) 

                try 

                    // ....  业务代码
                 catch (Exception e)
                    //回滚
                    transactionStatus.setRollbackOnly();
                

            
        );


再比如说,使用 TransactionManager 来管理事务:

@Autowired
private PlatformTransactionManager transactionManager;

public void testTransaction() 

  TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
          try 
               // ....  业务代码
              transactionManager.commit(status);
           catch (Exception e) 
              transactionManager.rollback(status);
          

就编程式事务管理而言,Spring 更推荐使用 TransactionTemplate。
在编程式事务中,必须在每个业务操作中包含额外的事务管理代码,就导致代码看起来非常的臃肿,但对理解 Spring 的事务管理模型非常有帮助。

声明式事务

声明式事务将事务管理代码从业务方法中抽离了出来,以声明式的方式来实现事务管理,对于开发者来说,声明式事务显然比编程式事务更易用、更好用。
当然了,要想实现事务管理和业务代码的抽离,就必须得用到 Spring 当中的AOP,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。

声明式事务虽然优于编程式事务,但也有不足,声明式事务管理的粒度是方法级别,而编程式事务是可以精确到代码块级别的。

事务管理模型

Spring 将事务管理的核心抽象为一个事务管理器(TransactionManager),它的源码只有一个简单的接口定义,属于一个标记接口:

public interface TransactionManager 


该接口有两个子接口,分别是编程式事务接口 ReactiveTransactionManager 和声明式事务接口 PlatformTransactionManager。我们来重点说说 PlatformTransactionManager,该接口定义了 3 个接口方法:

interface PlatformTransactionManager extends TransactionManager
    // 根据事务定义获取事务状态
    TransactionStatus getTransaction(TransactionDefinition definition)
            throws TransactionException;

    // 提交事务
    void commit(TransactionStatus status) throws TransactionException;

    // 事务回滚
    void rollback(TransactionStatus status) throws TransactionException;

通过 PlatformTransactionManager 这个接口,Spring 为各个平台如 JDBC(DataSourceTransactionManager)、Hibernate(HibernateTransactionManager)、JPA(JpaTransactionManager)等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。
参数 TransactionDefinition 和 @Transactional 注解是对应的,比如说 @Transactional 注解中定义的事务传播行为、隔离级别、事务超时时间、事务是否只读等属性,在 TransactionDefinition 都可以找得到。
返回类型 TransactionStatus 主要用来存储当前事务的一些状态和数据,比如说事务资源(connection)、回滚状态等。
TransactionDefinition如下:

public interface TransactionDefinition 

 // 事务的传播行为
 default int getPropagationBehavior() 
  return PROPAGATION_REQUIRED;
 

 // 事务的隔离级别
 default int getIsolationLevel() 
  return ISOLATION_DEFAULT;
 

  // 事务超时时间
  default int getTimeout() 
  return TIMEOUT_DEFAULT;
 

  // 事务是否只读
  default boolean isReadOnly() 
  return false;
 

 

Transactional注解如下:

@Target(ElementType.TYPE, ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional 

 Propagation propagation() default Propagation.REQUIRED;
 Isolation isolation() default Isolation.DEFAULT;
  int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
  boolean readOnly() default false;


 

@Transactional 注解中的 propagation 对应 TransactionDefinition 中的 getPropagationBehavior,默认值为 Propagation.REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED)。
@Transactional 注解中的 isolation 对应 TransactionDefinition 中的 getIsolationLevel,默认值为 DEFAULT(TransactionDefinition.ISOLATION_DEFAULT)。
@Transactional 注解中的 timeout 对应 TransactionDefinition 中的 getTimeout,默认值为TransactionDefinition.TIMEOUT_DEFAULT。
@Transactional 注解中的 readOnly 对应 TransactionDefinition 中的 isReadOnly,默认值为 false。

说到这,我们来详细地说明一下 Spring 事务的传播行为、事务的隔离级别、事务的超时时间、事务的只读属性,以及事务的回滚规则。

事务传播行为

当事务方法被另外一个事务方法调用时,必须指定事务应该如何传播,例如,方法可能继续在当前事务中执行,也可以开启一个新的事务,在自己的事务中执行。
声明式事务的传播行为可以通过 @Transactional 注解中的 propagation 属性来定义,比如说:

@Transactional(propagation = Propagation.REQUIRED)
public void savePosts(PostsParam postsParam) 

TransactionDefinition 一共定义了 7 种事务传播行为,其中PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW 两种传播行为是比较常用的。

PROPAGATION_REQUIRED

这也是 @Transactional 默认的事务传播行为,指的是如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。更确切地意思是:
如果外部方法没有开启事务的话,Propagation.REQUIRED 修饰的内部方法会开启自己的事务,且开启的事务相互独立,互不干扰。
如果外部方法开启事务并且是 Propagation.REQUIRED 的话,所有 Propagation.REQUIRED 修饰的内部方法和外部方法均属于同一事务 ,只要一个方法回滚,整个事务都需要回滚。

也就是说如果a方法和b方法都添加了注解,在默认传播模式下,a方法内部调用b方法,会把两个方法的事务合并为一个事务。

PROPAGATION_REQUIRES_NEW

创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部方法都会开启自己的事务,且开启的事务与外部的事务相互独立,互不干扰。

当类A中的 a 方法用默认 Propagation.REQUIRED模式,类B中的 b方法加上采用 Propagation.REQUIRES_NEW模式,然后在 a 方法中调用 b方法操作数据库,然而 a方法抛出异常后,b方法并没有进行回滚,因为Propagation.REQUIRES_NEW会暂停 a方法的事务 ,总结就是a不影响b,b影响a

PROPAGATION_NESTED

如果当前存在事务,就在当前事务内执行;否则,就执行与 PROPAGATION_REQUIRED 类似的操作。

当类A中的 a 方法用默认 Propagation.REQUIRED模式,类B中的 b方法加上采用 Propagation.NESTED模式,然后在 在a 方法里调用 b方法操作数据库,然而 b方法抛出异常后,a方法是不的回滚 ,总结就是b不影响a,a影响b。

PROPAGATION_SUPPORTS

如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

PROPAGATION_NOT_SUPPORTED

以非事务方式运行,如果当前存在事务,则把当前事务挂起。

PROPAGATION_MANDATORY

如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

PROPAGATION_NEVER

以非事务方式运行,如果当前存在事务,则抛出异常。

事务隔离级别

前面我们已经了解了数据库的事务隔离级别,再来理解 Spring 的事务隔离级别就容易多了。
TransactionDefinition 中一共定义了 5 种事务隔离级别:

  • ISOLATION_DEFAULT,使用数据库默认的隔离级别,MySql 默认采用的是 REPEATABLE_READ,也就是可重复读。
  • ISOLATION_READ_UNCOMMITTED,最低的隔离级别,可能会出现脏读、幻读或者不可重复读。
  • ISOLATION_READ_COMMITTED,允许读取并发事务提交的数据,可以防止脏读,但幻读和不可重复读仍然有可能发生。
  • ISOLATION_REPEATABLE_READ,对同一字段的多次读取结果都是一致的,除非数据是被自身事务所修改的,可以阻止脏读和不可重复读,但幻读仍有可能发生。
  • ISOLATION_SERIALIZABLE,最高的隔离级别,虽然可以阻止脏读、幻读和不可重复读,但会严重影响程序性能。

通常情况下,我们采用默认的隔离级别 ISOLATION_DEFAULT 就可以了,也就是交给数据库来决定。

事务的超时时间

事务超时**timeout **,也就是指一个事务所允许执行的最长时间,如果在超时时间内还没有完成的话,就自动回滚。
假如事务的执行时间格外的长,由于事务涉及到对数据库的锁定,就会导致长时间运行的事务占用数据库资源。

事务的只读属性

事务的只读属性readOnly, 如果一个事务只是对数据库执行读操作,那么该数据库就可以利用事务的只读属性,采取优化措施,适用于多条数据库查询操作中。
为什么一个查询操作还要启用事务支持呢?
这是因为 MySql(innodb)默认对每一个连接都启用了 autocommit 模式,在该模式下,每一个发送到 MySql 服务器的 SQL 语句都会在一个单独的事务中进行处理,执行结束后会自动提交事务。
那如果我们给方法加上了 @Transactional 注解,那这个方法中所有的 SQL 都会放在一个事务里。否则,每条 SQL 都会单独开启一个事务,中间被其他事务修改了数据,都会实时读取到。
有些情况下,当一次执行多条查询语句时,需要保证数据一致性时,就需要启用事务支持。否则上一条 SQL 查询后,被其他用户改变了数据,那么下一个 SQL 查询可能就会出现不一致的状态。

事务的回滚策略

**回滚策略rollbackFor **,用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。默认情况下,事务只在出现运行时异常(Runtime Exception)时回滚,以及 Error,出现检查异常(checked exception,需要主动捕获处理或者向上抛出)时不回滚。

如果你想要回滚特定的异常类型的话,可以这样设置:

@Transactional(rollbackFor= MyException.class)

事务的不回滚策略

**不回滚策略noRollbackFor **,用于指定不触发事务回滚的异常类型,可以指定多个异常类型。

@Transaction失效场景

  • 作用于非public方法上,之所以会失效是因为在Spring AOP 代理时,如下图所示 TransactionInterceptor (事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute

方法,获取Transactional 注解的事务配置信息。
此方法会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息。

 

注意:protected、private修饰的方法上使用 @Transactional 注解,虽然事务无效,但不会有任何报错,这是我们很容犯错的一点。

  • propagation设置问题,会导致事务不生效,也就事务不会回滚
  • rollbackFor指定事务回滚的异常类型
  • 同个类中的调用被@transaction修饰的方法,会失效,因为只有当事务方法被当前类以外的代码调用,才会由spring生成的代理对象来管理。
  • try catch导致失效

 

Spring 事务快速入门详解

文章目录

Spring事务简介

什么是事务?

事务指数据库中多个操作合并在一起形成的操作序列

事务特征(ACID)

事务有四个特性:

  • 原子性(Atomicity)指事务是一个不可分割的整体,其中的操作要么全执行或全不执行

  • 一致性(Consistency)事务前后数据的完整性必须保持一致

  • 隔离性(Isolation)事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离

  • 持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响

Spring事务的作用

  • 事务作用:在数据层保障一系列的数据库操作同成功同失败
  • Spring事务作用:在数据层或**业务层**保障一系列的数据库操作同成功同失败

当数据库操作序列中个别操作失败时,提供一种方式使数据库状态恢复到正常状态(A),保障数据库即使在异常状态下仍能保持数据一致性(C)(要么操作前状态,要么操作后状态)。

当出现并发访问数据库时,在多个访问间进行相互隔离,防止并发访问操作结果互相干扰(I),从而最终保持数据的持久性(D)。

Spring事务核心接口

在Spring中,数据层的Mybatis使用的是DataSourceTransactionManager实现类来进行事务控制,它实现的是Spring中的事务管理的核心接口PlatformTransactionManager

Spring事务角色

你可以看完案例再来看这个事务角色的概念

  • 事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法
  • 事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法

例如下面事务流程案例:业务层的方法就充当事务管理员,负责开启事务,调用的两个DAO方法加入事务,是业务协调员

Spring事务实战分析

需求和分析

需求:实现任意两个账户间转账操作

例如:A账户像B账户转账,那么就是A账户减钱,B账户加钱

分析:

  1. 数据层提供基础操作,指定账户减钱(outMoney),指定账户加钱(inMoney)
  2. 业务层提供转账操作(transfer),调用减钱与加钱的操作
  3. 提供2个账号和操作金额执行转账操作
  4. 基于Spring整合MyBatis环境搭建上述操作

结果分析:

  1. 程序正常执行时,账户金额A减B加,没有问题
  2. 程序出现异常后,转账失败,但是异常之前操作成功,异常之后操作失败,整体业务失败

代码实现

测试环境准备

项目结构如下:

创建一个数据库:transaction_test,建一个简单的测试表

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for tb_account
-- ----------------------------
DROP TABLE IF EXISTS `tb_account`;
CREATE TABLE `tb_account`  (
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
  `money` DOUBLE NULL DEFAULT NULL
) ENGINE = MyISAM CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of tb_account
-- ----------------------------
INSERT INTO `tb_account` VALUES ('Tong', 1000);
INSERT INTO `tb_account` VALUES ('Meng', 200);

SET FOREIGN_KEY_CHECKS = 1;

Spring整合Mybatis相关代码(依赖、JdbcConfig、MybatisConfig、SpringConfig)

相关依赖

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.yyl</groupId>
  <artifactId>spring_24_case_transfer</artifactId>
  <version>1.0-SNAPSHOT</version>

  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.16</version>
    </dependency>

    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.6</version>
    </dependency>

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.47</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.0</version>
    </dependency>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>

  </dependencies>
</project>

jdbc配置文件

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/transaction_test?useSSL=false
jdbc.username=root
jdbc.password=root

配置类

// JdbcConfig 
package com.yyl.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;


public class JdbcConfig 
    @Value("$jdbc.driver")
    private String driver;
    @Value("$jdbc.url")
    private String url;
    @Value("$jdbc.username")
    private String userName;
    @Value("$jdbc.password")
    private String password;

    @Bean
    public DataSource dataSource()
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(userName);
        ds.setPassword(password);
        return ds;
    

package com.yyl.config;

import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;

import javax.sql.DataSource;

public class MybatisConfig 

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource)
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        ssfb.setTypeAliasesPackage("com.yyl.domain");
        ssfb.setDataSource(dataSource);
        return ssfb;
    

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer()
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("com.yyl.dao");
        return msc;
    

package com.yyl.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@ComponentScan("com.yyl")
@PropertySource("classpath:jdbc.properties")
@Import(JdbcConfig.class,MybatisConfig.class)
public class SpringConfig 

这里我们先不开启事务

编写DAO层

public interface AccountDao 

    @Update("update tb_account set money = money + #money where name = #name")
    void inMoney(@Param("name") String name, @Param("money") Double money);

    @Update("update tb_account set money = money - #money where name = #name")
    void outMoney(@Param("name") String name, @Param("money") Double money);


public interface AccountService 
    /**
     * 转账操作
     * @param out 传出方
     * @param in 转入方
     * @param money 金额
     */
    public void transfer(String out,String in ,Double money) ;


@Service
public class AccountServiceImpl implements AccountService 
    @Autowired
    private AccountDao accountDao;

    public void transfer(String out,String in ,Double money) 
        accountDao.outMoney(out,money);
        int i = 1/0;
        accountDao.inMoney(in,money);
    

此时运行结果,Tong的钱少了200,Meng的钱没加,因为执行到除0操作,程序挂掉了

上面的实现应用到现实中肯定是不行的,那么下面我们引入事务再来改善一下代码

使用Spring事务管理修改案例

在业务层接口上添加Spring事务管理

public interface AccountService 
    //配置当前接口方法具有事务
    @Transactional
    public void transfer(String out,String in ,Double money) ;

注意事项

  1. Spring注解式事务通常添加在业务层接口中而不会添加到业务层实现类中,降低耦合
  2. 注解式事务可以添加到业务方法上表示当前方法开启事务,也可以添加到接口上表示当前接口所有方法开启事务

设置事务管理器(将事务管理器添加到IOC容器中)

说明:可以在JdbcConfig中配置事务管理器

//配置事务管理器,mybatis使用的是jdbc事务
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource)
    DataSourceTransactionManager dtm = new DataSourceTransactionManager();
    transactionManager.setDataSource(dataSource);
    return transactionManager;

注意事项

  1. 事务管理器要根据实现技术进行选择
  2. MyBatis框架使用的是JDBC事务

开启注解式事务驱动

@Configuration
@ComponentScan("com.yyl")
@PropertySource("classpath:jdbc.properties")
@Import(JdbcConfig.class,MybatisConfig.class)
//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig 

运行测试类,查看结果

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest 

    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer() throws IOException 
        accountService.transfer("Tong","Meng",100D);
    

运行结果如下:即使报错也不会发生数据库误操作,因为进行了事务回滚操作

去掉除0操作,再运行结果如下:一个减钱,一个价钱,没有问题

还有一个问题

问题1:Spring提供的事务管理是数据层的事务还是业务层的事务?

首先那肯定是业务层。Spring事务为业务逻辑进行事务管理,保证业务逻辑上数据的原子性

扩展:事务分类

事务得根据项目性质来细分:事务可以设置到三个层面(dao层、service层和web层)。

第一:web层事务,这一般是针对那些安全性要求较高的系统来说的。例如电子商务网站。粒度小,一般系统用不着这么细。
第二:service层事务,这是一常见的事务划分, 将事务设置在业务逻辑上,只要业务逻辑出错或异常就事务回滚。粒度较小,一般推荐这种方式。
第三:数据持久层数据务,也就是常说的数据库事务。这种事务在安全性方面要求低。就是给一个简单的增删改之类的操作增加事务操作。粒度大

问题2:为什么只用服务层的事务?

给Service层配置事务,因为一个Service层方法操作可以关联到多个DAO的操作。在Service层执行这些Dao操作,多DAO操作有失败全部回滚,成功则全部提交。

事务分为业务事务和系统事务,业务事务也就是业务逻辑上操作的一致性,系统事务自然就是指真正的数据库事务,

Dao层是数据访问层,是不应该包含业务逻辑的,这就是和Service层的不同;
Service层就是业务逻辑层,事务的管理就是为Service层上的保证。

以上是关于Spring事务详解的主要内容,如果未能解决你的问题,请参考以下文章

Spring 事务快速入门详解

Spring事务管理(详解+实例)

Spring中的事务传播属性详解

spring事务详解

Spring事务管理(详解+实例)

Spring 事务机制详解