Spring 事务原理总结四

news2024/11/25 18:30:52

作为一名认知有限的中国人,我对年的喜爱,胜过其他一切,因为它给了我拒绝一切的合理理由。每到这个时候,我都会用各种理由来为自己的不作为开脱,今年亦是如此。看着频频发出警报的假期余额,我内心的焦躁变得更加强烈。为了抚慰这烦人的情绪,我决定让自己静下来,继续梳理工作经常用到的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对象这个地方

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

分布式文件系统 SpringBoot+FastDFS+Vue.js【一】

分布式文件系统 SpringBootFastDFSVue.js【一】 一、分布式文件系统1.1.文件系统1.2.什么是分布式文件系统1.3.分布式文件系统的出现1.3.主流的分布式文件系统1.4.分布式文件服务提供商1.4.1.阿里OSS1.4.2.七牛云存储1.4.3.百度云存储 二、fastDFS2.1.fastDSF介绍2.2.为什么要使…

详解CC++内存管理(new和delete)

文章目录 写在前面1. C&C内存分布2. C语言中动态内存管理方式&#xff1a;malloc/calloc/realloc/free3. C内存管理方式&#xff08;语法&#xff09;3.1 new/delete操作内置类型3.2 new和delete操作自定义类型 4. new和delete的实现原理4.1 operator new与operator delete…

