一、基础知识
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项目
配置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下新建测试类
整体项目结构如图:
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();
}
}
复制代码
执行前,表中无记录
执行方法
@Test
public void NotRuntimeExceptionCanNotRollback() throws CustomException{
springTransaction.NotRuntimeExceptionCanNotRollback();
}
复制代码
并没有回滚,数据插入成功
清空数据库,继续下一个
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异常了
执行方法
没有回滚,成功插入
清空数据库,继续下一条
3、uncheck异常,事务可以回滚
@Override
@Transactional
public void RuntimeExceptionCanRollback() {
extraAdDao.save(new ExtraAd("test3"));
throw new RuntimeException();
}
复制代码
执行方法
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());
}
}
复制代码
执行方法
参数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();
}
复制代码
可以看到,事务并没有回滚,数据成功插入
运行中抛出的异常
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、不同类中,一个不标注事务的方法调用了标注了事务的方法,事务生效
可以看到,由于异常是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。所以,这就有事务了,即事务不会失效。