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 exception
          completeTransactionAfterThrowing(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) {
          @Override
          public 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对象这个地方

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

VSCode无法连接远程服务器的两种解决方法

文章目录 VSCode Terminal 报错解决方式1解决方式2you are connected to an OS version that is unsupported by Visual Studio Code解决方法 VSCode Terminal 报错 直接在terminal或cmd中使用ssh命令可以连接服务器&#xff0c;但是在vscode中存在报错&#xff0c;最后一行为…

第12讲创建图文投票实现

创建图文投票实现 图文投票和文字投票基本一样&#xff0c;就是在投票选项里面&#xff0c;多了一个选项图片&#xff1b; <view class"option_item" v-for"(item,index) in options" :key"item.id"><view class"option_input&qu…

第13章 网络 Page724 asio定时器

程序代码&#xff1a; 11行&#xff0c;声明一个ios对象 13行&#xff0c;使用ios对象作为参数声明一个定时器&#xff0c;此时&#xff0c;定时器和ios完成了关联&#xff0c;后面定时器如果有任务的话&#xff0c;就可以将任务交给ios 16行&#xff0c;为定时器设置一个定…

Vue3+Vite+TS+Pinia+ElementPlus+Router+Axios创建项目

目录 初始项目组成1. 创建项目1.1 下载项目依赖1.2 项目自动启动1.3 src 别名设置vite.config.ts配置文件tsconfig.json配置若新创项目ts提示1.4 运行测试2. 清除默认样式2.1 样式清除代码下载2.2 src下创建公共样式文件夹`style`2.3 main.js中引入样式2.4 安装`sass`解析插件2…

Unity(单元测试)在STM32上的移植与应用

概述 Unity Test是一个为C构建的单元测试框架。本文基于STM32F407为基础&#xff0c;完全使用STM32CubeIDE进行开发&#xff0c;移植和简单使用Unity。 单片机型号&#xff1a;STM32F407VET6 软件&#xff1a;STM32CubeIDE Version: 1.14.1 Unity Version&#xff1a;2.…

小结与数字的魅力的开篇

小结 本系列主要介绍了一些排序算法&#xff0c;包括冒泡排序、快速排序、直接插入排序、希尔排序、简单选择排序、堆排序、归并排序、计数排序、桶排序和基数排序。 排序算法本身并不难&#xff0c;但其涉及的知识点却星罗棋布&#xff0c;其变化莫测的思路更让人难以捉摸&am…

【数据结构】哈希桶封装出map和set

利用之前的哈希桶封装出unordered_map和unordered_set。 这个封装并不简单&#xff0c;迭代器的使用&#xff0c;模板参数的繁多&#xff0c;需要我们一层一层封装。 map是一个k - v类型&#xff0c;set是k类型&#xff0c;那么就明确了如果需要封装&#xff0c;底层的tables…

Python算法探索:从经典到现代(三)

一、引言 随着信息技术的飞速发展&#xff0c;数据已经成为现代社会不可或缺的资源。Python&#xff0c;作为数据处理和分析的利器&#xff0c;为我们提供了大量强大的库和工具&#xff0c;用于从经典到现代的各种算法探索。本文将带你领略Python在算法领域的魅力&#xff0c;从…

OpenAI宣布ChatGPT新增记忆功能;谷歌AI助理Gemini应用登陆多地区

&#x1f989; AI新闻 &#x1f680; OpenAI宣布ChatGPT新增记忆功能&#xff0c;可以自由控制内存&#xff0c;提供个性化聊天和长期追踪服务 摘要&#xff1a;ChatGPT新增的记忆功能可以帮助AI模型记住用户的提问内容&#xff0c;并且可以自由控制其内存。这意味着用户不必…

mysql5.6安装---windows版本

安装包下载 链接&#xff1a;https://pan.baidu.com/s/1L4ONMw-40HhAeWrE6kluXQ 提取码&#xff1a;977q 安装视频 1.解压完成之后将其放到你喜欢的地址当中去&#xff0c;这里我默认放在了D盘&#xff0c;这是我的根目录 2.配置环境变量 我的电脑->属性->高级->环境…

今日arXiv最热NLP大模型论文:清华提出LongAlign,打破长上下文对齐瓶颈,数据、训练策略、评估基准一网打尽

随着LLMs的发展&#xff0c;其支持的上下文长度越来越长。仅一年时间&#xff0c;GPT-4就从一开始的4K、8K拓展到了128k。 128k什么概念&#xff1f;相当于一本300页厚的书。这是当初只支持512个tokens的BERT时代不敢想象的事情。 随着上下文窗口长度的增加&#xff0c;可以提…

【优化数学模型】1. 基于Python的线性规划问题求解

【优化数学模型】1. 基于Python的线性规划问题求解 一、线性规划问题1.概述2.三要素 二、示例&#xff1a;药厂生产问题三、使用 Python 绘图求解线性规划问题1.绘制约束条件2.绘制可行域3.绘制目标函数4.绘制最优解 四、使用 scipy.optimize 软件包求解线性规划问题1.导入库2.…

springboot742餐厅点餐系统

springboot742餐厅点餐系统 获取源码——》公主号&#xff1a;计算机专业毕设大全

面试前的准备

面试前的准备 Java程序员校招与社招的区别 校招和社招都是企业招聘形式的一种&#xff0c;只是面向的对象不同。校招 只允许在校生参加&#xff0c;社招理论上是任何人都能参加的(包括在校生)。 但是&#xff0c;无论是社招还是校招&#xff0c;它的难度都取决于你的水平高低。…

【Win10 触摸板】在插入鼠标时禁用触摸板,并在没有鼠标时自动启用触摸板。取消勾选连接鼠标时让触摸板保持打开状态,但拔掉鼠标后触摸板依旧不能使用

出现这种问题我的第一反应就是触摸板坏了&#xff0c;但是无意间我换了一个账户发现触摸板可以用&#xff0c;因此推断触摸板没有坏&#xff0c;是之前的账户问题&#xff0c;跟系统也没有关系&#xff0c;不需要重装系统。 解决办法&#xff1a;与鼠标虚拟设备有关 然后又从知…

Redis笔记

Redis&#xff08;Remote Dictionary Server&#xff09;是一种非关系型数据库 Redis 与其他 key - value 缓存产品有以下三个特点&#xff1a; Redis支持数据的持久化&#xff0c;可以将内存中的数据保存在磁盘中&#xff0c;重启的时候可以再次加载进行使用。Redis不仅仅…

C# Winform .net6自绘的圆形进度条

using System; using System.Drawing; using System.Drawing.Drawing2D; using System.Windows.Forms;namespace Net6_GeneralUiWinFrm {public class CircularProgressBar : Control{private int progress 0;private int borderWidth 20; // 增加的边框宽度public int Progr…

Qt:槽函数的五种写法

一、Qt4写法&#xff08;不推荐&#xff09; connect(ui.btnOpen,SIGNAL(clicked),this,SLOT( open() ) );因为是以宏定义的方式展开&#xff0c;所以如果SIGNAL写错&#xff0c;或者信号名字、槽函数写错、编译器是无法检验出来的&#xff0c;导致出现隐性BUG&#xff0c;不容…

SpringBoot+Vue3 完成小红书项目

简介 该项目采用微服务架构&#xff0c;实现了前后端分离的系统设计。在前端&#xff0c;我们选择了 Vue3 配合 TypeScript 和 ElementUi 框架&#xff0c;以提升开发效率和用户体验。而在后端&#xff0c;则是运用 SpringBoot 和 Mybatis-plus 进行开发&#xff0c;保证了系统…

JVM(2)实战篇

1 内存调优 1.1 内存溢出和内存泄漏 内存泄漏&#xff08;memory leak&#xff09;&#xff1a;在Java中如果不再使用一个对象&#xff0c;但是该对象依然在GC ROOT的引用链上&#xff0c;这个对象就不会被垃圾回收器回收&#xff0c;这种情况就称之为内存泄漏。 内存泄漏绝…