【MySQL】学习外键约束处理员工数据

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-g4glZPIY0IKhiTfe {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

STM32—DHT11温湿度传感器

文章目录 一.温湿度原理1.1 时序图 二.代码 一.温湿度原理 1.1 时序图 (1).下图一是DHT11总的时序图。 (2).图二对应图一的左边黑色部分&#xff0c;图三对应图一的绿色部分&#xff0c;图四的左部分图对应图一的红色部分&#xff0c;图四的右部分对应图一的黄色部分。 (3)…

计算机组成原理(1)----主存储器

目录 1.基本半导体元件及原理 2.寻址 1.基本半导体元件及原理 一个主存储器可以分为存储器&#xff0c;MAR&#xff08;地址寄存器&#xff09;和MDR&#xff08;数据寄存器&#xff09;&#xff0c;这三个部件由在时序控制逻辑的控制下工作 其中存储体用来存放二进制数据0和…

[BIZ-缓存] - 3.金融交易系统缓存架构设计

1. 前置文章 [BIZ] - 1.金融交易系统特点https://blog.csdn.net/besthezhaowen/article/details/136118133 [缓存] - 1.缓存共性问题https://blog.csdn.net/besthezhaowen/article/details/136111466 [缓存] - 2.分布式缓存重磅中间件 Redis-CSDN博客文章浏览阅读1.4k次&…

docker (一)-简介

1.什么是docker Docker 是一个开源的应用容器引擎&#xff0c;由于docker影响巨大&#xff0c;今天也用"Docker" 指代容器化技术。 2.docker的优势 一键部署&#xff0c;开箱即用 容器使用基于image镜像的部署模式&#xff0c;image中包含了运行应用程序所需的一…

【Java程序员面试专栏 分布式中间件】ElasticSearch 核心面试指引

关于ElasticSearch 部分的核心知识进行一网打尽,包括ElasticSearch 的基本概念,基本架构,工作流程,存储机制等,通过一篇文章串联面试重点,并且帮助加强日常基础知识的理解,全局思维导图如下所示 基础概念 从数据分类入手,考察全文索引的基本概念 现实世界中数据有哪…

【教程】Kotlin语言学习笔记(二)——数据类型(持续更新)

写在前面&#xff1a; 如果文章对你有帮助&#xff0c;记得点赞关注加收藏一波&#xff0c;利于以后需要的时候复习&#xff0c;多谢支持&#xff01; 【Kotlin语言学习】系列文章 第一章 《认识Kotlin》 第二章 《数据类型》 文章目录 【Kotlin语言学习】系列文章一、基本数据…

机器学习和统计学的区别?

1、本质区别&#xff1a; 目标&#xff1a;机器学习的核心目标是建立一个可以自动学习和改进的模型&#xff0c;以预测未知数据。它更关注结果的准确性和模型的泛化能力&#xff0c;通常不关心模型是否可以解释。而统计学的目标是探究变量之间的关系&#xff0c;理解数据的内在…

Python数据科学:Scikit-Learn机器学习

4.1Scikit-Learn机器学习 Scikit-Learn使用的数据表示&#xff1a;二维网格数据表 实例1&#xff1a;通过Seaborn导入数据 def skLearn():scikit Learn基本介绍:return:import seaborn as sns#导入Iris数据集#注&#xff1a;一般网络访问不了iris sns.load_dataset(iris)ir…

Spring Boot 笔记 020 redis集成

1.1 安装redis Windows 下 Redis 安装与配置 教程_redis windows-CSDN博客 2.1 引入redis坐标 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency> 2.2 配置…

144.【Activiti 7】

Activiti 7 工作流 (一)、工作流程1.概念2.工作流系统3.适用行业4.具体应用5.实现方式 (二)、Activiti7概述1.介绍(1).BPM 介绍(2).BPM软件(3). BPMN 2.使用步骤(1).部署activiti(2).流程定义(3).流程定义部署(4).启动一个流程实例(5).用户查询待办任务(Task)(6).用户办理任务(…

第13讲创建图文投票

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

如何在JavaScript中使用大于和小于运算符

在你的 JavaScript 程序中&#xff0c;你经常需要比较两个值&#xff0c;以确定一个是否大于另一个或小于另一个。这就是大于和小于运算符派上用场的地方。 在本文中&#xff0c;我们将通过代码示例更详细地介绍如何使用这些运算符。 &#xff08;本文内容参考&#xff1a;ja…

Mysql中关于on,in,as,where的区别

目录 Mysql on,in,as,where的区别 Mysql语句问题解决 1、left join数据筛选问题 2、相同数据重复筛选使用问题 3、根据某个字段排序取每个类别最后三条数据或前三条数据 4、业务逻辑书写位置问题 5、查找另一表内和本表相关字段的数量 6、关于union的使用 7、limit的巧…

Ubuntu如何设置成中文

1、右上角点击&#xff08;音量图标和电池图标位置&#xff09;&#xff0c;选择“Settings”&#xff08;设置&#xff09;。 2、左侧选择“Region & Language”&#xff08;区域与语言&#xff09;&#xff0c;然后选择“Manage Installed Languages”&#xff08;管理已…

测试开发-2-概念篇

文章目录 衡量软件测试结果的依据—需求1.需求的概念2.从软件测试人员角度看需求3.为什么需求对软件测试人员如此重要4.如何才可以深入理解被测试软件的需求5.测试用例的概念6.软件错误&#xff08;BUG&#xff09;的概念7.开发模型和测试模型8.软件的生命周期9.瀑布模型&#…

中科大计网学习记录笔记(十一):CDN

前言&#xff1a; 学习视频&#xff1a;中科大郑烇、杨坚全套《计算机网络&#xff08;自顶向下方法 第7版&#xff0c;James F.Kurose&#xff0c;Keith W.Ross&#xff09;》课程 该视频是B站非常著名的计网学习视频&#xff0c;但相信很多朋友和我一样在听完前面的部分发现信…

Qt 的准备知识

文章目录 1. Qt 背景介绍2. 搭建 Qt 开发环境3. 认识 Qt Creator3.1 main.cpp3.2 widget.h3.3 widget.cpp3.4 Forms3.5 .pro文件 1. Qt 背景介绍 Qt 是⼀个 跨平台的 C 图形用户界面应用程序框架 。它为应用程序开发者提供了建立艺术级图形界⾯所需的所有功能。它是完全⾯向对…