Spring事务 Transaction

一:Spring事务定义,添加位置

 

  1)事务:一整套操作,要么一起成功,要么一起失败。

  2)@Transaction在 Spring中添加的位置:

    类上:对类上所有的public方法配置事务

    方法上:对public方法配置事务,方法上的@Transaction会覆盖类上的@Transaction

  3)@Transaction Spring声明事务,通过Cglib,子类继承实现;

    a) 涉及开启事务(begin/start transaction),提交事务(commit),出现RuntimeException/Error回滚事务(rollback);

      b) MySQL默认自动提交事务;

     c)Spring中,connection对象管理事务,在开启事务时,connection对象调用setAutoCommit(false);

     connection.commit()才提交事务 

 

二:事务四大特性ACID

  原子性(Atomicity)

    事务是一个原子操作,由一系列动作组成。要么全部完成,要么完全不起作用。

  一致性(Consistency)

    一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。

  隔离性(Isolation)

    可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。

  持久性(Durability)

    一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,事务的结果被写到持久化磁盘中。

  参考:https://blog.csdn.net/Rex_WUST/article/details/95106798?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.edu_weight&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.edu_weight

 

三:事务传播行为

  定义:主要场景:一个方法调用另一个方法,两个方法都可以由事务控制,可以指定方法上事务行为

传播行为 含义
Propogation.REQUIRED 如果当前方法没有事务,新建一个事务,如果已经存在事务中,加入这个事务中
Propogation.SUPPORTS 支持当前事务,如果没有当前事务,就以非事务方法执行
Propogation.MANDATORY 使用当前事务,如果没有当前事务,就抛出异常
Propogation.REQUIRES_NEW 新建事务,如果存在当前事务,把当前事务挂起
Propogation.NOT_SUPPORTED 以非事务方式执行操作,如果以前存在事务,把当前事务挂起
Propogation.NEVER 以非事务方式执行操作,如果当前事务存在,则抛出异常
Propogation.NESTED 如果当前事务存在,则在嵌套事务内执行,如果当前没有事务,则执行REQUIRED类似操作

举例:

  完全参考 https://blog.csdn.net/zhangsweet1991/article/details/83897211

  1. MySQL建表:

CREATE TABLE account(
    userid VARCHAR(64) NOT NULL,
    username VARCHAR(64) NOT NULL,
    accountbalance DECIMAL(10,2) NOT NULL DEFAULT '0.00',
    createtime DATETIME DEFAULT NULL,
    updatetime DATETIME DEFAULT NULL,
    PRIMARY KEY(userid)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

INSERT INTO account VALUES ('1001','张三','1000.00','2018-11-09 09:00:00','2018-11-09 11:00:00');
INSERT INTO account VALUES ('1002','李四','1000.00','2018-11-09 09:02:00','2018-11-09 11:02:00');

   2. Springboot搭建工程 

POM  

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.6.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--mybatis 启动器-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>
        <!--单元测试-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>

启动类

@MapperScan("com.example.dao")
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

yml文件

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db1
    username: root
    password: root

mybatis:
  type-aliases-package: com.example.domain
  # log configuration
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  

service层:

public interface TestServiceA {
    public void dowork(Boolean isExce,String userId,double accountBalance);
}
public interface TestServiceB {
    public void dowork(Boolean isExce, String userId, double accountBalance);
}

serviceImpl 

@Service
public class TestServiceAImpl implements TestServiceA {

    @Resource
    private TestDao testDao;
    @Resource
    private TestServiceB testServiceB;
    
    // A调用B,对B的7中事务级别进行测试
    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public void dowork(Boolean isExce,String userId,double accountBalance) {

        System.out.println("进入方法doWorkA");
        testDao.doWork(userId,accountBalance);

        testServiceB.dowork(isExce,userId,accountBalance);

        testDao.doWork(userId,-accountBalance);
        System.out.println("方法workA执行结束");
    }
}
@Service
public class TestServiceBImpl implements TestServiceB {

    @Resource
    private TestDao testDao;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public void dowork(Boolean isExce,String userId,double accountBalance) {
        System.out.println("进入方法doWorkB");

        testDao.doWork(userId,accountBalance);
        if(isExce) {
            throw new RuntimeException();
        }
        testDao.doWork(userId,-accountBalance);
        System.out.println("方法workB执行结束");
    }
}

dao层

public interface TestDao {
    @Update("update account set accountBalance =accountBalance + #{accountBalance} where userid = #{userId}")
    public void doWork(String userId,double accountBalance);
}

单元测试 

@RunWith(SpringRunner.class)
@SpringBootTest
public class TestServiceTest {
    // 先不用controller,用测试类测试
    @Resource
    private TestServiceA testServiceA;
    
    @Test
    public void doWork() {
        try {
            testServiceA.dowork(true,"1001",200);
        } catch (Exception e) {
            System.out.println("异常" + e.getClass().getName());
        }
        System.out.println("success");
    }
}

测试传播行为:

1)

 

 

