Spring 事务原理总结四

作为一名认知有限的中国人,我对年的喜爱,胜过其他一切,因为它给了我拒绝一切的合理理由。每到这个时候,我都会用各种理由来为自己的不作为开脱,今年亦是如此。看着频频发出警报的假期余额,我内心的焦躁变得更加强烈。为了抚慰这烦人的情绪,我决定让自己静下来,继续梳理工作经常用到的Spring事务。通过前面三篇文章,我知道了事务的配置流程,也懂得了向Spring容器中注册事务的流程,更了解了Spring事务中的相关组件及其作用,但这依旧无法让我认识到这个知识点的全貌,所以希望通过今天的跟踪能完全了解Spring事务;也希望通过这次跟踪对Spring事务进行一次总结;更希望通过这次跟踪结束本系列,以为后面的学习腾出时间。

这里想跟大家说声对不起!在增加这段文字之前,下述描述中有这样一段:2.调用PlatformTransactionManager对象的getTransaction()方法(该方法需要一个TransactionAttribute对象,具体逻辑可以参考JdbcTransactionManager类的父类AbstractPlatformTransactionManager中的源码)获取TransactionStatus对象,具体如下图所示:

但根据实际的接口定义,getTransaction()方法接收的参数的类型为TransactionDefinition,而非TransactionAttribute,所以本次修改将更改这个错误!不过这里这么些也没有问题,因为TransactionAttribute继承了TransactionDefinition接口。关于这两个接口之间的继承关系,如果觉得本博客梳理的可以的同仁可以翻看一下《Spring 事务原理总结三》这篇文章。

回到第一篇文章中的案例(注意这个案例并没有真正操作数据库中的数据),在transferService.check("jack", "tom", BigDecimal.valueOf(100));这行代码处新增一个断点,然后运行代码,会看到下图所示的情况:

继续执行,会看到代码执行到了TransactionInterceptor#invoke(MethodInvocation invocation)方法中(这段逻辑在《Spring 事务原理总结二》这篇文章中有提到过,当时没有深入跟踪这段代码),如下图所示:

这里看到的参数MethodInvocation的实际类型为org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation,其中包含了许多信息,具体如下图所示:

接下来继续执行,会进入到TransactionInterceptor#invokeWithinTransaction (Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation) throws Throwable方法中(这段逻辑在《Spring 事务原理总结二》这篇文章中有提到过,当时没有深入跟踪这段代码,今天我们会详细看一下这段代码的具体逻辑),先来看一下这段逻辑的具体代码:

@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,final InvocationCallback invocation) throws Throwable {// If the transaction attribute is null, the method is non-transactional.TransactionAttributeSource tas = getTransactionAttributeSource();final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);final TransactionManager tm = determineTransactionManager(txAttr);if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager rtm) {boolean isSuspendingFunction = KotlinDetector.isSuspendingFunction(method);boolean hasSuspendingFlowReturnType = isSuspendingFunction &&COROUTINES_FLOW_CLASS_NAME.equals(new MethodParameter(method, -1).getParameterType().getName());if (isSuspendingFunction && !(invocation instanceof CoroutinesInvocationCallback)) {throw new IllegalStateException("Coroutines invocation not supported: " + method);}CoroutinesInvocationCallback corInv = (isSuspendingFunction ? (CoroutinesInvocationCallback) invocation : null);ReactiveTransactionSupport txSupport = this.transactionSupportCache.computeIfAbsent(method, key -> {Class<?> reactiveType =(isSuspendingFunction ? (hasSuspendingFlowReturnType ? Flux.class : Mono.class) : method.getReturnType());ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(reactiveType);if (adapter == null) {throw new IllegalStateException("Cannot apply reactive transaction to non-reactive return type: " +method.getReturnType());}return new ReactiveTransactionSupport(adapter);});InvocationCallback callback = invocation;if (corInv != null) {callback = () -> KotlinDelegate.invokeSuspendingFunction(method, corInv);}return txSupport.invokeWithinTransaction(method, targetClass, callback, txAttr, rtm);}PlatformTransactionManager ptm = asPlatformTransactionManager(tm);final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager cpptm)) {// Standard transaction demarcation with getTransaction and commit/rollback calls.TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);Object retVal;try {// This is an around advice: Invoke the next interceptor in the chain.// This will normally result in a target object being invoked.retVal = invocation.proceedWithInvocation();}catch (Throwable ex) {// target invocation exceptioncompleteTransactionAfterThrowing(txInfo, ex);throw ex;}finally {cleanupTransactionInfo(txInfo);}if (retVal != null && txAttr != null) {TransactionStatus status = txInfo.getTransactionStatus();if (status != null) {if (retVal instanceof Future<?> future && future.isDone()) {try {future.get();}catch (ExecutionException ex) {if (txAttr.rollbackOn(ex.getCause())) {status.setRollbackOnly();}}catch (InterruptedException ex) {Thread.currentThread().interrupt();}}else if (vavrPresent && VavrDelegate.isVavrTry(retVal)) {// Set rollback-only in case of Vavr failure matching our rollback rules...retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);}}}commitTransactionAfterReturning(txInfo);return retVal;}else {Object result;final ThrowableHolder throwableHolder = new ThrowableHolder();// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.try {result = cpptm.execute(txAttr, status -> {TransactionInfo txInfo = prepareTransactionInfo(ptm, txAttr, joinpointIdentification, status);try {Object retVal = invocation.proceedWithInvocation();if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {// Set rollback-only in case of Vavr failure matching our rollback rules...retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);}return retVal;}catch (Throwable ex) {if (txAttr.rollbackOn(ex)) {// A RuntimeException: will lead to a rollback.if (ex instanceof RuntimeException runtimeException) {throw runtimeException;}else {throw new ThrowableHolderException(ex);}}else {// A normal return value: will lead to a commit.throwableHolder.throwable = ex;return null;}}finally {cleanupTransactionInfo(txInfo);}});}catch (ThrowableHolderException ex) {throw ex.getCause();}catch (TransactionSystemException ex2) {if (throwableHolder.throwable != null) {logger.error("Application exception overridden by commit exception", throwableHolder.throwable);ex2.initApplicationException(throwableHolder.throwable);}throw ex2;}catch (Throwable ex2) {if (throwableHolder.throwable != null) {logger.error("Application exception overridden by commit exception", throwableHolder.throwable);}throw ex2;}// Check result state: It might indicate a Throwable to rethrow.if (throwableHolder.throwable != null) {throw throwableHolder.throwable;}return result;}
}

这个方法的第一行会首先获取一个TransactionAttributeSource对象(实际类型为AnnotationTransactionAttributeSrouce。这里多啰嗦几句,这个类是Spring中处理事务管理的一个类,它负责在基于注解的事务控制机制中解析方法级别的事务属性。在使用声明式事务管理时,比如通过@Transactional注解,其作用至关重要。具体来讲,当Spring发现一个类的方法上标注了@Transactional注解时,AnnotationTransactionAttributeSrouce会在这个类加载和初始化过程中被Spring AOP使用来解析该注解,并根据注解中的属性,如传播行为、隔离级别、回滚规则等,生成相应的事务属性对象,通常是TransactionAttribute的实例或其子类。这样当执行到被注解的方法时,Spring的事务代理能够根据这些属性来正确的管理和控制事务的声明周期,确保方法内数据库操作的原子性和一致性。),具体如下图所示:

接下来会从拿到的TransactionAttributeSource对象中拿到一个TransactionAttribute类型的对象,该对象中包含了许多数据,譬如事务超时时间、事务隔离级别、事务可读属性、事务传播行为等等,具体如下图所示:

接下来继续执行,会执行到第三行,这里的主要作用就是从Spring容器中拿到事务管理器对象(拿对象的详细过程可以研读determineTransactionManager()方法的代码,我们拿到的这个对象的实际类型为JdbcTransactionManager,关于该类的继承结构可以参考前一篇文章,即《Spring 事务原理总结三》),具体如下图所示:

接下来继续执行,会发现先跳过if分支,然后执行PlatformTransactionManager ptm = asPlatformTransactionManager(tm);这段代码,具体如下图所示:

