文章目录
- 一. 使用位置
- 二. 参数说明
- 三. 事务传播行为
- 四. 隔离级别
- 1. SQL标准规范
- 2. 事务隔离级别
- 五. 失效问题
- 1. 非静态方法和公共方法
- 2. 自调用问题
- 3. 错误捕获异常
- 4. 默认回滚unchecked
一. 使用位置
@Transactional
:使用在方法或者类的上面。
二. 参数说明
参数 | 含义 | 备注 |
value | 定义事务管理器 | Spring IoC容器里的一个Bean id,这Bean需要实现接口PlatformTransactionManager |
TransactionManager | 同上 | 同上 |
propagation | 事务传播行为 | 传播行为是方法之间条用的问题。例如:@Transactional(propagation=Propagation.NOT_SuPPORTED) |
isolation | 事务隔离级别 | 隔离级别是一个数据库在多个事务同时存在时的概念,该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置 (默认取值数据库默认隔离级别) |
timeout | 事务超时时间1 | 单位为秒,发生超时时引发异常,默认会导致事务回滚,默认值为-1表示永不超时 |
readOnly | 是否开启只读事务2 | 该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。例如:@Transactional(readOnly=true) |
rollbackFor | 回滚事务的异常类定义3 | 该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如︰ 指定单一异常类:@Transactional(rollbackFor=RunTimeException.class); 指定多个异常类:@Transactional(rollbackForClassName={“RuntimeException”,“Exception”}) 默认值是:RunTimeException 和 Error (如果说没有指定,产生) |
rollbackForClassName | 回滚事务的异常类名定义3 | 该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如∶ 指定单―异常类名称@Transactional(rollbackForClassName=“RuntimeException”); 指定多个异常类名称:@Transactional(rollbackForClassName={“RuntimeException”,“Exception”}) |
noRollbackFor | 当产生哪些异常不回滚事务3 | 该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回。例如: 指定单一异常类:@Transactional(rollbackFor=RunTimeException.class); 指定多个异常类:@Transactional(rollbackForClassName={“RuntimeException”,“Exception”}) |
noRollbackForClassName | 当产生哪些异常类名不回滚事务3 | 该属性用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。例如: 指定―异常类名称:@Transactional(noRollbackForClassName=“RuntimeException”) 指定多个异常类名称:@Transactional(noRollbackForClassName={“RuntimeException” ,“Exception”}) |
三. 事务传播行为
事务传播行为:如果在开始当前事务之前,一个事务上下文已经存在,此时有若干个选项可以指定一个事物性方法的执行行为。在TransactionalDefinition
定义中包括了如下几个表示传播行为常量:
-
TransactionalDefinition.PROPAGATION_REQUIRED
:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务。该设置是默认值。 -
TransactionalDefinition.PROPAGATION_REQUIRES_NEW
:创建一个新的事务,如果当前存在事务,则把当前事务挂起。 -
TransactionalDefinition.PROPAGATION_SUPPORTS
:如果当前存在事务,就加入该事务;如果当前不存在事务,就以非事务执行。 -
TransactionalDefinition.PROPAGATION_NOT_SUPPORTED
:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 -
TransactionalDefinition.PROPAGATION_NEVER
:以非事务方式执行,如果当前存在事务,则抛出异常。 -
TransactionalDefinition.PROPAGATION_MANDATORY
(强制的):如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。 -
TransactionalDefinition.PROPAGATION_NESTED
(嵌套):如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则按REQUIRED属性执行。
四. 隔离级别
1. SQL标准规范
SQL标准规范把隔离级别定义为4层,分别是:
- 脏读(dirty read);
- 读/写提交(read commit);
- 可重复度(repeatable read);
- 序列化(serializable)
各类隔离级别对应产生的现象:
隔离级别 | 脏读 | 不可重复度 | 幻读 |
脏读 | Y | Y | Y |
读/写提交 | N | Y | Y |
可重复度 | N | N | Y |
序列化 | N | N | N |
2. 事务隔离级别
事务隔离级别:指若干个并发的事务之间的隔离程度。TransactionDefinition
接口中定义了五个表示隔离级别的常量:
-
TransactionDefnion.ISOLATION_DEFAULT
:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED
。 -
Transactionboehnlion. ISOLATION_READ_UNCOMMITTED
∶该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据,该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。 -
TransactionDefinition.ISOLATION_READ_COMMITTED
:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。 -
TransactionDefinition.ISOLATION_REPEATABLE_READ
:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。 -
TransactionDefinition.ISOLATION_SERIALIZABLE
:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰。也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
五. 失效问题
在使用@Transactional
注解配置事务时,需要注意一些细节上的问题,避免@Transactional
注解的失效问题;
1. 非静态方法和公共方法
@Transactional
的底层实现是Spring AOP,而Spring AOP技术底层使用的是动态代理技术,也就是说使用@Transactional
注解的方法必须是非静态 (static
修饰)方法和public
修饰方法。
2. 自调用问题
一个类中的一个方法去调用另外一个方法的过程。
@Service
public class StudentListServiceImpl implements StudentListService {
private static final Logger log = LoggerFactory.getLogger(StudentListServiceImpl.class);
@Autowired
private StudentMapper studentMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class)
@Override
public int insertStudent(Student student){
return studentMapper.insertStudent(student);
}
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class)
@Override
public int insertStudentList(List<Student> studentList) {
int count = 0;
for (Student student: studentList){
try {
// 调用自身类的方法,产生自调用问题
count += insertStudent(student);
} catch (Exception e) {
log.info("系统异常:{}", e);
}
}
return count;
}
}
执行查看主要日志输出:
2021/04/05-13:55:38 [main] DEBUG org.springframework.jdbc.support.JdbcTransactionManager- Acquired Connection [HikariProxyConnection@315059566 wrapping com.mysql.cj.jdbc.ConnectionImpl@592a1882] for JDBC transaction
2021/04/05-13:55:38 [main] DEBUG org.springframework.jdbc.datasource.DataSourceUtils- Changing isolation level of JDBC Connection [HikariProxyConnection@315059566 wrapping com.mysql.cj.jdbc.ConnectionImpl@592a1882] to 2
2021/04/05-13:55:38 [main] DEBUG org.springframework.jdbc.support.JdbcTransactionManager- Switching JDBC Connection [HikariProxyConnection@315059566 wrapping com.mysql.cj.jdbc.ConnectionImpl@592a1882] to manual commit
2021/04/05-13:55:38 [main] DEBUG org.mybatis.spring.SqlSessionUtils- Creating a new SqlSession
......
2021/04/05-13:55:38 [main] DEBUG org.mybatis.spring.SqlSessionUtils- Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@247bbfba]
......
2021/04/05-13:55:38 [main] DEBUG org.mybatis.spring.transaction.SpringManagedTransaction- JDBC Connection [HikariProxyConnection@315059566 wrapping com.mysql.cj.jdbc.ConnectionImpl@592a1882] will be managed by Spring
......
2021/04/05-13:55:38 [main] DEBUG cn.zhuzicc.spring.transactional.dao.StudentMapper.insertStudent- ==> Preparing: insert into student(name, number) values(?, ?)
2021/04/05-13:55:39 [main] DEBUG cn.zhuzicc.spring.transactional.dao.StudentMapper.insertStudent- ==> Parameters: 学生1(String), 1(Integer)
2021/04/05-13:55:39 [main] DEBUG cn.zhuzicc.spring.transactional.dao.StudentMapper.insertStudent- <== Updates: 1
2021/04/05-13:55:39 [main] DEBUG org.mybatis.spring.SqlSessionUtils- Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@247bbfba]
2021/04/05-13:55:39 [main] DEBUG org.mybatis.spring.SqlSessionUtils- Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@247bbfba] from current transaction
2021/04/05-13:55:39 [main] DEBUG cn.zhuzicc.spring.transactional.dao.StudentMapper.insertStudent- ==> Preparing: insert into student(name, number) values(?, ?)
2021/04/05-13:55:39 [main] DEBUG cn.zhuzicc.spring.transactional.dao.StudentMapper.insertStudent- ==> Parameters: 学生2(String), 2(Integer)
2021/04/05-13:55:39 [main] DEBUG cn.zhuzicc.spring.transactional.dao.StudentMapper.insertStudent- <== Updates: 1
2021/04/05-13:55:39 [main] DEBUG org.mybatis.spring.SqlSessionUtils- Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@247bbfba]
2021/04/05-13:55:39 [main] DEBUG org.mybatis.spring.SqlSessionUtils- Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@247bbfba]
2021/04/05-13:55:39 [main] DEBUG org.mybatis.spring.SqlSessionUtils- Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@247bbfba]
2021/04/05-13:55:39 [main] DEBUG org.mybatis.spring.SqlSessionUtils- Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@247bbfba]
2021/04/05-13:55:39 [main] DEBUG org.springframework.jdbc.support.JdbcTransactionManager- Initiating transaction commit
来看看日志的具体内容:
1.获取连接592a1882给当前JDBC事务使用;
2.改变隔离级别为2,这里的2我理解的是TransactionDefinition中的ISOLATION_READ_COMMITTED;
3.改变当前事务的提交方式为手动提交;
4.创建了一个SqlSession会话;
5.为本次SqlSession会话注册事务同步;
7.将当前JDBC连接交给Spring管理;
17.事务同步释放SqlSession会话;
18.事务同步提交SqlSession会话;
19.事务同步注销SqlSession会话;
20.事务同步关闭SqlSession会话;
21.启动事务提交;
从日志中可以看到数据插入两次都使用了同一个事务,说明insertStudent()
方法上标注的@Transactional
失效,出现这个问题的根本原因就在于AOP的实现原理,AOP实现原理是动态代理,而上述示例代码是本类自己调用自己的过程,并不存在代理对象的调用,所以就不会产生AOP去设置insertStudent()
方法上标注的@Transactional
参数。
第3行和第21行是Spring 关闭数据库中自动提交,改为手动提交,在方法执行前关闭自动提交,方法执行完毕后再开启自动提交;org.springframework.jdbc.datasource.DataSourceTransactionManager.java
相关源码展示:
// Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
// so we don't want to do it unnecessarily (for example if we've explicitly
// configured the connection pool to set it already).
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
if (logger.isDebugEnabled()) {
logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
}
con.setAutoCommit(false);
}
3. 错误捕获异常
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class)
@Override
public int insertStudentList(List<Student> studentList) {
int count = 0;
for (Student student: studentList){
try {
count += studentService.insertStudent(student);
if (count == studentList.size()){
studentService.allotStudent(student);
}
} catch (Exception e) {
log.info("系统异常:{}", e);
}
}
return count;
}
场景:在上例方法中分别进行了添加学生和分配学生的操作,并且进行了try…catch异常捕获,当产生异常时就可能会出现添加学生成功,但是在分配学生时产生异常,此时的Spring依然会照常提交事务,因为Spring本该在数据库事务所约定的流程中获取到方法抛出的异常,但是异常信息已经被方法自定义的catch所捕获,所以现在却获取不到任何异常信息。这样就会导致数据错误提交,产生严重的生产事故。
代码改造:
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class)
@Override
public int insertStudentList(List<Student> studentList) {
int count = 0;
for (Student student: studentList){
try {
count += studentService.insertStudent(student);
if (count == studentList.size()){
studentService.allotStudent(student);
}
} catch (Exception e) {
log.info("系统异常:{}", e);
// 自行抛出异常,让spring事务管理流程获取到方法抛出的异常,从而进行事务管理
throw new RuntimeException(e);
}
}
return count;
}
4. 默认回滚unchecked
@Transactional
默认回滚规则只回滚 RuntimeException
,而抛出 checked 异常则不会使事务回滚。
所以,我们在使用 @Transactional
时,需要指定 rollbackFor
的事务回滚级别:
@Transactional(rollbackFor = Exception.class)
下图是@Transactional
源码中的原话:
顺带着回顾一下 Throwable
、Error
、Exception
之间的关系:
- 事务超时:指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回浪事务,在
Transactonbeiatlon
中i以int
的值来表示超时的时间,其单位是秒,默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是none,没有超时限制。 ↩︎ - 只读事务用户代码只读取数据,不修改数据的情形,只读事务用于特定情境下的优化。 ↩︎
- 事务的回滚条件: 可以设置当方法遇到指定异常类型回滚或者不会滚。 ↩︎ ↩︎ ↩︎ ↩︎