【Spring】@Transactional 闲聊
菜瓜:上次的AOP理论知识看完收获挺多的,虽然有一个自定义注解的demo,但还是觉得差点东西
水稻:我也觉得没有跟一遍源码还是差点意思,这次结合@Transactional注解深入源码看一下
菜瓜:事务注解,这个平时用的挺多的
水稻:是吗?来看看你的基础咋样
- 要保证一个方法中多个数据库操作的原子性,要共用一个数据库连接,但是coding时我们不用显示传递连接对象,这是咋弄的?
- 如果一个方法里面只有查询操作,是否不用开启事务?
- 如何解决非事务方法调用本地事务方法失效的?
- 注解常用的传播属性,你知道他们的区别吗
菜瓜:虽然没看过源码,我大胆猜测一下
- 隐式传递连接对象可以将其封装到线程中,一般一次请求操作都是在一个线程中完成。使用ThreadLocal将连接和线程绑定
- 查询操作也得看业务场景,如果多次查询相同的数据要避免不可重复读问题,可开启只读事务 (readOnly = true)
- 结合AOP的知识,这里其实要解决调用事务方法的对象不是代理对象的问题。用代理对象调本地事务方法即可(注入自己)
/**
* @author QuCheng on 2020/6/24.
*/
@Service
public class ItemServiceImpl implements ItemService { @Resource
private IcbcItemMapper itemMapper; @Resource
private ItemService itemService; @Override
public void changeNameById(Long itemId) {
// changeItemById(itemId);
itemService.changeItemById(itemId);
} @Transactional(rollbackFor = RuntimeException.class)
@Override
public void changeItemById(Long itemId) {
itemMapper.updateNameById(itemId, "name4");
int a = 10 / 0;
itemMapper.updatePriceById(itemId, 100L);
}
}
- 传播属性这个没了解过啊,数据库事务里面么得这个概念
水稻:可以啊,平时的代码没白写
菜瓜:coding这种事情,easy啦!
水稻:这就飘了?来看这个问题
- 如果我想在A事务方法中调用B事务方法,B方法如果回滚了,不能影响A事务继续执行,但是A事务如果执行出问题了,B也要回滚,怎么弄?
菜瓜:。。。这不就是大事务嵌套小事务嘛。。。我不会
水稻:不扯了,来看源码吧,这个问题等解释了传播属性你就知道了
- 上回我们说到,@Transactional是AOP的典型应用,bean被实例化之后要创建代理(参考自定义注解),就少不了切面类Advisor对象。那么它是谁,它在哪,它在干什么?
- 回到梦开始的地方,事务功能开启的注解@EnableTransactionManagement
- 没错,它肯定会有一个Import注解引入TransactionManagementConfigurationSelector类,它又引入了切面类
public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> { @Override
protected String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return new String[] {AutoProxyRegistrar.class.getName(),
// 看这里
ProxyTransactionManagementConfiguration.class.getName()};
case ASPECTJ:
return new String[] {determineTransactionAspectClass()};
default:
return null;
}
}
。。。 } @Configuration
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration { @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() {
BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
advisor.setTransactionAttributeSource(transactionAttributeSource());
advisor.setAdvice(transactionInterceptor());
if (this.enableTx != null) {
advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
}
return advisor;
} @Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource();
} @Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionInterceptor transactionInterceptor() {
// 增强
TransactionInterceptor interceptor = new TransactionInterceptor();
interceptor.setTransactionAttributeSource(transactionAttributeSource());
if (this.txManager != null) {
interceptor.setTransactionManager(this.txManager);
}
return interceptor;
} }- 切面类对象设置了事务的扫描器,也set了增强类TransactionInterceptor
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
。。。
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
// Work out the target class: may be {@code null}.
// The TransactionAttributeSource should be passed the target class
// as well as the method, which may be from an interface.
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null); // Adapt to TransactionAspectSupport's invokeWithinTransaction...
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}
} public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean { 。。。
@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
。。。
// ①创建事务,数据库连接处理也在这
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
// 调用目标方法
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// 异常后事务处理
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
commitTransactionAfterReturning(txInfo);
return retVal;
}
。。。
}
菜瓜:懂,接下来的代码逻辑就是在增强类TransactionInterceptor的invoke方法里
水稻:对
- 先看数据库连接的处理 - 验证ThreadLocal
protected void doBegin(Object transaction, TransactionDefinition definition) {
。。。
// 如果连接是新的,就进行绑定
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(this.obtainDataSource(), txObject.getConnectionHolder());
}
。。。
} class TransactionSynchronizationManager
public static void bindResource(Object key, Object value) throws IllegalStateException {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Assert.notNull(value, "Value must not be null");
Map<Object, Object> map = resources.get();
// set ThreadLocal Map if none found
if (map == null) {
map = new HashMap<>();
resources.set(map);
}
Object oldValue = map.put(actualKey, value);
// Transparently suppress a ResourceHolder that was marked as void...
if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
oldValue = null;
}
if (oldValue != null) {
throw new IllegalStateException("Already value [" + oldValue + "] for key [" +
actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
}
if (logger.isTraceEnabled()) {
logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" +
Thread.currentThread().getName() + "]");
}
}- 回过头来看AB方法调用的回滚问题,直接给出答案(突然发现这个问题要讲清楚篇幅会很大,就。。挺突然的。。挺突然der)
- 在B方法上设置传播属性为NESTED即可,然后在A中catch住B的异常
- 你肯定会问我不加NESTED去catch不行吗?不行,非NESTED的方法抛出的异常是无法回滚的。
- 不信你看
@Transactional(rollbackFor = RuntimeException.class)
@Override
public void changeNameById(Long itemId) {
itemMapper.updateNameById(itemId, "A");
try {
itemService.changeItemById(itemId);
// itemService.changeItemByIdNested(itemId);
} catch (Exception e) {
System.out.println("我想继续执行,不影响修改A");
}
itemMapper.updatePriceById(itemId, 1L);
} @Transactional(rollbackFor = RuntimeException.class)
@Override
public void changeItemById(Long itemId) {
itemMapper.updateNameById(itemId, "B+REQUIRED");
itemMapper.updatePriceById(itemId, 10L);
int a = 10 / 0;
} @Transactional(rollbackFor = RuntimeException.class, propagation = Propagation.NESTED)
@Override
public void changeItemByIdNested(Long itemId) {
itemMapper.updateNameById(itemId, "B+NESTED");
itemMapper.updatePriceById(itemId, 100L);
int a = 10 / 0;
} -- 测试结果
//① itemService.changeItemById(itemId); 数据库所有数据都不会改变
我想继续执行,不影响修改A
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only // ② itemService.changeItemByIdNested(itemId); 第一个方法的修改会生效
我想继续执行,不影响修改A
菜瓜:这就是传播属性NESTED?默认的是REQUIRED,还有一个常用的REQUIRES_NEW呢?
水稻:搞清楚这个其实从数据库连接入手其实就很清楚
- REQUIRED修饰的方法和A使用同一个连接,A和B是挂一起的,谁回滚都会影响对方,且B方法的异常会被事务管理器标记为必须回滚
- NESTED修饰的方法和A使用同一个连接,但是用到了数据库的savePoint特性,它可以回滚到指定的点,如果是有回滚点的操作,抛出的异常可以被处理
- REQUIRES_NEW修饰的方法和A使用的就不是一个连接了,回不回滚都不会影响对方,当然,要捕捉异常
菜瓜:传播属性了解。回滚的问题还得再看看,篇幅很大是很复杂吗?
水稻:其实不复杂,就是要跟踪源码断点调试。。。截图搞来搞去,篇幅就很长,你自己去调的话其实很快
菜瓜:那我下去康康
总结
- 这里提到Transactional注解其实是为了巩固AOP的,当然提到了一些注意点。譬如本地调用,譬如ThreadLocal的应用,还譬如传播属性
- 传播属性其实用的少,但是聊起来就比较多了,可以聊事务的隔离级别,聊ACID的实现,聊MySQL的锁
【Spring】@Transactional 闲聊的更多相关文章
- 数据库事务中的隔离级别和锁+spring Transactional注解
数据库事务中的隔离级别和锁 数据库事务在后端开发中占非常重要的地位,如何确保数据读取的正确性.安全性也是我们需要研究的问题.ACID首先总结一下数据库事务正确执行的四个要素(ACID): 原子性(At ...
- How does Spring @Transactional Really Work?--转
原文地址:http://blog.jhades.org/how-does-spring-transactional-really-work/ In this post we will do a dee ...
- Spring @Transactional使用的示例
Spring @Transactional使用的示例: 参考: http://blog.csdn.net/seng3018/article/details/6690527 http://blog.si ...
- Spring @Transactional 使用
Spring @Transactional是Spring提供的一个声明式事务,对代码的侵入性比较小,只需考虑业务逻辑,不需要把事务和业务搞混在一起. @Transactional 可以注解在inter ...
- Java:Spring @Transactional工作原理
本文将深入研究Spring的事务管理.主要介绍@Transactional在底层是如何工作的.之后的文章将介绍: propagation(事务传播)和isolation(隔离性)等属性的使用 事务使用 ...
- spring @Transactional 事务注解
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.SERIALIZABLE, rollbackFor = ...
- Spring @Transactional (一)
Spring @Transactional (一) 博客分类: JAVA SpringJPAJDBCUPSQL Spring事务的传播行为 在service类前加上@Transactional,声明 ...
- [转]数据库事务中的隔离级别和锁+spring Transactional注解
数据库事务中的隔离级别和锁 数据库事务在后端开发中占非常重要的地位,如何确保数据读取的正确性.安全性也是我们需要研究的问题.ACID首先总结一下数据库事务正确执行的四个要素(ACID): 原子性(At ...
- 25.Spring @Transactional工作原理
转自:http://www.importnew.com/12300.html 本文将深入研究Spring的事务管理.主要介绍@Transactional在底层是如何工作的.之后的文章将介绍: prop ...
随机推荐
- 最小生成树——Kruskal算法理解
背景:本文是在小甲鱼数据结构教学视频中的代码的基础上,添加详细注释而完成的.该段代码并不完整,仅摘录了核心算法部分,结合自己的思考,谈谈理解. Prim算法理解: 如图(摘录自小甲鱼教学视频中的图片) ...
- 如何发布一个 npm 包
一 背景 在工作时,突然接到经理的一个要求,需要将一个react的高阶组件函数封装成一个npm包.之前从没弄过,当场还是有些懵逼的,但是这毕竟是工作,不能推脱.于是开始了学习.汤坑之旅.最终包发布,线 ...
- Chisel3-Intellij IDEA中使用sbt构建Chisel项目
https://mp.weixin.qq.com/s/gssjiiPW6zUzKwCFZdNduw 1. 使用Intellij IDEA创建Scala项目 Chisel项目,就是构建Scala ...
- js函数prototype属性学习(一)
W3school上针对prototype属性是这么给出定义和用法的:使您有能力向对象添加属性和方法.再看w3school上给的那个实例,如下图: 仔细一看,原来最基本的作用就是对某些对象的属性.方法来 ...
- Java实现 LeetCode 712 两个字符串的最小ASCII删除和(最长公共子串&&ASCII值最小)
712. 两个字符串的最小ASCII删除和 给定两个字符串s1, s2,找到使两个字符串相等所需删除字符的ASCII值的最小和. 示例 1: 输入: s1 = "sea", s2 ...
- Java实现 LeetCode 136 只出现一次的数字
136. 只出现一次的数字 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次.找出那个只出现了一次的元素. 说明: 你的算法应该具有线性时间复杂度. 你可以不使用额外空间来实现 ...
- java实现第四届蓝桥杯公式求值
公式求值 输入n, m, k,输出图1所示的公式的值.其中C_n^m是组合数,表示在n个人的集合中选出m个人组成一个集合的方案数.组合数的计算公式如图2所示. 输入的第一行包含一个整数n:第二行包含一 ...
- Linux 文件特殊权限-SetUID
SetUID非常类似于Windows中以管理员身份来运行文件,针对的是可执行文件,而且命令执行者要拥有对这个文件的执行权限,只在文件执行的过程中变换身份,最常见的passwd命令就具有SetUID权限 ...
- Python学习之温度转换实例分析篇
#TempConvert.py Tempstr=input('请输入要转换的温度值:') if Tempstr[-1] in ['C','c']: F=1.8*eval(Tempstr[0:-1])+ ...
- (十一)DVWA全等级SQL Injection(Blind)盲注--手工测试过程解析
一.DVWA-SQL Injection(Blind)测试分析 SQL盲注 VS 普通SQL注入: 普通SQL注入 SQL盲注 1.执行SQL注入攻击时,服务器会响应来自数据库服务器的错误信息,信息提 ...