关于asPlatformTransactionManager()方法的源码如下所示(这个方法的主要作用是判断当前的tm对象是否是PlatformTransactionManager类型,如果是则返回当前对象,如果不是则直接抛出参数非法异常——IllegalStateException)):

private PlatformTransactionManager asPlatformTransactionManager(@Nullable Object transactionManager) {if (transactionManager == null) {return null;}if (transactionManager instanceof PlatformTransactionManager ptm) {return ptm;}else {throw new IllegalStateException("Specified transaction manager is not a PlatformTransactionManager: " + transactionManager);}
}

继续执行,会到final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);这一行,个人理解这行代码的主要作用是获取当前正在执行的目标方法的签名,比如这里的org.com.chinasofti.springtransaction.service.TransferServiceImpl.check(这是我们自定义的check()方法,该方法上添加了@Transactional注解),详情参见下图:

接下来一起看一下if分支的判断逻辑,详细代码为:if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager cpptm)),这里首先会判断txAttr(它是一个TransactionAttribute类型的对象)是否为空,因为前面获取过,所以这里等于null的判断结果是false;再来看一下后半段,ptm是一个PlatformTransactionManager类型的对象,虽然这里的CallbackPreferringPlatformTransactionManager继承了PlatformTransactionManager接口,但是这里的实际类型JdbcTransactionManager并没有实现CallbackPreferringPlatformTransactionManager接口,所以后半段逻辑最终返回的结果是true,具体如下图所示:

下面详细看一下if分支中的第一句代码TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);,从字面大概可以猜出这段代码的主要作用就是创建一个TransactionInfo对象,下面来看一下这里涉及的两个方法的代码:

protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {// If no name specified, apply method identification as transaction name.if (txAttr != null && txAttr.getName() == null) {txAttr = new DelegatingTransactionAttribute(txAttr) {@Overridepublic String getName() {return joinpointIdentification;}};}TransactionStatus status = null;if (txAttr != null) {if (tm != null) {status = tm.getTransaction(txAttr);}else {if (logger.isDebugEnabled()) {logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +"] because no transaction manager has been configured");}}}return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}/*** Prepare a TransactionInfo for the given attribute and status object.* @param txAttr the TransactionAttribute (may be {@code null})* @param joinpointIdentification the fully qualified method name* (used for monitoring and logging purposes)* @param status the TransactionStatus for the current transaction* @return the prepared TransactionInfo object*/
protected TransactionInfo prepareTransactionInfo(@Nullable PlatformTransactionManager tm,@Nullable TransactionAttribute txAttr, String joinpointIdentification,@Nullable TransactionStatus status) {TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);if (txAttr != null) {// We need a transaction for this method...if (logger.isTraceEnabled()) {logger.trace("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]");}// The transaction manager will flag an error if an incompatible tx already exists.txInfo.newTransactionStatus(status);}else {// The TransactionInfo.hasTransaction() method will return false. We created it only// to preserve the integrity of the ThreadLocal stack maintained in this class.if (logger.isTraceEnabled()) {logger.trace("No need to create transaction for [" + joinpointIdentification +"]: This method is not transactional.");}}// We always bind the TransactionInfo to the thread, even if we didn't create// a new transaction here. This guarantees that the TransactionInfo stack// will be managed correctly even if no transaction was created by this aspect.txInfo.bindToThread();return txInfo;
}

通过阅读createTransactionIfNecessary()方法的源码不难发现,这个方法主要做了这样几件事情:1.将TransactionAttribute对象包装为DelegatingTransactionAttribute对象(重写该对象的getName()方法,返回org.com.chinasofti.springtransaction.service.TransferServiceImpl.check);2.调用PlatformTransactionManager对象的getTransaction()方法(该方法需要一个TransactionDefinition对象,具体逻辑可以参考JdbcTransactionManager类的父类AbstractPlatformTransactionManager中的源码)获取TransactionStatus对象;3.调用本类(TransactionAspectSupport)中的prepareTransactionInfo()方法(这个方法就是上面罗列的第二个方法的源码)。接下来看一下prepareTransactionInfo()方法的执行逻辑:1.创建TransactionInfo对象,该对象会持有TransactionAttribute、PlatformTransactionManager及目标方法签名;2.调用TransactionInfo对象上的newTransactionStatus()方法(该方法会接收一个TransactionStatus对象。该方法的主要目的就是将TransactionStatus对象赋值给TransactionInfo对象,也就是说该方法的主要逻辑就是赋值);3.调用TransactionInfo对象上的bindToThread()方法,将TransactionInfo对象绑定到当前线程上,该方法的源码如下所示:

private void bindToThread() {// Expose current TransactionStatus, preserving any existing TransactionStatus// for restoration after this transaction is complete.this.oldTransactionInfo = transactionInfoHolder.get();transactionInfoHolder.set(this);
}

注意:这里看到的源码(bindToThread()、createTransactionIfNecessary()、prepareTransactionInfo()包括TransactionInfo类)都位于TransactionAspectSupport类中。

接下来继续回到TransactionAspectSupport类的invokeWithinTransaction()方法中,直接来到cleanupTransactionInfo(txInfo)这段代码处,最终会调到TransactionInfo对象上的restoreThreadLocalStatus()方法,该方法源码为:

private void restoreThreadLocalStatus() {// Use stack to restore old transaction TransactionInfo.// Will be null if none was set.transactionInfoHolder.set(this.oldTransactionInfo);
}

这里又看到了transactionInfoHolder对象,这个对象是在TransactionAspectSupport对象中创建的,其实际类型就是NamedThreadLocal,说白了就是一个ThreadLocal(关于这个类的解释这里不再赘述,后面会专门写一篇文章对其进行介绍)。这里想在啰嗦几句,还记得前面创建TransactionInfo对象的代码吗?那里的主要目的就是想transactionInfoHolder对象中存放TransactionInfo对象(实际上就是操作TransactionAspectSupport对象中transactionInfoHolder对象)。

接下来继续向下走,会来到提交事务的代码处(commitTransactionAfterReturning(txInfo)),详细执行逻辑如下所示:

这个被调用的方法(commitTransactionAfterReturning(@Nullable TransactionInfo txInfo))的源码如下所示:

protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {if (txInfo != null && txInfo.getTransactionStatus() != null) {if (logger.isTraceEnabled()) {logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");}txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());}
}

从源码不难看出,事务提交的主要逻辑就是从TransactionInfo对象中拿到TransactionManager对象然后调用其上的commit()方法(注意:该方法会接收一个TransactionStatus对象),具体执行详情如下所示:

接下来一起看一下commit(TransactionStatus status)方法的源码吧,详情请参见下述源代码:

@Override
public final void commit(TransactionStatus status) throws TransactionException {if (status.isCompleted()) {throw new IllegalTransactionStateException("Transaction is already completed - do not call commit or rollback more than once per transaction");}DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;if (defStatus.isLocalRollbackOnly()) {if (defStatus.isDebug()) {logger.debug("Transactional code has requested rollback");}processRollback(defStatus, false);return;}if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {if (defStatus.isDebug()) {logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");}processRollback(defStatus, true);return;}processCommit(defStatus);
}

这个方法会首先判断当前事务是否需要执行回滚操作。如果需要,则继续调用pocessRollback()方法。如果不需要,则直接调用processCommit(DefaultTransactionStatus status)方法。首先来看一下processCommit()方法的源码:

private void processCommit(DefaultTransactionStatus status) throws TransactionException {try {boolean beforeCompletionInvoked = false;boolean commitListenerInvoked = false;try {boolean unexpectedRollback = false;prepareForCommit(status);triggerBeforeCommit(status);triggerBeforeCompletion(status);beforeCompletionInvoked = true;if (status.hasSavepoint()) {if (status.isDebug()) {logger.debug("Releasing transaction savepoint");}unexpectedRollback = status.isGlobalRollbackOnly();this.transactionExecutionListeners.forEach(listener -> listener.beforeCommit(status));commitListenerInvoked = true;status.releaseHeldSavepoint();}else if (status.isNewTransaction()) {if (status.isDebug()) {logger.debug("Initiating transaction commit");}unexpectedRollback = status.isGlobalRollbackOnly();this.transactionExecutionListeners.forEach(listener -> listener.beforeCommit(status));commitListenerInvoked = true;doCommit(status);}else if (isFailEarlyOnGlobalRollbackOnly()) {unexpectedRollback = status.isGlobalRollbackOnly();}// Throw UnexpectedRollbackException if we have a global rollback-only// marker but still didn't get a corresponding exception from commit.if (unexpectedRollback) {throw new UnexpectedRollbackException("Transaction silently rolled back because it has been marked as rollback-only");}}catch (UnexpectedRollbackException ex) {triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);this.transactionExecutionListeners.forEach(listener -> listener.afterRollback(status, null));throw ex;}catch (TransactionException ex) {if (isRollbackOnCommitFailure()) {doRollbackOnCommitException(status, ex);}else {triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);if (commitListenerInvoked) {this.transactionExecutionListeners.forEach(listener -> listener.afterCommit(status, ex));}}throw ex;}catch (RuntimeException | Error ex) {if (!beforeCompletionInvoked) {triggerBeforeCompletion(status);}doRollbackOnCommitException(status, ex);throw ex;}// Trigger afterCommit callbacks, with an exception thrown there// propagated to callers but the transaction still considered as committed.try {triggerAfterCommit(status);}finally {triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);if (commitListenerInvoked) {this.transactionExecutionListeners.forEach(listener -> listener.afterCommit(status, null));}}}finally {cleanupAfterCompletion(status);}
}

因为本次测试是一个正常流程,所以这里会直接走到doCommit(status)方法处,继续看这个方法的源码如下所示(注意这段源码位于DataSourceTransactionManager类中,关于其继承体系可以参考《Spring 事务原理总结三》这篇文章):

protected void doCommit(DefaultTransactionStatus status) {DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();Connection con = txObject.getConnectionHolder().getConnection();if (status.isDebug()) {logger.debug("Committing JDBC transaction on Connection [" + con + "]");}try {con.commit();}catch (SQLException ex) {throw translateException("JDBC commit", ex);}
}

从这里不难看出,其主要目的就是通过程序与数据库之间的连接对象来提交事务(数据库层面的事务),conn.commit(),这里我有个疑问当单独利用JDBC进行手动事务控制时,会有一个将当前事务设置为false的操作,比如conn.setAutoCommit(false),为什么这里没看到呢?再次梳理一下事务提交的执行流程:

  1. TransactionAspectSupport#commitTransactionAfterReturning (@Nullable TransactionInfo txInfo)
  2. AbstractPlatformTransactionManager#commit(TransactionStatus status)
  3. AbstractPlatformTransactionManager#processCommit(DefaultTransactionStatus status)
  4. DataSourceTransactionManager#doCommit(DefaultTransactionStatus status)

下面让我们继续回到TransactionAspectSupport#invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation)方法中,执行完commitTransactionAfterReturning()方法后,该方法就结束了(后面直接return retVal),详细执行过程参见下图:

至此我们把事务正常执行的流程梳理完了,不过这个过程中还遗留了几个小问题,下一篇博客我会对这些问题进行详细跟踪,这些问题分别是:

  1. 为什么这里没看到conn.setAutoCommit(false)?
  2. Spring事务异常回滚的执行流程是什么?
  3. Spring事务失效的场景有那些?
  4. 本篇文章中我们梳理了完整的流程,但是还有一个地方梳理的不够完整,即调用PlatformTransactionManager对象的getTransaction()方法(该方法需要一个TransactionAttribute对象,具体逻辑可以参考JdbcTransactionManager类的父类AbstractPlatformTransactionManager中的源码)获取TransactionStatus对象这个地方

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/470991.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机网络——多媒体网络

前些天发现了一个巨牛的人工智能学习网站 通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家&#xff0c; 跳转到网站 小程一言 我的计算机网络专栏&#xff0c;是自己在计算机网络学习过程中的学习笔记与心得&#xff0c;在参考相关教材&#xff0c;网络搜素…

Learn LaTeX 020 - LaTex Math Space Font 数学排版之空格、字号、字体

数学排版中很好的处理空格、字号和字体可以使你的出版文档平添更多的特色。 这个视频介绍并演示了这些方面的相关配置。 https://www.ixigua.com/7298100920137548288?id7307759620737466891&logTagb138f9145ce004f6b52a

林浩然与杨凌芸的Java奇缘:包装类间的“恋爱”游戏

林浩然与杨凌芸的Java奇缘&#xff1a;包装类间的“恋爱”游戏 Lin Haoran and Yang Lingyun’s Java Adventure: The “Romance” Game of Wrapper Classes 在一个充满二进制和咖啡香的午后&#xff0c;程序员界的才子林浩然与机智女神杨凌芸正在进行一场别开生面的编程对话。…

optuna,一个好用的Python机器学习自动化超参数优化库

🏷️个人主页:鼠鼠我捏,要死了捏的主页 🏷️付费专栏:Python专栏 🏷️个人学习笔记,若有缺误,欢迎评论区指正 前言 超参数优化是机器学习中的重要问题,它涉及在训练模型时选择最优的超参数组合,以提高模型的性能和泛化能力。Optuna是一个用于自动化超参数优化的…

学法减分线上考试答案查找?分享九个搜题直接出答案的软件 #媒体#媒体#笔记

在信息爆炸的时代&#xff0c;选择适合自己的学习辅助工具和资料&#xff0c;能够提供更高效、便捷和多样化的学习方式。 1.试题猪 这是个微信公众号 一款聚合了好多款搜题软件的公众号&#xff0c;对话框可以直接搜题&#xff0c;题库好像挺多的&#xff0c;一次性能出好多…

(11)Hive调优——explain执行计划

一、explain查询计划概述 explain将Hive SQL 语句的实现步骤、依赖关系进行解析&#xff0c;帮助用户理解一条HQL 语句在底层是如何实现数据的查询及处理&#xff0c;通过分析执行计划来达到Hive 调优&#xff0c;数据倾斜排查等目的。 官网指路&#xff1a; https://cwiki.ap…

一站式安装对应显卡版本的cuda和torch(windows)

前言 一年前&#xff0c;安装过cuda&#xff0c;觉得并不难&#xff0c;就没有记录。 这次安装还算顺利&#xff0c;就是在找资料的时候&#xff0c;浪费了不少时间 这次就记录下来&#xff0c;方便以后再次安装 总结安装程序&#xff1a; 1、安装python环境 2、安装VS的C环境&…

vue axios 请求后端无法传参问题

vue请求后端无法传参问题 问题描述处理过程总结 问题描述 在学习vue时&#xff0c;使用axios调用后端&#xff0c;发现无法把参数正确传到后端&#xff0c;现象如下&#xff1a; 使用vue发起请求&#xff0c;浏览器上已经有传参&#xff0c;但是后端没接收到对应的用户名密码&…

springboot185基于vue.js的客户关系管理系统(crm)的设计与实现

简介 【毕设源码推荐 javaweb 项目】基于springbootvue 的 适用于计算机类毕业设计&#xff0c;课程设计参考与学习用途。仅供学习参考&#xff0c; 不得用于商业或者非法用途&#xff0c;否则&#xff0c;一切后果请用户自负。 看运行截图看 第五章 第四章 获取资料方式 **项…

【stm32】hal库学习笔记-DAC数模转换(超详细!)

【stm32】hal库学习笔记-DAC数模转换&#xff08;超详细&#xff01;&#xff09; DAC功能概述 DAC&#xff1a;将数字信号转换为模拟信号 并行式 分辨率 采样速率 DAC驱动函数 Cube图形化配置 导入TFT_LCD ioc 设置DAC通道 更改ADC配置 优先级设置 更改TIM3配置 按键…

【web | CTF】BUUCTF [护网杯 2018] easy_tornado

天命&#xff1a;这题是框架性的漏洞&#xff0c;Python的web服务器框架&#xff0c;应该已经比较古老了 开局先看一下三个文件 简单阅读后会发现&#xff0c;这里存在文件包含漏洞&#xff0c;可以直接读取文件&#xff0c;但是有一个哈希值校验 一开始我以为是扫描文件后得到…

房屋租赁系统的Java实战开发之旅

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…