前言

在《数据库事务概论》、《MySQL事务学习笔记(一)》, 咱们曾经探讨过了事务相干概念,事务相干的操作,如何开始事务,回滚等。那在程序中咱们该如何做事务的操作呢。在Java中是通过JDBC API来管制事务,这种形式相对来说有点原始,古代 Java Web畛域个别都少不了Spring 框架的影子,Spring 为咱们提供了管制事务的简介计划,上面咱们就来介绍Spring 中如何管制事务的。 倡议在浏览本文之前先预读:

  • 《代理模式-AOP绪论》
  • 《欢迎光临Spring时代(二) 上柱国AOP列传》

申明式事务: @Transational 注解

简略应用示例

@Servicepublic class StudentServiceImpl implements StudentService{        @Autowired    private StudentInfoDao studentInfoDao;        @Transactional(rollbackFor = Exception.class) // 代表碰见任何Exception和其子类都会回滚    @Override    public void studyTransaction() {        studentInfoDao.updateById(new StudentInfo());    }}

这是Spring 为咱们提供的一种优雅管制事务的计划,但这里有个小坑就是如果办法的修饰符不是public,则@Transational就会生效。起因在于Spring通过TransactionInterceptor来拦挡有@Transactional注解的类和办法,

留神看TransactionInterceptor实现了MethodInterceptor接口,如果对Spring比拟相熟的话,能够直到这是一个盘绕告诉,在办法要执行的时候,咱们就能够加强这个类,在这个办法执行之前、执行之后,做些工作。

办法调用链如下:

得悉假相的我眼泪掉下来,我记得是我哪一次面试的时候,哪个面试官问我的,过后我是不晓得Spring的事务拦截器会有这样的操作的,因为我潜意识中是感觉,AOP的原理是动静代理,不论是啥办法,我都能代理。我看@Transational注解中的正文也没说,以为什么办法修饰符都能失效呢。

属性选讲

下面咱们只应用了@Transational的一个属性rollbackFor,这个属性用于管制办法在产生了什么异样的状况下回滚,当初咱们进@Transational简略的看下还有哪些属性:

  • value 和 transactionManager是同义语 用于指定事务管理器 4.2版本开始提供

数据库的事务在被Java畛域的框架管制,有不同的实现。比方Jdbc事务、Hibernate事务等

Spring进行了对立的形象,造成了PlatformTransactionManager、ReactiveTransactionManage两个事务管理器次顶级接口。两个类继承自TransactionManager.

咱们在用Spring整合的时候,如果是有连接池来治理连贯,Spring有DataSourceTransactionManager来治理事务。

如果你用的是Spring Data JPA,Spring Data Jpa还带了一个JpaTransationManager.

如果你应用的是Spring-boot-jdbc-starter,那么Spring Boot 会默认注入DataSourceTransactionManager,当做事务管理器。如果应用了spring-boot-starter-data-jpa, 那么Spring Boot默认会采纳 JpaTransactionManager。

  • label 5.3 开始提供

Defines zero (0) or more transaction labels.
Labels may be used to describe a transaction, and they can be evaluated by individual transaction managers. Labels may serve a solely descriptive purpose or map to pre-defined transaction manager-specific options.

定义一个事务标签,用来形容一些非凡的事务,来被一些事后定义的事务管理器非凡解决。

  • Propagation 流传行为

    • REQUIRED

      默认选项, 如果以后办法不存在事务则创立一个,如果以后办法存在事务则退出。
    • SUPPORTS

      反对以后事务,如果以后没有事务,就以非事务的形式来执行。
    • MANDATORY

      应用以后办法的事务,如果以后办法不存在事务,则抛出异样。
      @Transactional(propagation = Propagation.MANDATORY)@Overridepublic void studyTransaction() {    Student studentInfo = new Student();    studentInfo.setId(1);    studentInfo.setName("ddd");    studentInfoDao.updateById(studentInfo);}

      后果:

      <img src="https://tva3.sinaimg.cn/large/006e5UvNly1gzk1u5wbqhj314g03zq8d.jpg" alt="抛了一个异样" style="zoom:200%;" />

