@Transactional
的属性参数属性 | 类型 | 描述 |
---|---|---|
value |
String |
可选,指定事务管理器 |
propagation |
enum: Propagation |
可选,指定事务传播行为 |
isolation |
enum: Isolation |
可选,指定事务隔离级别 |
readOnly |
boolean |
读写或只读事务,默认读写 |
timeout | int (in seconds granularity) | 事务超时时间设置 |
rollbackFor |
Class 对象数组,必须继承自Throwable |
导致事务回滚的异常类数组 |
rollbackForClassName |
类名数组,必须继承自Throwable |
导致事务回滚的异常类名字数组 |
noRollbackFor | Class对象数组,必须继承自Throwable | 不会导致事务回滚的异常类数组 |
noRollbackForClassName | 类名数组,必须继承自Throwable | 不会导致事务回滚的 |
Spring
中事务的传播机制事务的传播机制:如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为
枚举 Propagation
中定义了 7
个传播机制的值
Propagation.REQUIRED
:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。是 spring
默认的传播机制Propagation.SUPPORTS
:持当前事务,如果当前有事务,就以事务方式执行;如果当前没有事务,就以非事务方式执行Propagation.MANDATORY
:使用当前的事务,且必须在一个已有的事务中执行,如果当前不存在事务,否则抛出异常Propagation.REQUIRES_NEW
:不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务Propagation.NOT_SUPPORTED
:以非事务方式执行,如果当前存在事务,就把当前事务挂起Propagation.NEVER
:以非事务方式执行,且必须在一个没有的事务中执行,如果当前存在事务,则抛出异常Propagation.NESTED
:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则执行与 Propagation.REQUIRED
类似的操作Spring
中事务的隔离级别隔离级别:若干个并发的事务之间的隔离程度,与我们开发时候主要相关的场景包括:脏读取、重复读、幻读
枚举 Isolation
中定义了 5
个表示隔离级别的值
Isolation.DEFAULT
:使用各个数据库默认的隔离级别,是 Spring
默认的隔离级别Isolation.READ_UNCOMMITTED
:读取未提交数据(会出现脏读, 不可重复读)Isolation.READ_COMMITTED
:读取已提交数据(会出现不可重复读和幻读)Isolation.REPEATABLE_READ
:可重复读(会出现幻读)Isolation.SERIALIZABLE
:串行化Read Uncommited
)Read Commited
):Oracle
默认的隔离级别Repeatable Read
):MySQL
默认的隔离级别,其可避免脏读和不可重复读,但不能避免幻读Serializable
)不同的隔离级别带来不同的问题
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 | 会 | 会 | 会 |
读已提交 | 不会 | 会 | 会 |
可重复读 | 不会 | 不会 | 会 |
串行化 | 不会 | 不会 | 不会 |
readOnly
事务的读写性默认情况下是 false
(不指定只读性);设置为 true
的含义: 该方法下使用的是只读操作,如果进行其他非读操作,则会跑出异常
从这一点设置的时间点开始(时间点 a
),到这个事务结束的过程中,其他事务所提交的数据,该事务将看不见!!即查询中不会出现别人在时间点 a
之后提交的数据
SQL
执行期间的读一致性SQL
必须保证整体的读一致性;否则,在前条 SQL
查询之后,后条 SQL
查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态。此时,就有必要启用事务的只读性支持是一次执行多次查询来统计某些信息,这时为了保证数据整体的一致性,要用只读事务
timeout
超时时间TransactionTimedOutException
异常rollbackFor
和 rollbackForClassName
遇到时回滚spring
默认情况下会对运行期异常 RunTimeException
进行事务回滚,如果遇到 checked
异常就不回滚noRollbackFor
和 noRollbackForClassName
遇到时不回滚用来指明不回滚的条件是哪些异常类或者异常类名
value
指定使用的事务管理器value
主要用来指定不同的事务管理器,主要用来满足在同一个系统中,存在不同的事务管理器的场景需要spring
中声明了两种事务管理器 txManager1
,txManager2
。然后用户可以根据需要,修改这个参数来指定特定的 txManage
存在多个事务管理器的情况:在一个系统中,需要访问多个数据源,则必然会配置多个事务管理器
Spring
事务不生效的原因Spring
团队建议在具体的 类或类的方法上
使用 @Transactional
注解,而不要使用在 类所要实现的任何接口上
。在接口上使用 @Transactional
注解,只能当你设置了基于接口的代理时它才生效
。因为注解是不能继承的,这就意味着如果正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装
对 Spring
来说,@Transactional
注解所标注的方法或类是主体对象,Spring
会从二者身上获取合适的事务增强器和事务属性,如果获取不到合适的增强器和事务属性,那么事务就会失效
比如我们常用的 MySQL
,从 MySQL 5.5.5
开始的默认存储引擎是 InnoDB
,之前默认的都是 MyISAM
,引擎 MyISAM
是不支持事务操作的,需要改成 InnoDB
才能支持。所以这点要值得注意,底层引擎不支持事务再怎么搞都是白搭
@Transactional
所在类非 Spring
容器的 bean
// @Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) {
// update order
}
}
如果此时把 @Service
注解注释掉,这个类就不会被加载成一个 bean
,那这个类就不会被 Spring
管理了,事务自然就失效了
public
的spring 事务官方文档
Method visibility and @Transactional
When you use proxies, you should apply the @Transactional annotation only to methods with
public visibility. If you do annotate protected, private or package-visible methods with the
@Transactional annotation, no error is raised, but the annotated method does not exhibit the
configured transactional settings. If you need to annotate non-public methods, consider using
AspectJ (described later).
@Transactional
只能用于 public
的方法上,否则事务会失效;即使方法是 public
的,但是如果被 private
的方法调用,事务同样也会失效
可以查看 Spring
事务源码:Spring事务源码中的提取事务注解信息
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
当前数据源如果没有配置事务管理器,那事务是不会生效的
propagation
传播机制设置错误@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void update(Order order) {
updateOrder(order);
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void updateOrder(Order order) {
// update order
}
}
Propagation.NOT_SUPPORTED
:以非事务方式执行,如果当前存在事务,就把当前事务挂起
catch
语句没有抛出异常@Service
public class ClassServiceImpl implements ClassService {
@Override
@Transactional
public void insertClassByException(ClassDo classDo) {
classMapper.insertClass(classDo);
try {
int i = 1 / 0;
} catch (Exception e) {
e.printStackTrace();
}
}
}
把异常吃了,然后又不抛出来,事务也不会回滚!
@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) {
try {
// update order
} catch {
throw new Exception("更新错误");
}
}
}
@Transactional
默认回滚的是 RuntimeException
和 Error
,而 Exception
是 RuntimeException
的父类,事务不生效的
如果你想触发其他异常的回滚,需要在注解上配置一下,如
@Transactional(rollbackFor = Exception.class)
@Transactional
@Override
public void save(User user1, User user2) {
new Thread(() -> {
saveError(user1, user2);
System.out.println(1 / 0);
}).start();
}
@Transactional
的事务开启,或者是基于接口的或者是基于类的代理被创建。所以 在同一个类中一个无事务的方法调用另一个有事务的方法,事务是不会起作用的
addInfo()
上没有事务注解,Spring
获取不到其事务增强器和事务属性,也就是事务切面不会生效
事务生效
由于 Spring
事务默认的传播机制是 Propagation.REQUIRED
,create()
方法的事务会加入到 addInfo()
方法的事务之中;而所在的类是可以产生代理对象的
事务生效
由于 Spring
事务默认的传播机制是 Propagation.REQUIRED
,create()
方法的事务会加入到 addInfo()
方法的事务之中;而所在的类是可以产生代理对象的
事务生效
事务生效
这里虽然是方法内部调用,但是事务切入了 addInfo() 方法
,所以即使内部抛出异常,也是可以生效的
事务不生效
事务生效
这是我们解决方法内部调用事务不生效的最常用方法之一:这个类中维护一个注入自己的 bean
,然后使用这个 bean
来调用方法,如下所示
@Autowired
private PeriodService periodService
针对这个案列的详细分析,可参考:https://blog.csdn.net/qq_33589510/article/details/120387044