关于Spring事务的一些理论和实践

一、基础知识

1、Transational注释

特性

  • 可以在类(接口)或方法上标注

    • 标注在类上:类中所有方法都进行事务处理
    • 标注在接口、实现类的方法上:方法进行事务处理
  • 优先级:方法注解>类注解

属性

  • progation:事务传播机制
事务传播机制 特性
REQUIRED 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务
SUPPORTS 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。
MANDATORY 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常
REQUIRES_NEW 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起
NOT_SUPPORTED 总是非事务地执行,并挂起任何存在的事务
NEVER 总是非事务地执行,如果存在一个活动事务,则抛出异常
NESTED 如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行
  • isolation:事务隔离级别
DEFAULT 使用后端数据库默认的隔离级别

其他四个隔离级别对应着数据库的四个隔离

  • timeout:事务超时时间,单位是秒。是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。默认值是当前数据库默认事务过期时间。

  • readOnly:事务是否是只读的,默认是 false。对于只读查询,可以指定事务类型为 readonly,即只读事务。由于只读事务不存在数据的修改, 因此数据库将会为只读事务提供一些优化手段

  • rollbackFor:设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则事务回滚

  • noRollbackFor:设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚

  • rollbackForClassName:设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。

  • noRollbackForClassName:设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。

使用@Transactional需要注意的地方

  • @Transactional 只能应用到 public 方法才有效
  • 在默认配置中,Spring FrameWork 的事务框架代码只会将出现 runtime, unchecked 异常的事务标记为回滚;也就是说事务中抛出的异常是 RuntimeException 或其子类,这样事务才会回滚(默认情况下 Error 也会导致事务回滚)。但是,在默认配置的情况下,所有的 checked 异常都不会引起事务回滚。

二、实践

前期准备

1、创建spring项目

新建Spring Initializr项目

创建项目.JPG 配置pom.xml文件

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.12</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>
复制代码

创建entity包,创建对应数据库表的实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "extra_ad")
public class ExtraAd {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name ="id",nullable = false)
    private Long id;

    @Basic
    @Column(name = "name",nullable = false)
    private String name;

    public ExtraAd(String name){
        this.name = name;
    }
}
复制代码

创建dao包,创建操作数据库表的类的接口

public interface ExtraAdDao extends JpaRepository<ExtraAd,Long> {

}
复制代码

创建exception包,自定义异常类

public class CustomException extends Exception{
    public CustomException(String message){
        super(message);
    }
}
复制代码

创建service包,定义服务接口

public interface ISpringTransaction {

    // 主动捕获异常,事务不能回滚
    void CatchExceptionCanNotRollback();

    // 不是unchecked异常,事务不能回滚
    void NotRuntimeExceptionCanNotRollback() throws CustomException;

    // uncheck异常,事务可以回滚
    void RuntimeExceptionCanRollback();

    // 指定异常,事务可以回滚
    void AssignExceptionCanRollback() throws CustomException;

    // Rollback Only,事务可以回滚
    void RollbackOnlyCanRollback() throws CustomException;

    // 同一个类中,一个不标注事务的方法区调用了标注了事务的方法,事务会失效
    void NonTransactionalCanNotRollback();
}
复制代码

在service包下创建impl包,实现ISpringTransaction接口

在test下新建测试类

整体项目结构如图:

项目结构.JPG

2、创建测试数据库

CREATE DATABASE IF NOT EXISTS `example`;

CREATE TABLE IF NOT EXISTS `example`.`extra_ad` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
    `name` varchar(48) NOT NULL COMMENT '名称',
    PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 COMMENT='额外的表';

TRUNCATE TABLE extra_ad;
复制代码

对应的application.yml

spring:
  profiles:
    active: dev
  jpa:
    open-in-view: false
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/example?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false
    username: ****
    password: ****
    driver-class-name: com.mysql.cj.jdbc.Driver
    tomcat:
      max-active: 4
      min-idle: 2
      initial-size: 2

---
spring:
  profiles: dev

server:
  port: 8080

---
spring:
  profiles: test

server:
  port: 8081

---
spring:
  profiles: prod

server:
  port: 8082
复制代码

几种实践情况

1、主动捕获异常,事务不能回滚

@Override
@Transactional
public void CatchExceptionCanNotRollback() {
    try{
        extraAdDao.save(new ExtraAd("test1"));
        throw new RuntimeException();
    }catch (Exception ex){
        ex.printStackTrace();
    }
}
复制代码

执行前,表中无记录

执行前.JPG 执行方法

@Test
public void NotRuntimeExceptionCanNotRollback() throws CustomException{
    springTransaction.NotRuntimeExceptionCanNotRollback();
}
复制代码

插入1.JPG 并没有回滚,数据插入成功

清空数据库,继续下一个