        @Transactional(rollbackFor = Exception.class)  @Override   public void testTransaction() {        studyTransaction(); // 这样就不会报错了   }
    • REQUIRES_NEW

      Create a new transaction, and suspend the current transaction if one exists. Analogous to the EJB transaction attribute of the same name.

      创立一个新的事务,如果以后曾经处于一个事务内,则挂起所属的事务,同EJB事务的属性有类似的名字。

      NOTE: Actual transaction suspension will not work out-of-the-box on all transaction managers. This in particular applies to org.springframework.transaction.jta.JtaTransactionManager, which requires the javax.transaction.TransactionManager to be made available to it (which is server-specific in standard Java EE).

      留神,不是所有的事务管理器都会抵赖此属性,挂起属性只被JtaTransactionManager事务管理器所抵赖。(也有对挂起的了解是先不提交, 期待其余事务的提交之后,再提交。我认为这个了解也是正确的。JtaTransactionManager是一个分布式事务管理器,)

      所以我实测,没有挂起景象。当初咱们来看看有没有开启一个新事务。

      SELECT TRX_ID FROM information_schema.INNODB_TRX  where TRX_MYSQL_THREAD_ID = CONNECTION_ID(); // 能够查看事务ID

      我在myBatis里做了测试,输入两个办法的事务ID:

        @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)   @Override    public void studyTransaction() {        // 先执行任意一句语句,不然不会有事务ID产生        studentInfoDao.selectById(1);        System.out.println(studentInfoDao.getTrxId());    }    @Transactional(rollbackFor = Exception.class)    @Override    public void testTransaction() {        // 先执行任意一句语句,不然不会有事务ID产生        studentInfoDao.selectById(1);        System.out.println(studentInfoDao.getTrxId());        studyTransaction();    }

      后果:

      网上其余博客大多都是会开启一个事务,当初看来并没有,然而网上看到有人做测试的时候,产生回滚了,测试方法的原理是testTransaction()执行更新数据库,studyTransaction也更新数据库,studyTransaction办法抛异样看是否回滚,咱们来用另一种测试,testTransaction更新,看studyTransaction中能不能查到,如果在一个事务中该当是能查到的。如果查不到更新那阐明就不再一个事务中。

      @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)@Overridepublic void studyTransaction() {    // 先执行任意一句语句,不然不会有事务ID产生    System.out.println(studentInfoDao.selectById(2).getNumber());    System.out.println(studentInfoDao.getTrxId());}@Transactional(rollbackFor = Exception.class)@Overridepublic void testTransaction() {    // 先执行任意一句语句,不然不会有事务ID产生    Student student = new Student();    student.setId(1);    student.setNumber("LLL");    studentInfoDao.updateById(student);    studyTransaction();}

      而后没输入LLL, 看来的确是新起了一个事务。

    • NOT_SUPPORTED

      Execute non-transactionally, suspend the current transaction if one exists. Analogous to EJB transaction attribute of the same name.
      NOTE: Actual transaction suspension will not work out-of-the-box on all transaction managers. This in particular applies to org.springframework.transaction.jta.JtaTransactionManager, which requires the javax.transaction.TransactionManager to be made available to it (which is server-specific in standard Java EE).

      以非事务的形式运行,如果以后办法存在事务则挂起。仅被JtaTransactionManager反对。

          @Transactional(propagation = Propagation.NOT_SUPPORTED,rollbackFor = Exception.class)    @Override    public void studyTransaction() {        // 先执行任意一句语句,不然不会有事务ID产生        System.out.println("studyTransaction办法的事务ID: "+studentInfoDao.getTrxId());    }    @Transactional(rollbackFor = Exception.class)    @Override    public void testTransaction() {        // 先执行任意一句语句,不然不会有事务ID产生        studentInfoDao.selectById(1);        System.out.println("testTransactiond的事务Id: "+studentInfoDao.getTrxId());        studyTransaction();    }

      验证后果:

      仿佛退出到了testTransaction中,没有以非事务的形式运行,我不死心,我要再试试。

        @Override    public void studyTransaction() {        // 先执行任意一句语句,不然不会有事务ID产生        Student student = new Student();        student.setId(1);        student.setNumber("cccc");        studentInfoDao.updateById(student);        // 如果是以非事务运行,那么办法执行完该当,别的办法该当立刻能查问到这条数据。        try {            TimeUnit.SECONDS.sleep(30);        } catch (InterruptedException e) {            e.printStackTrace();        }    }    @Transactional(rollbackFor = Exception.class)    @Override    public void testTransaction() {        // 先执行任意一句语句,不然不会有事务ID产生        System.out.println(studentInfoDao.selectById(1).getNumber());    }

      输入后果: testTransaction办法输入位cccc。 的确是以非事务形式在运行。

    • NEVER

      Execute non-transactionally, throw an exception if a transaction exists

      以非事务的形式运行,如果以后办法存在事务,则抛异样。

          @Transactional(propagation = Propagation.NEVER)    @Override    public void studyTransaction() {        System.out.println("hello world");    }    @Transactional(rollbackFor = Exception.class)    @Override    public void testTransaction() {        studyTransaction();    }

      没抛异样,难道是因为我没执更新语句? 我发现我外面写了更新语句也是一样的状况,原先在于这两个办法在一个类外面,在另一个接口实现类外面调studyTransaction办法, 像上面这样就会抛出异样:

      @Servicepublic class StuServiceImpl implements  StuService{    @Autowired    private StudentService studentService;        @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)    @Override    public void test() {        studentService.studyTransaction();    }}

      就会抛出如下的异样:

      那这是事务生效吗? 咱们对立放到下文事务的生效场景来探讨。

    • NESTED

      Execute within a nested transaction if a current transaction exists, behave like REQUIRED otherwise. There is no analogous feature in EJB.
      Note: Actual creation of a nested transaction will only work on specific transaction managers. Out of the box, this only applies to the JDBC DataSourceTransactionManager. Some JTA providers might support nested transactions as well.
      See Also:org.springframework.jdbc.datasource.DataSourceTransactionManager

      如果以后存在一个事务,当作该事务的子事务,同REQUIRED相似。留神,事实上这个个性仅被一些非凡的事务管理器所反对。在DataSourceTransactionManager能够做到开箱即用。

      那该怎么了解这个嵌套的子事务,还记得咱们《MySQL事务学习笔记(一) 初遇篇》提到的保留点吗? 这个NESTED就是保留点意思,假如 A办法调用B办法,A办法的流传行为是REQUIRED,B的办法时NESTED。A调用B,B产生了异样,只会回滚B办法的行为,A不受株连。

      @Transactional(propagation = Propagation.NESTED,rollbackFor = Exception.class)@Overridepublic void studyTransaction() {    Student student = new Student();    student.setId(1);    student.setNumber("bbbb");    studentInfoDao.updateById(student);    int i = 1 / 0;}@Transactional(rollbackFor = Exception.class)@Overridepublic void testTransaction() {    // 先执行任意一句语句,不然不会有事务ID产生    Student student = new Student();    student.setId(1);    student.setNumber("LLL");    studentInfoDao.updateById(student);    studyTransaction();}

      这样咱们会发现还是整个都回滚了,起因在于studyTransaction办法抛出的异样也被 testTransaction()所解决. 然而就是你catch住了会发现还是整个回滚,然而如果你在另一个service先后调用studyTransaction、testTransaction就能做到部分回滚。像上面这样:

      @Servicepublic class StuServiceImpl implements  StuService{    @Autowired    private StudentService studentService;    @Autowired    private StudentInfoDao studentInfoDao;    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)    @Override    public void test() {        Student student = new Student();        student.setId(1);        student.setNumber("jjjjj");        studentInfoDao.updateById(student);        try {            studentService.studyTransaction();        }catch (Exception e){        }    }

      或者在调用studyTransaction,本人注入本人也能起到部分回滚的成果,想上面这样:

      @Servicepublic class StudentServiceImpl implements StudentService, ApplicationContextAware {    @Autowired    private StudentInfoDao studentInfoDao;    @Autowired    private StudentService studentService        @Transactional(rollbackFor = Exception.class)    @Override    public void testTransaction() {        Student student = new Student();        student.setId(1);        student.setNumber("qqqq");        studentInfoDao.updateById(student);        try {            studentService.studyTransaction();        }catch (Exception e){        }    }  }
  • isolation 隔离级别

是一个枚举值, 咱们在《MySQL事务学习笔记(一) 初遇篇》曾经探讨过,能够通过此属性指定隔离级别,一共有四个:

  • DEFAULT 追随数据库的隔离级别
  • READ_UNCOMMITTED
  • READ_COMMITTED
  • REPEATABLE_READ
  • SERIALIZABLE
  • timeout 超时工夫
超过多长时间未提交,则主动回滚。
  • rollbackFor
  • rollbackForClassName
  • noRollbackFor
  • noRollbackForClassName
 @Transactional(noRollbackForClassName = "ArithmeticException",rollbackFor = ArithmeticException.class )   @Override    public void studyTransaction() {        Student studentInfo = new Student();        studentInfo.setId(1);        studentInfo.setName("ddd");        studentInfoDao.updateById(studentInfo);        int i = 1 / 0;    }

noRollback和RollbackFor指定雷同的类,优先走RollbackFor。

事务生效场景

下面事实上咱们曾经探讨了一种事务的生效场景,即办法被润饰的办法是private的。如果想要对private办法级别失效,则须要开启AspectJ 代理模式。开启也比拟麻烦,知乎搜寻: Spring Boot教程(20) – 用AspectJ实现AOP外部调用 , 外面讲如何开启,这里就不再赘述了。

再有就是在事务流传行为中设置为NOT_SUPPORTED。

下面咱们在探讨的事务管理器,如果事务管理器没有被纳入到Spring的管辖范畴之内,那么办法有@Transactional也不会失效。

类中办法自调用,像上面这样:

 @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)    @Override    public void studyTransaction() {        Student student = new Student();        student.setId(1);        student.setNumber("aaaaaaLLL");        studentInfoDao.updateById(student);        int i = 1 / 0;    }  @Override  public void testTransaction() {        studyTransaction(); }

这样还是不会产生回滚。起因还是才从代理模式说起,咱们之所以在办法和类上加事务注解就能实现对事务的治理,实质上还是Spring再帮咱们做加强,咱们在调用办法上有@Transactional的办法上时,事实上调用的是代理类,像没有事务注解的办法,Spring去调用的时候就没有用代理类。如果是有事务注解的办法调用没事务注解的办法,也不会生效,起因是一样的,调用被@Transactional事实上调用的是代理类,开启了事务。

  • 对应的数据库未开启反对事务,比方在MySQL中就是数据库的表指定的引擎MyIsam。
  • 打上事务的注解没有应用一个数据库连贯,也就是多线程调用。像上面这样:
   @Override    @Transactional    public void studyTransaction() {       // 两个线程可能应用不同的连贯,相似于MySQL开了两个黑窗口,天然相互不影响。        new Thread(()-> studentInfoDao.insert(new Student())).start();        new Thread(()-> studentInfoDao.insert(new Student())).start();    }

编程式事务简介

与申明式事务相同,申明式事务像是自动挡,由Spring帮忙咱们开启、提交、回滚事务。而编程式事务则像是自动挡,咱们本人开启、提交、回滚。如果有一些代码块须要用到事务,在办法上加事务显得太过轻便,不再办法上加,在抽出来的办法上加又会导致生效,那么咱们这里就能够思考应用编程式事务.Spring 框架下提供了两种编程式事务管理:

  • TransactionTemplate(Spring 会主动帮咱们回滚开释资源)
  • PlatformTransactionManager(须要咱们手动开释资源)
@Servicepublic class StudentServiceImpl implements StudentService{    @Autowired    private StudentInfoDao studentInfoDao;    @Autowired    private TransactionTemplate transactionTemplate;    @Autowired    private PlatformTransactionManager platformTransactionManager;    @Override    public void studyTransaction() {        // transactionTemplate能够设置隔离级别、流传行为等属性        String result = transactionTemplate.execute(status -> {            testUpdate();            return "AAA";        });        System.out.println(result);    }    @Override    public void testTransaction() {        // defaultTransactionDefinition   能够设置隔离级别、流传行为等属性        DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition();        TransactionStatus status = platformTransactionManager.getTransaction(defaultTransactionDefinition);        try {            testUpdate();        }catch (Exception e){            // 指定回滚            platformTransactionManager.rollback(status);        }        studyTransaction();// 提交        platformTransactionManager.commit(status);    }    private void testUpdate() {        Student student = new Student();        student.setId(1);        student.setNumber("aaaaaaLLL111111qqqw");        studentInfoDao.updateById(student);        int i = 1 / 0;    }}

总结一下

Spring 为咱们对立治理了事务,Spring提供的治理事务的形式大抵上能够分为两种:

  • 申明式事务 @Transactional
  • 编程式事务 TransactionTemplate 和 PlatformTransactionManager

如果你想享受Spring的提供的事务管理遍历,那么前提须要你将事务管理器纳入到容器的治理范畴。

参考资料

  • 详解Spring的事务管理PlatformTransactionManager https://www.jianshu.com/p/903...
  • 解惑 spring 嵌套事务 https://blog.csdn.net/z691837...
  • Spring事务在哪几种状况下会生效? https://www.zhihu.com/questio...
  • Spring事务在哪几种状况下会生效? https://www.zhihu.com/questio...
  • Spring编程式事务管理 https://zhuanlan.zhihu.com/p/...