每次测试前:表的数组都相同,如下图

         

 结果:注 isExce=true,每次SQL 执行过程 对id='1001'用户的accountBalance+200

  即serviceB的doWork存在事务,serviceB的dowork执行抛出RuntimeException,回滚;但serviceA没有事务管理

  执行完默认提交;

  

2)

 

 

 测试结果:注 isExce=true,每次SQL 执行过程 对id='1001'用户的accountBalance+200

  

 ServiceA 和 ServiceB 的doWork方法都由事务管理,都会回滚;

3)

 

 测试结果:注 isExce=true,SQL 执行过程 对id='1001'用户的accountBalance+200

  

 ServiceA 和 ServiceB 的doWork方法都由没有事务管理,默认直接提交增删改;

4)

 

测试结果:注 isExce=true,SQL 执行过程 对id='1001'用户的accountBalance+200

  

   A事务对dowork方法事务管理,都回滚;

 5)

 

测试结果: 注 isExce=true,SQL 执行过程 对id='1001'用户的accountBalance+200

       

serviceA没有事务,执行SQL直接提交,所以为1200;因为没有事务管理,serviceB 由于@MANDATORY

     抛出IllegalTransactionStateException

6)

 

 测试结果: 注 isExce=true,SQL 执行过程 对id='1001'用户的accountBalance+200

        

   都是ServiceA事务,都回滚;

其他情况

  略

四:事务隔离级别

  定义:多个事务之间相互隔离,如果多个事务操作同一批数据,可能会引发问题,需要设置事务隔离级别;

  存在问题:

    1. 脏读:一个事务读取到另一个事务没有提交之前的数据库中的数据

    2. 不可重复读:同一个事务两次读取数据不一样,有其他并发事务进行了更新

    3. 幻读:重点在于新增/删除,第1次读和第2次读的记录数不一样,有其他并发事务进行了新增/删除

  隔离级别:

    1)read uncommitted:读未提交

      可能产生脏读,幻读,不可重复读

    2)read committed

      可能产生 幻读,不可重复读

    3)repeatable read:可重复读(mysql默认,mysql有锁,不会幻读)

      可能产生 幻读

    4)serializable   串行化

      对一个事务加锁,互斥

五:Spring事务回滚规则

  1)默认配置,只有在抛出RuntimeException/Error时,才会回滚事务,而 checked异常则不会导致事务回滚,会被catch捕获;

  2)事务方法,内部调用本来内部的其他方法,不会引起事务,因为此时的调用对象是this,而@Transaction 通过AOP,代理对象来调用时才会被事务管理

六:只读

  @Transaction(readOnly = true/ false)如果事务只读,数据库会利用事务只读进行一些特定的优化;

七:事务超时

  默认@Transaction(TransactionDefinition.TIMEOUT_DEFAULT),跟数据库相同;

  事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。

posted @ 2020-09-26 15:53  dlkla_jianchi  阅读(117)  评论(0)    收藏  举报