概念
事务定义
事务特点
-
原子性: 一个事务中所有对数据库的操作是一个不可分割的操作序列,要么全做要么全不做 -
一致性: 数据不会因为事务的执行而遭到破坏 -
隔离性: 一个事务的执行,不受其他事务的干扰,即并发执行的事务之间互不干扰 -
持久性: 一个事务一旦提交,它对数据库的改变就是永久的。
事务实现机制
-
编程式事务管理: 编程式事务管理使用 TransactionTemplate
或者直接使用底层的PlatformTransactionManager
。对于编程式事务管理,spring推荐使用TransactionTemplate
。 -
声明式事务管理: 建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
-
一种是在配置文件(xml)中做相关的事务规则声明(因为很少用本文不讲解) -
另一种是基于 @Transactional
注解的方式。注释配置是目前流行的使用方式,推荐使用。
@Transactional
的目标方法时,Spring Framework 默认使用 AOP 代理,在代码运行时生成一个代理对象,根据 @Transactional
的属性配置信息,这个代理对象决定该声明 @Transactional
的目标方法是否由拦截器 TransactionInterceptor
来使用拦截,在 TransactionInterceptor
拦截时,会在目标方法开始执行之前创建并加入事务,并执行目标方法的逻辑,最后根据执行情况是否出现异常,利用抽象事务管理器 AbstractPlatformTransactionManager
操作数据源 DataSource
提交或回滚事务。CglibAopProxy
和 JdkDynamicAopProxy
两种,以 CglibAopProxy
为例,对于 CglibAopProxy
,需要调用其内部类的 DynamicAdvisedInterceptor
的 intercept
方法。对于 JdkDynamicAopProxy
,需要调用其 invoke 方法。开启事务
注解@Transactional的使用
注解@Transactional常用配置
Propagation的属性(事务的传播行为)
@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)
事务5种隔离级别
@Transactional(isolation = Isolation.READ_COMMITTED)
使用注意事项(防止事务失效)
@Transactional
注解,而不要使用在类所要实现的任何接口上。@Transactional
注解应该只被应用在 public
修饰的方法上。如果你在 protected
、private
或者 package-visible
的方法上使用 该注解,它也不会报错(IDEA会有提示), 但事务并没有生效。-
被外部调用的公共方法A未声明事务 @Transactional
,子方法B和C若是其他类的方法且各自声明事务,则事务由子方法B和C各自控制 -
被外部调用的公共方法A未声明事务 @Transactional
,子方法B和C若是本类的方法,则无论子方法B和C是否声明事务,事务均不会生效 -
被外部调用的公共方法A声明事务 @Transactional
,无论子方法B和C是不是本类的方法,无论子方法B和C是否声明事务,事务均由公共方法A控制 -
被外部调用的公共方法A声明事务 @Transactional
,子方法运行异常,但运行异常被子方法自己 try-catch 处理了,则事务回滚是不会生效的!
-
方案1:子方法中不用 try-catch 处理运行异常 -
方案2:子方法的catch里面将运行异常抛出【 throw new RuntimeException();
】
RuntimeException()
异常或是其子类进行事务回滚。如果是checked异常则不回滚,例如空指针异常、算数异常等会被回滚;文件读写、网络问题Spring就没法回滚。@Transactional(rollbackFor = Exception.class
@Transactional
由spring控制时,它会在抛出异常的时候进行回滚。如果自己使用try-catch捕获处理了,是不生效的。如果想事务生效可以进行手动回滚或者在catch里面将异常抛出【throw new RuntimeException();
】try{
....
}catch(Exception e){
logger.error("",e);
throw new RuntimeException(e);
}
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
】try{
...
}catch(Exception e){
log.error("fail",e);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return false;
}
@Transactional
可以放在Controller下面直接起作用,看到网上好多同学说要放到@Component
下面或者@Service
下面,经过试验,可以不用放在这两个下面也起作用。@Transactional
引入包问题,它有两个包:import javax.transaction.Transactional;
// 和
import org.springframework.transaction.annotation.Transactional; // 推荐
使用场景
自动回滚
@Override
@Transactional(rollbackFor = Exception.class)
public Object submitOrder() throws Exception {
success();
//假如exception这个操作数据库的方法会抛出异常,方法success()对数据库的操作会回滚。
exception();
return ApiReturnUtil.success();
}
手动回滚
@Override
@Transactional(rollbackFor = Exception.class)
public Object submitOrder (){
success();
try {
exception();
} catch (Exception e) {
e.printStackTrace();
// 手动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return ApiReturnUtil.error();
}
return ApiReturnUtil.success();
}
回滚部分异常
Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
】设置回滚点。TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);
】回滚到savePoint。@Override
@Transactional(rollbackFor = Exception.class)
public Object submitOrder (){
success();
//只回滚以下异常,
Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
try {
exception();
} catch (Exception e) {
e.printStackTrace();
// 手工回滚事务
TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);
return ApiReturnUtil.error();
}
return ApiReturnUtil.success();
}
手动创建、提交、回滚事务
PlatformTransactionManager
这个接口中定义了三个方法 getTransaction
创建事务,commit 提交事务,rollback 回滚事务。它的实现类是 AbstractPlatformTransactionManager
。@Autowired
priDataSourceTransactionManager dataSourceTransactionManager;
@Autowired
TransactionDefinition transactionDefinition;
// 手动创建事务
TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
// 手动提交事务
dataSourceTransactionManager.commit(transactionStatus);
// 手动回滚事务。(最好是放在catch 里面,防止程序异常而事务一直卡在哪里未提交)
dataSourceTransactionManager.rollback(transactionStatus);
事务失效不回滚的原因及解决方案
异常被捕获导致事务失效
@Transactional
就可以实现。@GetMapping("delete")
@ResponseBody
@Transactional
public void delete(@RequestParam("id") int id) {
try {
//delete country
this.repository.delete(id);
if(id == 1){
throw Exception("测试事务");
}
//delete city
this.repository.deleteByCountryId(id);
}catch (Exception e){
logger.error("delete false:" + e.getMessage());
}
}
this.repository.delete(id);
成功把数据删除了。RuntimeException
时才回滚。RuntimeException
的异常,但可以通过配置来捕获特定的异常并回滚。throw new RuntimeExcetpion()
抛出运行异常,这样程序异常时才能被aop捕获进而回滚。
org.springframework
spring-aspects
4.3.2.RELEASE
org.aspectj
aspectjrt
1.8.9
org.codehaus.mojo aspectj-maven-plugin 1.9
true
org.springframework
spring-aspects
compile
test-compile
解决方案
@Service
@Slf4j
public class MyTransactional {
// 最外层公共方法。自动回滚事务方式,insertOrder()方法报错后事务回滚,且线程中止,后续逻辑无法执行
@Transactional
public void test1() {
this.insertOrder();
System.out.println("11111111111111111");
}
// 最外层公共方法。手动回滚事务方式,insertOrder()方法报错后事务回滚,可以继续执行后续逻辑
@Transactional
public void test2() {
try {
insertOrder();
} catch (Exception e) {
log.error("faild to ...", e);
// 手动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
// 其他操作
}
// 其他操作
}
// 进行数据库操作的方法(private 或 public 均可)
private void insertOrder() {
//insert log info
//insertOrder
//updateAccount
}
}
@Transactional
注解只应用到 public 方法和自调用问题,是由于使用 Spring AOP 代理造成的。为解决这两个问题,可以使用 AspectJ 取代 Spring AOP 代理。
"aspectj" /> "transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
"dataSource" ref="dataSource" />
"org.springframework.transaction.aspectj.AnnotationTransactionAspect" factory-method="aspectOf">
"transactionManager" ref="transactionManager" />
spring-aspects
和 aspectjrt
的 dependency
以及 aspectj-maven-plugin
。-
方案1:例如service层处理事务,那么service中的方法中不做异常捕获,或者在catch语句中最后增加 throw new RuntimeException();
语句,以便让aop捕获异常再去回滚,并且在service的上层要继续捕获这个异常。 -
方案2:在service层方法的catch语句中进行手动回滚,这样上层就无需去处理异常。
@GetMapping("delete")
@ResponseBody
@Transactional
public Object delete(@RequestParam("id") int id){
if (id < 1){
return new MessageBean(101,"parameter wrong: id = " + id) ;
}
try {
//delete country
this.countryRepository.delete(id);
//delete city
this.cityRepository.deleteByCountryId(id);
return new MessageBean(200,"delete success");
}catch (Exception e){
logger.error("delete false:" + e.getMessage());
// 手动回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return new MessageBean(101,"delete false");
}
}
自调用导致事务失效
问题描述及原因
@Transactional
注解的方法 内部调用 有@Transactional
注解的方法,有@Transactional
注解的方法的事务被忽略,不会发生回滚。见 示例代码展示。@Service
public class OrderService {
private void insert() {
insertOrder();
}
@Transactional
public void insertOrder() {
//insert log info
//insertOrder
//updateAccount
}
}
// insertOrder() 尽管有@Transactional 注解,但它被内部方法 insert()调用,事务被忽略,出现异常事务不会发生回滚,并且会报错类似于:org.springframework.transaction.NoTransactionException: No transaction aspect-managed TransactionStatus in scope(翻译:没有Transaction无法回滚事务。自调用导致@Transactional 失效。)
@Transactional 的实现原理是AOP,AOP的实现原理是动态代理,而自调用时并不存在代理对象的调用,也就不会产生基于AOP 的事务回滚操作
org.springframework
spring-aspects
4.3.2.RELEASE
org.aspectj
aspectjrt
1.8.9
org.codehaus.mojo aspectj-maven-plugin 1.9
true
org.springframework
spring-aspects
compile
test-compile
其他
事务提交方式
connection.setAutoCommit(false);
,在执行完之后在进行提交 connection.commit();
。事务回滚规则
RuntimeException
的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。事务并发会产生的问题
第一类丢失更新
张三的工资为5000,事务A中获取工资为5000,事务B获取工资为5000,汇入100,并提交数据库,工资变为5100; 随后,事务A发生异常,回滚了,恢复张三的工资为5000,这样就导致事务B的更新丢失了。
脏读
张三的工资为5000,事务A中把他的工资改为8000,但事务A尚未提交。 与此同时,事务B正在读取张三的工资,读取到张三的工资为8000。 随后,事务A发生异常,回滚了事务,张三的工资又回滚为5000。 最后,事务B读取到的张三工资为8000的数据即为脏数据,事务B做了一次脏读。
不可重复读
在事务A中,读取到张三的工资为5000,操作没有完成,事务还没提交。 与此同时,事务B把张三的工资改为8000,并提交了事务。 随后,在事务A中,再次读取张三的工资,此时工资变为8000。在一个事务中前后两次读取的结果并不致,导致了不可重复读。
第二类丢失更新
在事务A中,读取到张三的存款为5000,操作没有完成,事务还没提交。 与此同时,事务B存入1000,把张三的存款改为6000,并提交了事务。 随后,在事务A中,存储500,把张三的存款改为5500,并提交了事务,这样事务A的更新覆盖了事务B的更新。
幻读
目前工资为5000的员工有10人,事务A读取到所有的工资为5000的人数为10人。 此时,事务B插入一条工资也为5000的记录。 这时,事务A再次读取工资为5000的员工,记录为11人。此时产生了幻读。
不可重复读和幻读的区别
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.shuli.cc/?p=19893,转载请注明出处。
评论0