一、基础知识
1、Transational注释
特性
可以在类(接口)或方法上标注
标注在类上:类中所有方法都进行事务处理
标注在接口、实现类的方法上:方法进行事务处理
优先级:方法注解>类注解
属性
progation:事务传播机制
isolation:事务隔离级别
其他四个隔离级别对应着数据库的四个隔离
timeout:事务超时时间,单位是秒。是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。默认值是当前数据库默认事务过期时间。
readOnly:事务是否是只读的,默认是 false。对于只读查询,可以指定事务类型为 readonly,即只读事务。由于只读事务不存在数据的修改, 因此数据库将会为只读事务提供一些优化手段
rollbackFor:设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则事务回滚
noRollbackFor:设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚
rollbackForClassName:设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。
noRollbackForClassName:设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。
使用@Transactional需要注意的地方
@Transactional 只能应用到 public 方法才有效
在默认配置中,Spring FrameWork 的事务框架代码只会将出现 runtime, unchecked 异常的事务标记为回滚;也就是说事务中抛出的异常是 RuntimeException 或其子类,这样事务才会回滚(默认情况下 Error 也会导致事务回滚)。但是,在默认配置的情况下,所有的 checked 异常都不会引起事务回滚。
二、实践
前期准备
1、创建spring项目
新建Spring Initializr项目
配置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")publicclassExtraAd{@Id@GeneratedValue(strategy=GenerationType.IDENTITY)@Column(name="id",nullable=false)privateLongid;@Basic@Column(name="name",nullable=false)privateStringname;publicExtraAd(Stringname){this.name=name;}}
创建dao包,创建操作数据库表的类的接口
publicinterfaceExtraAdDaoextendsJpaRepository<ExtraAd,Long>{}
创建exception包,自定义异常类
publicclassCustomExceptionextendsException{publicCustomException(Stringmessage){super(message);}}
创建service包,定义服务接口
publicinterfaceISpringTransaction{//主动捕获异常,事务不能回滚voidCatchExceptionCanNotRollback();//不是unchecked异常,事务不能回滚voidNotRuntimeExceptionCanNotRollback()throwsCustomException;//uncheck异常,事务可以回滚voidRuntimeExceptionCanRollback();//指定异常,事务可以回滚voidAssignExceptionCanRollback()throwsCustomException;//RollbackOnly,事务可以回滚voidRollbackOnlyCanRollback()throwsCustomException;//同一个类中,一个不标注事务的方法区调用了标注了事务的方法,事务会失效voidNonTransactionalCanNotRollback();}
在service包下创建impl包,实现ISpringTransaction接口
在test下新建测试类
整体项目结构如图:
2、创建测试数据库
CREATEDATABASEIFNOTEXISTS`example`;CREATETABLEIFNOTEXISTS`example`.`extra_ad`(`id`bigint(20)NOTNULLAUTO_INCREMENTCOMMENT'自增主键',`name`varchar(48)NOTNULLCOMMENT'名称',PRIMARYKEY(`id`))ENGINE=InnoDBAUTO_INCREMENT=10DEFAULTCHARSET=utf8COMMENT='额外的表';TRUNCATETABLEextra_ad;
对应的application.yml
spring:profiles:active:devjpa:open-in-view:falsedatasource:url:jdbc:mysql://127.0.0.1:3306/example?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=falseusername:****password:****driver-class-name:com.mysql.cj.jdbc.Drivertomcat:max-active:4min-idle:2initial-size:2---spring:profiles:devserver:port:8080---spring:profiles:testserver:port:8081---spring:profiles:prodserver:port:8082
几种实践情况
1、主动捕获异常,事务不能回滚
@Override@TransactionalpublicvoidCatchExceptionCanNotRollback(){try{extraAdDao.save(newExtraAd("test1"));thrownewRuntimeException();}catch(Exceptionex){ex.printStackTrace();}}
执行前,表中无记录
执行方法
@TestpublicvoidNotRuntimeExceptionCanNotRollback()throwsCustomException{springTransaction.NotRuntimeExceptionCanNotRollback();}
并没有回滚,数据插入成功
清空数据库,继续下一个
2、不是unchecked异常,事务不能回滚
@Override@TransactionalpublicvoidNotRuntimeExceptionCanNotRollback()throwsCustomException{try{extraAdDao.save(newExtraAd("test2"));thrownewRuntimeException();}catch(Exceptionex){thrownewCustomException(ex.getMessage());}}
这里强制把RumtimeException转为CustomException,就不是unchecked异常了
执行方法
没有回滚,成功插入
清空数据库,继续下一条
3、uncheck异常,事务可以回滚
@Data@NoArgsConstructor@AllArgsConstructor@Entity@Table(name="extra_ad")publicclassExtraAd{@Id@GeneratedValue(strategy=GenerationType.IDENTITY)@Column(name="id",nullable=false)privateLongid;@Basic@Column(name="name",nullable=false)privateStringname;publicExtraAd(Stringname){this.name=name;}}0
执行方法
RuntimeException是uncheck异常,发生回滚,数据并没有插入成功
4、指定异常,事务可以回滚
@Data@NoArgsConstructor@AllArgsConstructor@Entity@Table(name="extra_ad")publicclassExtraAd{@Id@GeneratedValue(strategy=GenerationType.IDENTITY)@Column(name="id",nullable=false)privateLongid;@Basic@Column(name="name",nullable=false)privateStringname;publicExtraAd(Stringname){this.name=name;}}1
执行方法
参数rollbackFor指定了CustomException异常类型,并且抛出了该异常,事务回滚,数据插入失败
5、Rollback Only,事务处理失败
@Data@NoArgsConstructor@AllArgsConstructor@Entity@Table(name="extra_ad")publicclassExtraAd{@Id@GeneratedValue(strategy=GenerationType.IDENTITY)@Column(name="id",nullable=false)privateLongid;@Basic@Column(name="name",nullable=false)privateStringname;publicExtraAd(Stringname){this.name=name;}}2
运行方法
抛出异常
@Data@NoArgsConstructor@AllArgsConstructor@Entity@Table(name="extra_ad")publicclassExtraAd{@Id@GeneratedValue(strategy=GenerationType.IDENTITY)@Column(name="id",nullable=false)privateLongid;@Basic@Column(name="name",nullable=false)privateStringname;publicExtraAd(Stringname){this.name=name;}}3
由于字段name要求不为空,extraAdDao.save(new ExtraAd())
运行出错
@Data@NoArgsConstructor@AllArgsConstructor@Entity@Table(name="extra_ad")publicclassExtraAd{@Id@GeneratedValue(strategy=GenerationType.IDENTITY)@Column(name="id",nullable=false)privateLongid;@Basic@Column(name="name",nullable=false)privateStringname;publicExtraAd(Stringname){this.name=name;}}4
本方法中调用了标记为事务的方法oneSaveMethod(),多个事务合为一个事务,只有全部正确执行完成才会提交事务,由于extraAdDao.save(new ExtraAd())
运行出错,事务被标记为rollback-only,导致事务回滚,数据插入失败。
6、同一个类中,一个不标注事务的方法调用了标注了事务的方法,事务会失效
@Data@NoArgsConstructor@AllArgsConstructor@Entity@Table(name="extra_ad")publicclassExtraAd{@Id@GeneratedValue(strategy=GenerationType.IDENTITY)@Column(name="id",nullable=false)privateLongid;@Basic@Column(name="name",nullable=false)privateStringname;publicExtraAd(Stringname){this.name=name;}}5
可以看到,事务并没有回滚,数据成功插入
运行中抛出的异常
@Data@NoArgsConstructor@AllArgsConstructor@Entity@Table(name="extra_ad")publicclassExtraAd{@Id@GeneratedValue(strategy=GenerationType.IDENTITY)@Column(name="id",nullable=false)privateLongid;@Basic@Column(name="name",nullable=false)privateStringname;publicExtraAd(Stringname){this.name=name;}}6
从抛出的异常可以看到,首先,我们拿到的是代理对象,调用 NonTransactionalCanNotRollback 方法。但是 NonTransactionalCanNotRollback 方法在调用 anotherOneSaveMethod 的时候却是原始对象的 anotherOneSaveMethod。所以,这里的调用根本就没有事务的存在,导致事务失效,就更加不存在回滚了.
7、不同类中,一个不标注事务的方法调用了标注了事务的方法,事务生效
可以看到,由于异常是uncheck类型,事务发生了回滚,数据并没有插入成功。
@Data@NoArgsConstructor@AllArgsConstructor@Entity@Table(name="extra_ad")publicclassExtraAd{@Id@GeneratedValue(strategy=GenerationType.IDENTITY)@Column(name="id",nullable=false)privateLongid;@Basic@Column(name="name",nullable=false)privateStringname;publicExtraAd(Stringname){this.name=name;}}7
从抛出的异常可以看到,首先,我们拿到的是代理对象,再去调用anotherOneSaveMethod。所以,这就有事务了,即事务不会失效。