Spring@Transactional注解详解⽂章⽬录
⼀. 使⽤位置
@Transactional:使⽤在⽅法或者类的上⾯。
⼆. 参数说明
参数含义备注
value 定义事务管
理器
Spring IoC容器⾥的⼀个Bean id,这Bean需要实现接⼝PlatformTransactionManager
TransactionManager同上同上
propagation 事务传播⾏
传播⾏为是⽅法之间条⽤的问题。例如:
@Transactional(propagation=Propagation.NOT_SuPPORTED)
isolation 事务隔离级
隔离级别是⼀个数据库在多个事务同时存在时的概念,该属性⽤于设置底层数据库的事务隔离级别,事务隔
离级别⽤于处理多事务并发的情况,通常使⽤数据库的默认隔离级别即可,基本不需要进⾏设置 (默认取值
数据库默认隔离级别)
timeout 事务超时时
单位为秒,发⽣超时时引发异常,默认会导致事务回滚,默认值为-1表⽰永不超时
readOnly 是否开启只
读事务
该属性⽤于设置当前事务是否为只读事务,设置为true表⽰只读,false则表⽰可读写,默认值为false。例
如:@Transactional(readOnly=true)
rollbackFor 回滚事务的
异常类定义
该属性⽤于设置需要进⾏回滚的异常类数组,当⽅法中抛出指定异常数组中的异常时,则进⾏事务回滚。例
如︰
指定单⼀异常类:@Transactional(rollbackFor=RunTimeException.class);
指定多个异常类:@Transactional(rollbackForClassName={“RuntimeException”,“Exception”})
默认值是:RunTimeException 和 Error (如果说没有指定,产⽣)
rollbackForClassName 回滚事务的
异常类名定
该属性⽤于设置需要进⾏回滚的异常类名称数组,当⽅法中抛出指定异常名称数组中的异常时,则进⾏事务
回滚。例如∶
指定单―异常类名称@Transactional(rollbackForClassName=“RuntimeException”);
指定多个异常类名称:@Transactional(rollbackForClassName=
{“RuntimeException”,“Exception”})
noRollbackFor 当产⽣哪些
异常不回滚
事务
该属性⽤于设置不需要进⾏回滚的异常类数组,当⽅法中抛出指定异常名称数组中的异常时,不进⾏事务
回。例如:
指定单⼀异常类:@Transactional(rollbackFor=RunTimeException.class);
指定多个异常类:@Transactional(rollbackForClassName={“RuntimeException”,“Exception”})
noRollbackForClassName 当产⽣哪些
异常类名不
回滚事务
该属性⽤于设置不需要进⾏回滚的异常类名称数组,当⽅法中抛出指定异常名称数组中的异常时,不进⾏事
务回滚。例如:
指定―异常类名称:@Transactional(noRollbackForClassName=“RuntimeException”)
指定多个异常类名称:@Transactional(noRollbackForClassName={“RuntimeException”
,“Exception”})
三. 事务传播⾏为
事务传播⾏为:如果在开始当前事务之前,⼀个事务上下⽂已经存在,此时有若⼲个选项可以指定⼀个事物性⽅法的执⾏⾏为。在TransactionalDefinition定义中包括了如下⼏个表⽰传播⾏为常量:
1. TransactionalDefinition.PROPAGATION_REQUIRED:如果当前没有事务,就创建⼀个新事务,如果当前存在事务,就加⼊该事务。该设
置是默认值。
2. TransactionalDefinition.PROPAGATION_REQUIRES_NEW:创建⼀个新的事务,如果当前存在事务,则把当前事务挂起。
3. TransactionalDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,就加⼊该事务;如果当前不存在事务,就以⾮事务执⾏。
spring ioc注解4. TransactionalDefinition.PROPAGATION_NOT_SUPPORTED:以⾮事务⽅式执⾏操作,如果当前存在事务,就把当前事务挂起。
5. TransactionalDefinition.PROPAGATION_NEVER:以⾮事务⽅式执⾏,如果当前存在事务,则抛出异常。
6. TransactionalDefinition.PROPAGATION_MANDATORY(强制的):如果当前存在事务,就加⼊该事务,如果当前不存在事务,就抛出异
常。
7. TransactionalDefinition.PROPAGATION_NESTED(嵌套):如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运⾏;如果
当前没有事务,则按REQUIRED属性执⾏。
四. 隔离级别
1. SQL标准规范
SQL标准规范把隔离级别定义为4层,分别是:
1. 脏读(dirty read);
2. 读/写提交(read commit);
3. 可重复度(repeatable read);
4. 序列化(serializable)
各类隔离级别对应产⽣的现象:
隔离级别脏读不可重复度幻读脏读Y Y Y 读/写提交N Y Y
可重复度N N Y
序列化N N N
2. 事务隔离级别
事务隔离级别:指若⼲个并发的事务之间的隔离程度。TransactionDefinition接⼝中定义了五个表⽰隔离级别的常量:
1. TransactionDefnion.ISOLATION_DEFAULT:这是默认值,表⽰使⽤底层数据库的默认隔离级别。对⼤部分数据库⽽⾔,通常这值就
是TransactionDefinition.ISOLATION_READ_COMMITTED。
2. Transactionboehnlion. ISOLATION_READ_UNCOMMITTED∶该隔离级别表⽰⼀个事务可以读取另⼀个事务修改但还没有提交的数据,该
级别不能防⽌脏读,不可重复读和幻读,因此很少使⽤该隔离级别。⽐如PostgreSQL实际上并没有此级别。
3. TransactionDefinition.ISOLATION_READ_COMMITTED:该隔离级别表⽰⼀个事务只能读取另⼀个事务已经提交的数据。该级别可以防⽌
脏读,这也是⼤多数情况下的推荐值。
4. TransactionDefinition.ISOLATION_REPEATABLE_READ:该隔离级别表⽰⼀个事务在整个过程中可以多次重复执⾏某个查询,并且每次
返回的记录都相同。该级别可以防⽌脏读和不可重复读。
5. 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 = Logger(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@3150595 66 sql.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 [HikariProxyConn ection@315059566 sql.cj.jdbc.ConnectionImpl@592a1882] to 2
2021/04/05-13:55:38 [main] DEBUG org.springframework.jdbc.support.JdbcTransactionManager- Switching JDBC Connection [HikariProxyConnection@31 5059566 sql.cj.jdbc.ConnectionImpl@592a1882] to manual commit
2021/04/05-13:55:38 [main] batis.spring.SqlSessionUtils- Creating a new SqlSession
......
2021/04/05-13:55:38 [main] batis.spring.SqlSessionUtils- Registering transaction synchronization for SqlSession [org.apache.ibatis.session. defaults.DefaultSqlSession@247bbfba]
......
2021/04/05-13:55:38 [main] ansaction.SpringManagedTransaction- JDBC Connection [HikariProxyConnection@315059566 w sql.cj.jdbc.ConnectionImpl@592a1882] will be managed by Spring
......
2021/04/05-13:55:38 [main] DEBUG cn.ansactional.dao.StudentMapper.insertStudent- ==>  Preparing: insert into student(name, number) values(?, ?)
2021/04/05-13:55:39 [main] DEBUG cn.ansactional.dao.StudentMapper.insertStudent- ==> Parameters: 学⽣1(String), 1(Integer)
2021/04/05-13:55:39 [main] DEBUG cn.ansactional.dao.StudentMapper.insertStudent- <==    Updates: 1
2021/04/05-13:55:39 [main] batis.spring.SqlSessionUtils- Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSql Session@247bbfba]
2021/04/05-13:55:39 [main] batis.spring.SqlSessionUtils- Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@247b bfba] from current transaction
2021/04/05-13:55:39 [main] DEBUG cn.ansactional.dao.StudentMapper.insertStudent- ==>  Preparing: insert into student(name, number) values(?, ?)
2021/04/05-13:55:39 [main] DEBUG cn.ansactional.dao.StudentMapper.insertStudent- ==> Parameters: 学⽣2(String), 2(Integer)
2021/04/05-13:55:39 [main] DEBUG cn.ansactional.dao.StudentMapper.insertStudent- <==    Updates: 1
2021/04/05-13:55:39 [main] batis.spring.SqlSessionUtils- Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSql Session@247bbfba]
2021/04/05-13:55:39 [main] batis.spring.SqlSessionUtils- Transaction synchronization committing SqlSession [org.apache.ibatis.session.def aults.DefaultSqlSession@247bbfba]
2021/04/05-13:55:39 [main] batis.spring.SqlSessionUtils- Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.d efaults.DefaultSqlSession@247bbfba]
2021/04/05-13:55:39 [main] batis.spring.SqlSessionUtils- Transaction synchronization closing SqlSession [org.apache.ibatis.session.default s.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).
AutoCommit()){
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所捕获,所以现在却获取不到任何异常信息。这样就会导致数据错误提交,产⽣严重的⽣产事故。
代码改造:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。