【Spring从成神到升仙系列 四】从源码分析 Spring 事务的来龙去脉
- 👏作者简介:大家好,我是爱敲代码的小黄,独角兽企业的Java开发工程师,CSDN博客专家,阿里云专家博主
- 📕系列专栏:Java设计模式、数据结构和算法、Kafka从入门到成神、Kafka从成神到升仙、Spring从成神到升仙系列
- 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
- 🍂博主正在努力完成2023计划中:以梦为马,扬帆起航,2023追梦人
- 📝联系方式:hls1793929520,加我进群,大家一起学习,一起进步👀
文章目录
- Spring 事务源码解析
- 一、引言
- 二、事务的本质
- 1、JDBC的事务
- 2、Spring的事务
- 2.1 xml配置
- 2.2 注解配置
- 三、Spring事务源码剖析
- 1、TransactionManager
- 1.1 获取事务
- 1.2 提交事务
- 1.3 回滚事务
- 2、 事务AOP的实现
- 2.1 为什么使用AOP?
- 2.2 @EnableTransactionManagement
- 2.3 TransactionInterceptor
- 2.4 XML配置
- 四、流程图
- 五、总结
Spring 事务源码解析
一、引言
对于Java开发者而言,关于 Spring
,我们一般当做黑盒来进行使用,不需要去打开这个黑盒。
但随着目前程序员行业的发展,我们有必要打开这个黑盒,去探索其中的奥妙。
本期 Spring
源码解析系列文章,将带你领略 Spring
源码的奥秘
本期源码文章吸收了之前 Kafka
源码文章的错误,将不再一行一行的带大家分析源码,我们将一些不重要的部分当做黑盒处理,以便我们更快、更有效的阅读源码。
废话不多说,发车!
本篇目录如下:
本文流程图可关注公众号:爱敲代码的小黄,回复:事务 获取
贴心的小黄为大家准备的文件格式为 POS文件,方便大家直接导入 ProcessOn 修改使用
二、事务的本质
数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。
事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。
一个逻辑工作单元要成为事务,必须满足所谓的 ACID
(原子性、一致性、隔离性和持久性)属性。事务是数据库运行中的逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。
1、JDBC的事务
我们来看一下在 JDBC 中对事务的操作处理:
public class JDBCTransactionExample {public static void main(String[] args) {Connection conn = null;PreparedStatement pstmt1 = null;PreparedStatement pstmt2 = null;try {// 加载驱动Class.forName("com.mysql.jdbc.Driver");// 获取连接conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "password");// 关闭自动提交,开启事务conn.setAutoCommit(false);// 创建SQL语句String sql1 = "UPDATE account SET balance = balance - ? WHERE id = ?";String sql2 = "UPDATE account SET balance = balance + ? WHERE id = ?";// 创建PreparedStatement对象pstmt1 = conn.prepareStatement(sql1);pstmt2 = conn.prepareStatement(sql2);// 设置参数pstmt1.setDouble(1, 1000);pstmt1.setInt(2, 1);pstmt2.setDouble(1, 1000);pstmt2.setInt(2, 2);// 执行更新操作int count1 = pstmt1.executeUpdate();int count2 = pstmt2.executeUpdate();if (count1 > 0 && count2 > 0) {System.out.println("转账成功");// 提交事务conn.commit();} else {System.out.println("转账失败");// 回滚事务conn.rollback();}} catch (ClassNotFoundException e) {e.printStackTrace();} catch (SQLException e) {try {if (conn != null) {// 回滚事务conn.rollback();}} catch (SQLException e1) {e1.printStackTrace();}e.printStackTrace();} finally {try {if (pstmt1 != null) {pstmt1.close();}if (pstmt2 != null) {pstmt2.close();}if (conn != null) {conn.close();}} catch (SQLException e) {e.printStackTrace();}}}
}
上面的代码,我相信大部分的人都应该接触过,这里也就不多说了
主要我们看几个重点步骤:
- 获取连接:
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "password")
- 关闭自动提交,开启事务:
conn.setAutoCommit(false)
- 提交事务:
conn.commit()
- 回滚事务:
conn.rollback()
2、Spring的事务
我们在日常生产项目中,项目由 Controller
、Serivce
、Dao
三层进行构建。
我们从上图中可以了解到:
对于 addUser
方法实际对于数据调用来说,分别调用了 insertUser()
、insertLog
方法,对数据库的操作为两次
我们要保证 addUser
方法是符合事务定义的。
2.1 xml配置
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:p="http://www.springframework.org/schema/p"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd"><!-- 开启扫描 --><context:component-scan base-package="com.dpb.*"></context:component-scan><!-- 配置数据源 --><bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource"><property name="url" value="jdbc:oracle:thin:@localhost:1521:orcl"/><property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/><property name="username" value="pms"/><property name="password" value="pms"/></bean><!-- 配置JdbcTemplate --><bean class="org.springframework.jdbc.core.JdbcTemplate" ><constructor-arg name="dataSource" ref="dataSource"/></bean><!-- Spring中,使用XML配置事务三大步骤: 1. 创建事务管理器 2. 配置事务方法 3. 配置AOP--><bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager"><property name="dataSource" ref="dataSource"/></bean><tx:advice id="advice" transaction-manager="transactionManager"><tx:attributes><tx:method name="fun*" propagation="REQUIRED"/></tx:attributes></tx:advice><!-- aop配置 --><aop:config><aop:pointcut expression="execution(* *..service.*.*(..))" id="tx"/><aop:advisor advice-ref="advice" pointcut-ref="tx"/></aop:config>
</beans>
2.2 注解配置
首先必须要添加 @EnableTransactionManagement
注解,保证事务注解生效
@EnableTransactionManagement
public class AnnotationMain {public static void main(String[] args) {}
}
其次,在方法上添加 @Transactional
代表注解生效
@Transactional
public int insertUser(User user) {userDao.insertUser();userDao.insertLog();return 1;
}
上面的操作涉及两个重点:
-
事务的传播属性
-
事务的隔离级别
三、Spring事务源码剖析
本次剖析源码我们会尽量挑重点来讲,因为事务源码本身就是依靠 AOP
实现的,我们之前已经很详细的讲过 IOC
和 AOP
的源码实现了,这次带大家一起过一遍事务即可。
因为从博主本身而言,我感觉 Spring
事务其实没有那么的重要,面试也不常考,所以不会花大量的时间去剖析细节源码。
1、TransactionManager
首先我们看一下这个接口的一些组成配置:
****
这里我们重点看 PlatformTransactionManager
的实现,其实现一共三个方法:
- 获取事务:
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
- 提交事务:
void commit(TransactionStatus status)
- 回滚事务:
void rollback(TransactionStatus status)
我们分别看一下其如何实现的
1.1 获取事务
我们想一下,在获取事务这一阶段,我们会做什么功能呢?
参考上述我们 JDBC
的步骤,这个阶段应该会 创建连接并且开启事务
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition){// PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED都需要新建事务if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {//没有当前事务的话,REQUIRED,REQUIRES_NEW,NESTED挂起的是空事务,然后创建一个新事务SuspendedResourcesHolder suspendedResources = suspend(null);try {// 看这里重点:开始事务return startTransaction(def, transaction, debugEnabled, suspendedResources);}catch (RuntimeException | Error ex) {// 恢复挂起的事务resume(null, suspendedResources);throw ex;}}
}private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction, boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {// 是否需要新同步boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);// 创建新的事务DefaultTransactionStatus status = newTransactionStatus( definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);// 【重点】开启事务和连接doBegin(transaction, definition);// 新同步事务的设置,针对于当前线程的设置prepareSynchronization(status, definition);return status;
}protected void doBegin(Object transaction, TransactionDefinition definition) {// 判断事务对象没有数据库连接持有器if (!txObject.hasConnectionHolder() ||txObject.getConnectionHolder().isSynchronizedWithTransaction()) {// 【重点】通过数据源获取一个数据库连接对象Connection newCon = obtainDataSource().getConnection();// 把我们的数据库连接包装成一个ConnectionHolder对象 然后设置到我们的txObject对象中去// 再次进来时,该 txObject 就已经有事务配置了txObject.setConnectionHolder(new ConnectionHolder(newCon), true);}// 【重点】获取连接con = txObject.getConnectionHolder().getConnection();// 为当前的事务设置隔离级别【数据库的隔离级别】Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);// 设置先前隔离级别txObject.setPreviousIsolationLevel(previousIsolationLevel);// 设置是否只读txObject.setReadOnly(definition.isReadOnly());// 关闭自动提交if (con.getAutoCommit()) {//设置需要恢复自动提交txObject.setMustRestoreAutoCommit(true);// 【重点】关闭自动提交con.setAutoCommit(false);}// 判断事务是否需要设置为只读事务prepareTransactionalConnection(con, definition);// 标记激活事务txObject.getConnectionHolder().setTransactionActive(true);// 设置事务超时时间int timeout = determineTimeout(definition);if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {txObject.getConnectionHolder().setTimeoutInSeconds(timeout);}// 绑定我们的数据源和连接到我们的同步管理器上,把数据源作为key,数据库连接作为value 设置到线程变量中if (txObject.isNewConnectionHolder()) {// 将当前获取到的连接绑定到当前线程TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());}}
}
到这里,我们的 获取事务
接口完成了 数据库连接的创建
和 关闭自动提交(开启事务)
,将 Connection
注册到了缓存(resources
)当中,便于获取。
1.2 提交事务
public final void commit(TransactionStatus status) throws TransactionException {DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;// 如果在事务链中已经被标记回滚,那么不会尝试提交事务,直接回滚if (defStatus.isLocalRollbackOnly()) {// 不可预期的回滚processRollback(defStatus, false);return;}// 设置了全局回滚if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {// 可预期的回滚,可能会报异常processRollback(defStatus, true);return;}// 【重点】处理事务提交processCommit(defStatus);
}// 处理提交,先处理保存点,然后处理新事务,如果不是新事务不会真正提交,要等外层是新事务的才提交,
// 最后根据条件执行数据清除,线程的私有资源解绑,重置连接自动提交,隔离级别,是否只读,释放连接,恢复挂起事务等
private void processCommit(DefaultTransactionStatus status) throws TransactionException {;// 如果是独立的事务则直接提交doCommit(status);//根据条件,完成后数据清除,和线程的私有资源解绑,重置连接自动提交,隔离级别,是否只读,释放连接,恢复挂起事务等cleanupAfterCompletion(status);
}
这里比较重要的有两个步骤:
-
doCommit
:提交事务(直接使用 JDBC 提交即可)protected void doCommit(DefaultTransactionStatus status) {DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();Connection con = txObject.getConnectionHolder().getConnection();try {// JDBC连接提交con.commit();}catch (SQLException ex) {throw new TransactionSystemException("Could not commit JDBC transaction", ex);} }
-
cleanupAfterCompletion
:数据清除,与线程中的私有资源解绑,方便释放// 线程同步状态清除 TransactionSynchronizationManager.clear();// 清除同步状态【这些都是线程的缓存,使用ThreadLocal的】 public static void clear() {synchronizations.remove();currentTransactionName.remove();currentTransactionReadOnly.remove();currentTransactionIsolationLevel.remove();actualTransactionActive.remove(); } // 如果是新事务的话,进行数据清除,线程的私有资源解绑,重置连接自动提交,隔离级别,是否只读,释放连接等 doCleanupAfterCompletion(status.getTransaction());// 此方法做清除连接相关操作,比如重置自动提交啊,只读属性啊,解绑数据源啊,释放连接啊,清除链接持有器属性 protected void doCleanupAfterCompletion(Object transaction) {DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;// 将数据库连接从当前线程中解除绑定TransactionSynchronizationManager.unbindResource(obtainDataSource());// 释放连接Connection con = txObject.getConnectionHolder().getConnection();// 恢复数据库连接的自动提交属性con.setAutoCommit(true);// 重置数据库连接DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel(), txObject.isReadOnly());// 如果当前事务是独立的新创建的事务则在事务完成时释放数据库连接DataSourceUtils.releaseConnection(con, this.dataSource);// 连接持有器属性清除txObject.getConnectionHolder().clear();}
这就是我们提交事务的操作了,总之来说,主要就是 调用JDBC的commit提交
和 清除一系列的线程内部数据和配置
1.3 回滚事务
public final void rollback(TransactionStatus status) throws TransactionException {DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;processRollback(defStatus, false);
}private void processRollback(DefaultTransactionStatus status, boolean unexpected) {// 回滚的擦欧洲哦doRollback(status);// 回滚完成后回调triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);// 根据事务状态信息,完成后数据清除,和线程的私有资源解绑,重置连接自动提交,隔离级别,是否只读,释放连接,恢复挂起事务等cleanupAfterCompletion(status);
}protected void doRollback(DefaultTransactionStatus status) {DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();Connection con = txObject.getConnectionHolder().getConnection();// jdbc的回滚con.rollback();
}
回滚事务,简单来说 调用JDBC的rollback
和 清除数据
2、 事务AOP的实现
我们来思考一下,利用 TransactionManager
重写一下我们第一步的 JDBC
的功能
@Autowiredprivate UserDao userDao;@Autowiredprivate PlatformTransactionManager txManager;@Autowiredprivate LogService logService;@Transactionalpublic void insertUser(User u) {// 1、创建事务定义DefaultTransactionDefinition definition = new DefaultTransactionDefinition();// 2、根据定义开启事务TransactionStatus status = txManager.getTransaction(definition);try {this.userDao.insert(u);Log log = new Log(System.currentTimeMillis() + "", System.currentTimeMillis() + "-" + u.getUserName());// this.doAddUser(u);this.logService.insertLog(log);// 3、提交事务txManager.commit(status);} catch (Exception e) {// 4、异常了,回滚事务txManager.rollback(status);throw e;}}
大家看到上述代码及思考一下 AOP
的作用和源码,有没有一丝丝想法
比如我现在是面试官,问你一个问题:你怎么去设计 Spring 的事务?
如果一点点想法都没有的话,也不用着急,我们来慢慢的分析
2.1 为什么使用AOP?
我们想,如果我们要实现事务,在每一个方法里面都需要进行以下三个步骤:
- 获取事务
- 提交事务
- 回滚事务
是不是显得我们的代码很臃肿,那么我们能不能把这三个步骤搞成一个通用化的东西
一些代码在方法前执行,一些代码方法后执行
这个时候,你是不是就想到了 AOP
(切面编程)了
当然,Spring
中也是如此做的,利用 AOP
来对事务进行了统一的包装实现
2.2 @EnableTransactionManagement
我们知道了使用 AOP
技术实现,那到底是如何实现的呢?
我们从 @EnableTransactionManagement
注解聊起,我们点进该注解:
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
很明显,TransactionManagementConfigurationSelector
类是我们主要关注的内容
public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {/*** 此处是AdviceMode的作用,默认是用代理,另外一个是ASPECTJ*/@Overrideprotected 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;}}
}
一共注册了两个:
-
AutoProxyRegistrar.class
:注册AOP处理器 -
ProxyTransactionManagementConfiguration.class
:代理事务配置,注册事务需要用的一些类,而且Role=ROLE_INFRASTRUCTURE都是属于内部级别的@Configuration(proxyBeanMethods = false) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) {// 【重点】注册了 BeanFactoryTransactionAttributeSourceAdvisor 的 advisor// 其 advice 为 transactionInterceptorBeanFactoryTransactionAttributeSourceAdvisor 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(TransactionAttributeSource transactionAttributeSource) {TransactionInterceptor interceptor = new TransactionInterceptor();interceptor.setTransactionAttributeSource(transactionAttributeSource);if (this.txManager != null) {interceptor.setTransactionManager(this.txManager);}return interceptor;}}
到这里,看到
BeanFactoryTransactionAttributeSourceAdvisor
以advisor
结尾的类,AOP
的DNA
应该动了,其advice
为 transactionInterceptor到这里,我们思考一下,利用我们之前学习到的
AOP
的源码,猜测其运行逻辑:- 我们在方法上写上
@EnableTransactionManagement
注解,Spring 会注册一个BeanFactoryTransactionAttributeSourceAdvisor
的类 - 创建对应的方法
Bean
时,会和AOP
一样,利用该Advisor
类生成对应的代理对象 - 最终调用方法时,会调用代理对象,并通过环绕增强来达到事务的功能
- 我们在方法上写上
2.3 TransactionInterceptor
我们从上面看到其 advice
正是 TransactionInterceptor
,那自然不用多说
从上篇 AOP
得知,代理对象运行时,会拿到所有的 advice
并进行排序,责任链递归运行
所以,我们直接看 TransactionInterceptor
这个类即可
这里先看一下 TransactionInterceptor
类图
然后看其源码内容:
这里的 invoke 方法怎么执行到的,我就不多介绍了,看过上篇 AOP
的文章应该都懂,不熟悉的小伙伴可以去看一下
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor {@Override@Nullablepublic Object invoke(MethodInvocation invocation) throws Throwable {// 获取我们的代理对象的class属性Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);// Adapt to TransactionAspectSupport's invokeWithinTransaction.../*** 以事务的方式调用目标方法* 在这埋了一个钩子函数 用来回调目标方法的*/return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);}
}@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation){// 获取我们的事务属性源对象TransactionAttributeSource tas = getTransactionAttributeSource();// 通过事务属性源对象获取到当前方法的事务属性信息final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);// 获取我们配置的事务管理器对象final TransactionManager tm = determineTransactionManager(txAttr);if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {// 【重点】创建TransactionInfoTransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);try {// 执行被增强方法,调用具体的处理逻辑【我们实际的方法】retVal = invocation.proceedWithInvocation();}catch (Throwable ex) {// 异常回滚completeTransactionAfterThrowing(txInfo, ex);throw ex;}finally {//清除事务信息,恢复线程私有的老的事务信息cleanupTransactionInfo(txInfo);}//成功后提交,会进行资源储量,连接释放,恢复挂起事务等操作commitTransactionAfterReturning(txInfo);return retVal;}
}// 创建连接 + 开启事务
protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {// 获取TransactionStatus事务状态信息status = tm.getTransaction(txAttr);// 根据指定的属性与status准备一个TransactionInfo,return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}// 存在异常时回滚事务
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {// 进行回滚txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}// 调用事务管理器的提交方法
protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo){txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
然后…没有然后了
事务就是这样简单、朴实无华的实现了
2.4 XML配置
这里稍微讲下 xml
配置的,xml
也一样,在我们进行 xml
文件解析的时候,将 BeanFactoryTransactionAttributeSourceAdvisor
组装起来注册到 BeanDefinitionMap
中
这里可以参考上篇 AOP
的解析和生成
生成后也一样,调用的是代理方法,判断改方法有没有被代理,然后递归+责任链执行 Advice
即可
没什么困难的
四、流程图
大家有没有感觉事务有点水,就是用了 Spring AOP
的功能包装了一下
根本的实现还是依靠的 JDBC
,但有一些不得不记的小知识点:事务的传播属性
和 事务的隔离级别
这两个关于配置的还是要去看一下,我们本篇就不再多叙述了,网上好多资料都有的
我们画一下基本的流程图
整个流程也相较于简单,有兴趣的可以去更细的看一下
五、总结
记得校招时候,当时对 Spring 懵懂无知,转眼间也被迫看了源码
本文主要从 JDBC
组装事务过度到 Spring
的事务注解,最终通过 AOP
的技术去进行切面处理
通过这篇文章,我相信,99% 的人应该都可以理解了 Spring
事务 的来龙去脉
那么如何证明你真的理解了 Spring
事务呢,我这里出个经典的题目,大家可以想一下:如果让你设计Spring中事务的流程,你会如何设计?
如果你能看到这,那博主必须要给你一个大大的鼓励,谢谢你的支持!
喜欢的可以点个关注,后续会更新 Spring 循环依赖
的源码文章
我是爱敲代码的小黄,独角兽企业的Java开发工程师,CSDN博客专家,Java领域新星创作者,喜欢后端架构和中间件源码。
相关文章:
![](https://img-blog.csdnimg.cn/2ab0ad51581f4dd9854484b124b593bc.png#pic_center)
【Spring从成神到升仙系列 四】从源码分析 Spring 事务的来龙去脉
👏作者简介:大家好,我是爱敲代码的小黄,独角兽企业的Java开发工程师,CSDN博客专家,阿里云专家博主📕系列专栏:Java设计模式、数据结构和算法、Kafka从入门到成神、Kafka从成神到升仙…...
![](https://www.ngui.cc/images/no-images.jpg)
使用Nginx反向代理OpenAI API
由于OpenAI的API在国内无法访问,所以可以通过海外服务器利用Nginx实现反向代理。 安装Nginx 这一步就不赘述了,不同的Linux系统安装方式略有不同,根据自己的服务器的系统自行百度即可。 OpenSSL创建证书 因为OpenAI的接口是https协议的&a…...
![](https://www.ngui.cc/images/no-images.jpg)
USB键盘实现——字符串描述符(四)
字符串描述符 字符串描述符内容解析和 HID鼠标 一致。 获取字符串描述符请求 标准设备请求 typedef struct __attribute__ ((packed)){union {struct __attribute__ ((packed)) {uint8_t recipient : 5; ///< Recipient type usb_request_recipient_t.uint8_t type …...
![](https://img-blog.csdnimg.cn/c7c0b0f9a0b94e85b4d19fd9380abbaa.png)
STM32的中断
目录 一、STM32中断概述 二、外部中断控制器EXTI 三、按键中断 四、串口中断 一、STM32中断概述 处理器中的中断在处理器中,中断是一个过程,即CPU在正常执行程序的过程中,遇到外部/内部的紧急事件需要处理,暂时中止当前程序的…...
![](https://img-blog.csdnimg.cn/img_convert/9fb0ae970598948cf0118967484a7dd5.png)
Flink进阶篇-CDC 原理、实践和优化采集到Doris中
简介 基于doris官方用doris构建实时仓库的思路,从flinkcdc到doris实时数仓的实践。 原文 Apache Flink X Apache Doris 构建极速易用的实时数仓架构 (qq.com) 前提-Flink CDC 原理、实践和优化 CDC 是什么 CDC 是变更数据捕获(Change Data Captur…...
![](https://img-blog.csdnimg.cn/e44f8d0328af4774a9442018443b5b1b.png)
看完这篇 教你玩转渗透测试靶机vulnhub——My File Server: 1
Vulnhub靶机My File Server: 1渗透测试详解Vulnhub靶机介绍:Vulnhub靶机下载:Vulnhub靶机安装:Vulnhub靶机漏洞详解:①:信息收集:②:FTP匿名登入:③:SMB共享服务…...
![](https://img-blog.csdnimg.cn/c927c3b482934c78b29b98758ac37782.png)
OpenHarmony实战STM32MP157开发板 “控制” Hi3861开发板 -- 中篇
一、前言 我们在 OpenHarmony实战STM32MP157开发板 “控制” Hi3861开发板 – 上篇 中介绍到了,App面板的开发,以及JS API接口的开发和调用。 那么本篇文章,会详解:BearPi-HM Nano开发板,如何实现数据上报和指令接收响应的。 看到这里,可能有同学可能已经知道思路了,因…...
![](https://img-blog.csdnimg.cn/c06ad346c63d4a97b5d0a048ef90a579.png)
【数据结构初阶】单链表
目录一、思路>>>>>>>>>>>>过程<<<<<<<<<<<<<<<1.打印2.尾插3.尾删4.头插5.头删6.查找7.指定位置后插入8.指定位置后删除9.链表的销毁二、整个程序1.SLTlist.c2.SLTlist.c一、思路 #define …...
![](https://img-blog.csdnimg.cn/7317382bf6874f668db994c998cfbd3e.png)
多线程代码案例-阻塞队列
hi,大家好,今天为大家带来多线程案例--阻塞队列 这块知识点也很重要,要好好掌握呀~~~ 🌸🌸🌸🌸🌸🌸🌸🌸🌸🌸🌸🌸🌸&#x…...
![](https://img-blog.csdnimg.cn/be50904ead4643fb9dc7ea227f8661af.png)
mysql的limit查询竟然有坑?
背景 最近项目联调的时候发现了分页查询的一个bug,分页查询总有数据查不出来或者重复查出。 数据库一共14条记录。 如果按照一页10条。那么第一页和第二页的查询SQL和和结果如下。 .png) 那么问题来了,查询第一页和第二页的时候都出现了11,12,13的记录…...
![](https://img-blog.csdnimg.cn/470cfa1f81d74f9891c5f5b9cbd1637d.png)
【Docker】MAC电脑下的Docker操作
文章目录安装Docker部署mysql 一主一从登录ChatGPT搞方案本地创建一个文件夹编辑docker-compose.yml文件启动检查并编排容器验证基于command的my.cnf配置的加载主数据库建一个用户给子数据库用于主从复制启动主从同步安装Docker 官网地址 https://www.docker.com/ 下载安装 验…...
![](https://img-blog.csdnimg.cn/51b40c4de18d4b7894f10ecb3fe715aa.png)
【Python3】matplotlib,模块,进/线程,文件/xml,百度人脸api,hal/aiohttp/curl
文章目录1.matplotlib/时间复杂度/线性表:顺序表要求存储空间必须连续2.python模块导入:python3 -c ‘import sys;print(sys.path)’ 显示导入模块时会去哪些路径下查找3.进/线程:进/线程是不能随便创建,就像每招一个员工是有代价…...
![](https://img-blog.csdnimg.cn/2010662931c44862966652ac3077cf50.png)
异或相关算法
文章目录1. 异或的性质2. 题目一3. 题目二4. 题目三5. 题目四1. 异或的性质 我们知道,异或的定义是:相同为0,相异为1。所以也被称为无进位相加,根据这定义,我们可以得出三个性质: 1. N ^ N0。2. N ^ 0N。3…...
![](https://www.ngui.cc/images/no-images.jpg)
python 使用pyshp读写shp文件
安装 pip install pyshp 引入 import shapefile读取 sfshapefile.Reader("{路径名}",encodingutf-8) # 仅仅读取 shapes与shape shapessf.shapes() 返回值是一个列表,包含该文件中所有的”几何数据”对象shapesf.shape(0) Shape是第1个”几何数据”…...
![](https://img-blog.csdnimg.cn/img_convert/fa1e64e1415b3d6c63c531376e1f4366.png)
eNSP FTP基础配置实验
关于本实验在本实验中,我们通过两台路由器来展示通过FTP在两台路由器之间传输文件。其中一台路由器AR2作为FTP服务器,另一台路由器AR1以FTP的方式登录AR2,并对AR2的文件系统进行一些更改。实验目的熟悉华为网络设备文件系统的管理。掌握华为网…...
![](https://img-blog.csdnimg.cn/865ac09e3efe4410b562094c2745c25a.png)
堆及其多种接口与堆排序的实现
我们本期来讲解堆结构 目录 堆的结构 堆的初始化 堆的销毁 堆的插入 向上调整算法 堆的删除 向下调整算法 取堆顶元素 判断堆是否为空 堆中元素个数 堆排序 向下调整与向上调整效率计算 Top-K问题 全部代码 堆的结构 堆是一种用数组模拟二叉树的结构 逻辑结构是…...
![](https://img-blog.csdnimg.cn/img_convert/fb726af0e444019feb1268123d3d68c9.webp?x-oss-process=image/format,png)
JNI原理及常用方法概述
1.1 JNI(Java Native Interface) 提供一种Java字节码调用C/C的解决方案,JNI描述的是一种技术。 1.2 NDK(Native Development Kit) Android NDK 是一组允许您将 C 或 C(“原生代码”)嵌入到 Android 应用中的工具,NDK描述的是工具集…...
![](https://img-blog.csdnimg.cn/05a6b930e9b74990a89e1f3936064a8c.png)
【Docker】之docker-compose的介绍与命令的使用
🍁博主简介 🏅云计算领域优质创作者 🏅华为云开发者社区专家博主 🏅阿里云开发者社区专家博主 💊交流社区:运维交流社区 欢迎大家的加入! 文章目录docker-compose简介docker-compose基础…...
![](https://img-blog.csdnimg.cn/9431052ba024472f9901b1e774aac902.png#pic_center)
水果新鲜程度检测系统(UI界面+YOLOv5+训练数据集)
摘要:水果新鲜程度检测软件用于检测水果新鲜程度,利用深度学习技术识别腐败或损坏的水果,以辅助挑拣出新鲜水果,支持实时在线检测。本文详细介绍水果新鲜程度检测系统,在介绍算法原理的同时,给出Python的实…...
![](https://www.ngui.cc/images/no-images.jpg)
flask多并发
多线程 flask默认使用多进程处理请求,因此,是支持并发的。比如两个调用a.html和b.html, 请求a.html未运行完成,在浏览访问b.html不会阻塞。开两个不同浏览器,分别请求请求运行时间较长的a.html也不阻塞。只要不用一个…...
![](https://img-blog.csdnimg.cn/img_convert/fa4cd871faaa4b938b3dac5bf7973a12.jpeg)
我用Python django开发了一个商城系统,已开源,求关注!
起始 2022年我用django开发了一个商城的第三方包,起名为:django-happy-shop。当时纯粹是利用业余时间来开发和维护这个包,想法也比较简单,Python语言做web可能用的人比较少,不一定有多少人去关注,就当是一个…...
![](https://img-blog.csdnimg.cn/58a03d4d7be14fbf99e7c26938fc700f.png)
大数据项目之数仓相关知识
第1章 数据仓库概念 数据仓库(DW): 为企业指定决策,提供数据支持的,帮助企业,改进业务流程,提高产品质量等。 DW的输入数据通常包括:业务数据,用户行为数据和爬虫数据等 ODS: 数据…...
![](https://img-blog.csdnimg.cn/f5d9ea1eaf0b46b99818aa9471b2ee10.jpeg)
RK3588平台开发系列讲解(视频篇)RTP H264 码流打包详解
平台内核版本安卓版本RK3588Linux 5.10Android 12文章目录 一、单 NALU 封包方式二、组合封包方式三、分片封包方式沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 H264 码流是放在 RTP 的有效载荷部分的。因此有效载荷前面的 RTP 头部跟码流本身是没有关系的,所以我…...
![](https://www.ngui.cc/images/no-images.jpg)
realloc的补充 柔性数组
🐶博主主页:ᰔᩚ. 一怀明月ꦿ ❤️🔥专栏系列:线性代数,C初学者入门训练,题解C,C的使用文章,「初学」C 🔥座右铭:“不要等到什么都没有了,才下…...
![](https://img-blog.csdnimg.cn/4632463a72104169838246cf21712270.png)
【C语言】柔性数组
柔性数组1. 柔性数组介绍2. 柔性数组特点3. 用例3.1 代码一:3.2 代码二:4. 柔性数组优势:1. 柔性数组介绍 也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。 C99 中,…...
![](https://img-blog.csdnimg.cn/img_convert/c6588e81487da374f3ce69ddffacbe4c.png)
【Linux】权限详解
前言首先我们先来看一下权限的概念:在多用户计算机系统的管理中,权限(privilege)是指某个特定的用户具有特定的系统资源使用权力,像是文件夹,特定系统指令的使用或存储量的限制。通常,系统管理员…...
![](https://img-blog.csdnimg.cn/img_convert/048293c67da93a9dafa3596fd8060e28.png)
Android 之 打开相机 打开相册
Android 之 打开系统摄像头拍照 打开系统相册,并展示1,清单文件 AndroidManifest.xml<uses-permission android:name"android.permission.INTERNET" /><!--文件读取权限--><uses-permission android:name"android.permiss…...
![](https://img-blog.csdnimg.cn/img_convert/b572dd1bd2640e00620327890f6002da.gif)
C语言数据结构初阶(8)----栈与队列OJ题
CSDN的uu们,大家好。这里是C语言数据结构的第八讲。 目标:前路坎坷,披荆斩棘,扶摇直上。 博客主页: 姬如祎 收录专栏:数据结构与算法栈与队列的知识点我➡➡队列相关点我➡➡栈相关2. 用栈实现队列原题链接…...
![](https://img-blog.csdnimg.cn/2304311dc8ef46d2972cccbe8ed3769a.png)
JavaScript——原型对象
JavaScript——原型对象专题 文章目录JavaScript——原型对象专题1. 原型对象2. 原型对象的this指向3. 案例4. constructor属性5. 对象原型6. 总结7. 原型继承8. 原型链由先前的学习可知,构造函数实例创建的对象彼此独立、互不影响,很好的体现了面向对象…...
![](https://img-blog.csdnimg.cn/img_convert/7a818b118d6e747f4db77f71a50dc446.png)
网络安全 2023 年为什么如此吃香?事实原来是这样....
前言由于我国网络安全起步晚,所以现在网络安全工程师十分紧缺。俗话说:没有网络安全就没有国家安全为什么选择网络安全?十四五发展规划建议明确提出建设网络强国,全面加强网络安全保障体系和能力建设,加强网络文明建设,…...
海南seo外包/搜索引擎优化
转载于:https://blog.csdn.net/prokgtfy9n18/article/details/68962512 在数组运算前,先了解一下数组的解引用到底是怎么回事。 #include <windows.h>#include <stdio.h>int main(){int arr[] {1,2,3,4,5,6,7,8,9,0};printf("%d\n&quo…...
![](https://img-blog.csdnimg.cn/1f9213186b4a42c78585cad05dd328b4.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiD5pyI55qE5bCP5bC-5be0,size_20,color_FFFFFF,t_70,g_se,x_16)
.net最新网站开发/无锡营销型网站建设
前言 是不是有很多小伙伴在做接口自动化的时候,大量的测试用例数据,写的即枯燥,有乏味呢? 那么下面你们的福利来啦~本文章会基于 mitmproxy python 做代理拦截,将我们拦截到的接口请求,转换成 .yaml 格式…...
![](https://img-blog.csdnimg.cn/img_convert/d77e78037821b13ae9de724aa69da236.png)
wordpress着陆页/安康seo
近几年,包含C语法,拥有高性能、高效率而且还易于上手的Go语言,自推出便受到了国内外开发者的欢迎,许多开发者将其列为最喜欢的语言之一。在《StackOverflow开发人员调查报告 2021》中,使用Go语言的开发者薪资排名进入前…...
![](/images/no-images.jpg)
宽城区网站建设/免费seo营销软件
近日使用VMware fushion 8 centos 7.0时,无法使用共享功能,所以必须安装vmtools。但是安装过程中有2个错误需要解决。 1、gcc错误 Searching for GCC... The path "" is not valid path to the gcc binary. 2、内核头文件问题 Searching for …...
![](/images/no-images.jpg)
wordpress 带数据/今日国际新闻头条15条
PHP递归实现无限分类数组处理1,php递归算法实现无限分类递归算法对无限分类的数组进行处理,分两种情况,一种树状展示(数组包含子数组),一种非树状展示(子类按照顺序排在父类的下面,不出现数组包含)注意:在查询数据的时候,也可以把…...
![](/images/no-images.jpg)
建设网站学习/sem公司
什么是SeabornSeaborn是基于matplotlib的图形可视化python包。它提供了一种高度交互式界面,便于用户能够做出各种有吸引力的统计图表。Seaborn是在matplotlib的基础上进行了更高级的API封装,从而使得作图更加容易,在大多数情况下使用seaborn能…...