2、不是unchecked异常,事务不能回滚

@Override
@Transactional
public void NotRuntimeExceptionCanNotRollback() throws CustomException {

    try {
        extraAdDao.save(new ExtraAd("test2"));
        throw new RuntimeException();
    } catch (Exception ex){
        throw new CustomException(ex.getMessage());
    }
}
复制代码

这里强制把RumtimeException转为CustomException,就不是unchecked异常

执行方法

插入2.JPG

没有回滚,成功插入

清空数据库,继续下一条

3、uncheck异常,事务可以回滚

@Override
@Transactional
public void RuntimeExceptionCanRollback() {
    extraAdDao.save(new ExtraAd("test3"));
    throw new RuntimeException();
}
复制代码

执行方法

插入3.JPG

RuntimeException是uncheck异常,发生回滚,数据并没有插入成功

4、指定异常,事务可以回滚

@Override
@Transactional(rollbackFor = {CustomException.class})
public void AssignExceptionCanRollback() throws CustomException {
    try{
        extraAdDao.save(new ExtraAd("test4"));
        throw new RuntimeException();
    }catch (Exception ex){
        throw new CustomException(ex.getMessage());
    }

}
复制代码

执行方法

插入3.JPG 参数rollbackFor指定了CustomException异常类型,并且抛出了该异常,事务回滚,数据插入失败

5、Rollback Only,事务处理失败

@Transactional
public void oneSaveMethod(){
    extraAdDao.save(new ExtraAd("test5"));
}

@Override
@Transactional
public void RollbackOnlyCanRollback() throws CustomException {

    oneSaveMethod();
    try{
        extraAdDao.save(new ExtraAd());
    }catch (Exception ex){
        ex.printStackTrace();
        // throw ex;
    }
}
复制代码

运行方法

抛出异常

org.springframework.dao.DataIntegrityViolationException: not-null property references a null or transient value : com.ad.extraad.entity.ExtraAd.name; nested exception is org.hibernate.PropertyValueException: not-null property references a null or transient value : com.ad.extraad.entity.ExtraAd.name

复制代码

由于字段name要求不为空,extraAdDao.save(new ExtraAd())运行出错

org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back because it has been marked as rollback-only
复制代码

本方法中调用了标记为事务的方法oneSaveMethod(),多个事务合为一个事务,只有全部正确执行完成才会提交事务,由于extraAdDao.save(new ExtraAd())运行出错,事务被标记为rollback-only,导致事务回滚,数据插入失败

6、同一个类中,一个不标注事务的方法调用了标注了事务的方法,事务会失效

@Transactional
public void anotherOneSaveMethod(){
    extraAdDao.save(new ExtraAd("test6"));
    throw new RuntimeException();
}
@Override
public void NonTransactionalCanNotRollback() {
    anotherOneSaveMethod();
}
复制代码

插入4.JPG

可以看到,事务并没有回滚,数据成功插入

运行中抛出的异常

java.lang.RuntimeException
	at com.ad.extraad.service.impl.SpringTransactionImpl.anotherOneSaveMethod(SpringTransactionImpl.java:89)
	at com.ad.extraad.service.impl.SpringTransactionImpl.NonTransactionalCanNotRollback(SpringTransactionImpl.java:93)
	at com.ad.extraad.service.impl.SpringTransactionImpl$$FastClassBySpringCGLIB$$3ffea55.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)
	at com.ad.extraad.service.impl.SpringTransactionImpl$$EnhancerBySpringCGLIB$$175ae292.NonTransactionalCanNotRollback(<generated>)
	at com.ad.extraad.service.TransactionTest.NonTransactionalCanNotRollback(TransactionTest.java:53)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
复制代码

从抛出的异常可以看到,首先,我们拿到的是代理对象,调用 NonTransactionalCanNotRollback 方法。但是 NonTransactionalCanNotRollback 方法在调用 anotherOneSaveMethod 的时候却是原始对象的 anotherOneSaveMethod。所以,这里的调用根本就没有事务的存在,导致事务失效,就更加不存在回滚了.

7、不同类中,一个不标注事务的方法调用了标注了事务的方法,事务生效

插入3.JPG

可以看到,由于异常是uncheck类型,事务发生了回滚,数据并没有插入成功

java.lang.RuntimeException
	at com.ad.extraad.service.impl.SpringTransactionImpl.anotherOneSaveMethod(SpringTransactionImpl.java:89)
	at com.ad.extraad.service.impl.SpringTransactionImpl$$FastClassBySpringCGLIB$$3ffea55.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:783)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753)

复制代码

从抛出的异常可以看到,首先,我们拿到的是代理对象,再去调用anotherOneSaveMethod。所以,这就有事务了,即事务不会失效。


猜你喜欢

转载自juejin.im/post/7040458719955517447