spring声明式事务原理01-调用第1层@Transactional方法(事务访问入口)

news2025/3/18 3:14:18

文章目录

  • 【README】
  • 【步骤1】UserAppService调用userSupport.saveNewUser()
  • 【步骤2】获取到TransactionInterceptor
  • 【步骤3】chain不为空,接着执行CglibMethodInvocation#proceed方法
    • 【补充】AopContext作用
  • 【步骤4】CglibMethodInvocation#proceed方法
  • 【步骤5】调用ReflectiveMethodInvocation#proceed方法(反射方法调用类)
  • 【步骤6】调用 TransactionInterceptor#invoke(事务拦截器#invoke方法)
    • 【补充】TransactionInterceptor-事务拦截器源码
  • 【步骤7】调用TransactionAspectSupport#invokeWithinTransaction方法
  • 【步骤8】根据事务属性txAttr获取事务管理器,tm=JdbcTransactionManager
    • 【补充】事务管理器是什么?
  • 【步骤9】TransactionAspectSupport#invokeWithinTransaction()-在事务中执行业务逻辑(非常重要)
    • 【步骤9.1】按需创建事务-createTransactionIfNecessary()
    • 【补充】TransactionInfo事务信息定义
    • 【补充】TransactionStatus事务状态定义
    • 【步骤9.2】执行目标方法

【README】

1)声明式事务代码样例:

在这里插入图片描述

public interface UserMapper {
    UserPO qryUserById(@Param("id") String id);
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    void insertUser(UserPO userPO);
}

public interface UserAccountMapper {

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    void insertUserAccount(UserAccountPO userAccountPO);
}

【代码解说】

  • 代码调用链路:UserAppService -> userSupport.saveNewUser -> userMapper.insertUser + userAccountMapper.insertUserAccount
  • userSupport.saveNewUser 带有 @Transactional 注解; 第1层(最外层)@Transactional标注的方法
    • userMapper.insertUser 带有 @Transactional 注解; 第2层@Transactional标注的方法1
    • userAccountMapper.insertUserAccount 带有 @Transactional 注解; 第2层@Transactional标注的方法2

2)@Transactional:事务注解,用于定义事务元数据,包括事务管理器名称,事务传播行为,超时时间(单位秒),是否只读,回滚的异常类型;

  • @Transactional可以标注类与方法,不管是标注类还是方法,@Transactional标注所在的类的bean都会被spring通过aop代理进行增强;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Reflective
public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";

    @AliasFor("value")
    String transactionManager() default "";

    String[] label() default {};

    Propagation propagation() default Propagation.REQUIRED;

    Isolation isolation() default Isolation.DEFAULT;

    int timeout() default -1;

    String timeoutString() default "";

    boolean readOnly() default false;

    Class<? extends Throwable>[] rollbackFor() default {};

    String[] rollbackForClassName() default {};

    Class<? extends Throwable>[] noRollbackFor() default {};

    String[] noRollbackForClassName() default {};
}


【步骤1】UserAppService调用userSupport.saveNewUser()

1)因为 userSupport中的saveNewUser方法被@Transaction标注,所以该bean被spring增强为aop代理,所以访问aop代理的入口方法intercept(),即CglibAopProxy#DynamicAdvisedInterceptor静态内部类的 intercept方法;

在这里插入图片描述



【步骤2】获取到TransactionInterceptor

在这里插入图片描述



【步骤3】chain不为空,接着执行CglibMethodInvocation#proceed方法

chain有一个元素: TransactionInterceptor-事务拦截器;

接着执行 467行:retVal = (new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy)).proceed();

在这里插入图片描述


【补充】AopContext作用

【补充455行代码】: AopContext实际上是带有ThreadContext的容器对象,用于存储代理对象;

public final class AopContext {
    private static final ThreadLocal<Object> currentProxy = new NamedThreadLocal("Current AOP proxy");

    private AopContext() {
    }

    public static Object currentProxy() throws IllegalStateException {
        Object proxy = currentProxy.get();
        if (proxy == null) {
            throw new IllegalStateException("Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available, and ensure that AopContext.currentProxy() is invoked in the same thread as the AOP invocation context.");
        } else {
            return proxy;
        }
    }

    @Nullable
    static Object setCurrentProxy(@Nullable Object proxy) {
        Object old = currentProxy.get();
        if (proxy != null) {
            currentProxy.set(proxy);
        } else {
            currentProxy.remove();
        }

        return old;
    }
}


【步骤4】CglibMethodInvocation#proceed方法

CglibMethodInvocation#proceed方法, 即Cglib方法调用类#proceed方法 (proceed=继续或进行或处理)

【CglibAopProxy#CglibMethodInvocation】

private static class CglibMethodInvocation extends ReflectiveMethodInvocation {
    public CglibMethodInvocation(Object proxy, @Nullable Object target, Method method, Object[] arguments, @Nullable Class<?> targetClass, List<Object> interceptorsAndDynamicMethodMatchers, MethodProxy methodProxy) {
        super(proxy, target, method, arguments, targetClass, interceptorsAndDynamicMethodMatchers);
    }

    // 调用proceed 
    @Nullable
    public Object proceed() throws Throwable {
        try {
            return super.proceed();
        } catch (RuntimeException var2) {
            throw var2;
        } catch (Exception var3) {
            if (!ReflectionUtils.declaresException(this.getMethod(), var3.getClass()) && !KotlinDetector.isKotlinType(this.getMethod().getDeclaringClass())) {
                throw new UndeclaredThrowableException(var3);
            } else {
                throw var3;
            }
        }
    }
}


【步骤5】调用ReflectiveMethodInvocation#proceed方法(反射方法调用类)

调用ReflectiveMethodInvocation#proceed方法, ReflectiveMethodInvocation=反射方法调用类

// ReflectiveMethodInvocation=反射方法调用类
// interceptorsAndDynamicMethodMatcher 是一个列表,包含一个元素 TransactionInterceptor 
// currentInterceptorIndex 初始值=-1 
public Object proceed() throws Throwable {
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
        return this.invokeJoinpoint();
    } else {
        Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
// interceptorOrInterceptionAdvice 就是 TransactionInterceptor ,而不是 InterceptorAndDynamicMethodMatcher
        if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
            InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher)interceptorOrInterceptionAdvice;
            Class<?> targetClass = this.targetClass != null ? this.targetClass : this.method.getDeclaringClass();
            return dm.matcher().matches(this.method, targetClass, this.arguments) ? dm.interceptor().invoke(this) : this.proceed();
        } else {
            return ((MethodInterceptor)interceptorOrInterceptionAdvice).invoke(this); // 调用到这里 
        }
    }
}

在这里插入图片描述

获取到的interceptorOrInterceptionAdvice的属性如下:

在这里插入图片描述



【步骤6】调用 TransactionInterceptor#invoke(事务拦截器#invoke方法)

调用 TransactionInterceptor#invoke(ReflectiveMethodInvocation), 传入的入参this是ReflectiveMethodInvocation-反射方法调用对象(包装了 UserSupportImpl#saveNewUser方法的代理对象)

【TransactionInterceptor#invoke】

public Object invoke(MethodInvocation invocation) throws Throwable {
    Class<?> targetClass = invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null;
    Method var10001 = invocation.getMethod();
    Objects.requireNonNull(invocation);
    return this.invokeWithinTransaction(var10001, targetClass, invocation::proceed);
}

上述 invocation::proceed中的invocation实际是 ReflectiveMethodInvocaion-反射方法调用类,ReflectiveMethodInvocaion封装了代理对象proxy,方法method等属性;

public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Cloneable {
    protected final Object proxy;
    @Nullable
    protected final Object target;
    protected final Method method;
    protected Object[] arguments;
    @Nullable
    private final Class<?> targetClass;
    @Nullable
    private Map<String, Object> userAttributes;
    protected final List<?> interceptorsAndDynamicMethodMatchers;
    private int currentInterceptorIndex = -1;

    protected ReflectiveMethodInvocation(Object proxy, @Nullable Object target, Method method, @Nullable Object[] arguments, @Nullable Class<?> targetClass, List<Object> interceptorsAndDynamicMethodMatchers) {
        this.proxy = proxy;
        this.target = target;
        this.targetClass = targetClass;
        this.method = BridgeMethodResolver.findBridgedMethod(method);
        this.arguments = AopProxyUtils.adaptArgumentsIfNecessary(method, arguments);
        this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers;
    }

【补充】TransactionInterceptor-事务拦截器源码

TransactionInterceptor继承自TransactionAspectSupport;

public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
    public TransactionInterceptor() {
    }

    public TransactionInterceptor(TransactionManager ptm, TransactionAttributeSource tas) {
        this.setTransactionManager(ptm);
        this.setTransactionAttributeSource(tas);
    }
    
    @Nullable
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Class<?> targetClass = invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null;
        Method var10001 = invocation.getMethod();
        Objects.requireNonNull(invocation);
        // 调用到这里, 注意最后一个入参是 invocation::proceed;
        return this.invokeWithinTransaction(var10001, targetClass, invocation::proceed);
    }

    private void writeObject(ObjectOutputStream oos) throws IOException {
//...
    }

    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
     // ...
    }
}


【步骤7】调用TransactionAspectSupport#invokeWithinTransaction方法

因为TransactionInterceptor 继承自TransactionAspectSupport; 而invokeWithinTransaction方法的最后一个入参invocation就是封装了目标方法,即UserSupportImpl#saveNewUser方法的包装对象ReflectiveMethodInvocation;

在这里插入图片描述

获取到的事务管理器类型是 JdbcTransactionManger, 而事务属性TransactionAttribute保存了注解@Transaction标注的方法的元数据,包括事务传播行为,隔离级别,超时时间,是否只读,事务管理器等属性; 而TransactionAttribute继承自TransactionDefinition-事务定义接口;

【TransactionAttribute】事务属性-TransactionAttribute定义

public interface TransactionAttribute extends TransactionDefinition {
    @Nullable
    String getQualifier();

    Collection<String> getLabels();

    boolean rollbackOn(Throwable ex);
}

【TransactionAspectSupport#invokeWithinTransaction方法】执行到获取PlatformTransactionManager-平台事务管理器;

在这里插入图片描述



【步骤8】根据事务属性txAttr获取事务管理器,tm=JdbcTransactionManager

上述第7步执行到145行,接着判断tm(也就是JdbcTransactionManager)是否是ReactiveTransactionManager类型;

JdbcTransactionManager父类是DataSourceTransactionManager,而DataSourceTransactionManager父类是AbstractPlatformTransactionManager,而AbstractPlatformTransactionManager父类是PlatformTransactionManager;结论:tm(JdbcTransactionManager)不是CallbackPreferringPlatformTransactionManager,所以执行到160行;

在这里插入图片描述

又160行得到的ptm还是JdbcTransactionManager,与tm指向同一个对象,162行判断是否为CallbackPreferringPlatformTransactionManager类型不通过,接着执行第220行;

【补充】事务管理器是什么?

public class JdbcTransactionManager extends DataSourceTransactionManager;

public class DataSourceTransactionManager extends AbstractPlatformTransactionManager implements ResourceTransactionManager, InitializingBean;
    
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, ConfigurableTransactionManager, Serializable; 

【DataSourceTransactionManager】数据源事务管理器-方法列表

  • doBegin-开启事务
  • doCommit-提交事务
  • doGetTransaction-获取事务
  • doResume-恢复事务
  • doRollback-回滚事务
  • doSuspend-挂起事务;

在这里插入图片描述

【DataSourceTransactionManager父类AbstractPlatformTransactionManager】抽象平台事务管理器-部分方法列表

  • doBegin 开启事务
  • doCommit 提交
  • doGetTransation 获取事务
  • doResume 恢复事务
  • doSuspend 挂起事务
  • doRollback 回滚
  • setNestedTransactionAllowed -设置允许嵌套事务;


【步骤9】TransactionAspectSupport#invokeWithinTransaction()-在事务中执行业务逻辑(非常重要)

1)TransactionAspectSupport#invokeWithinTransaction()-在事务中执行业务逻辑,具体步骤如下(跳过了分支判断):

  • 步骤9.1:调用createTransactionIfNecessary方法,创建事务;
  • 步骤9.2:调用invocation.proceedWithInvocation(),执行具体业务逻辑(执行目标方法);
    • 步骤9.2.1(或有) : 抛出异常,执行completeTransactionAfterThrowing
  • 步骤9.3:执行完成(无论是否抛出异常),调用cleanupTransactionInfo()
  • 步骤9.4:判断返回值是否不为null 且 事务属性是否不为null
  • 步骤9.5:调用commitTransactionAfterReturning(),提交事务;

【TransactionAspectSupport#invokeWithinTransaction()】 在事务中调用;上游是TransactionInterceptor,而TransactionInterceptor是TransactionAspectSupport的子类; 源码如下。

protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation) throws Throwable {
    TransactionAttributeSource tas = this.getTransactionAttributeSource();
    TransactionAttribute txAttr = tas != null ? tas.getTransactionAttribute(method, targetClass) : null;
    TransactionManager tm = this.determineTransactionManager(txAttr);
    if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager rtm) {
        // tm类型是JdbcTransactionManager,所以不会执行到这里
        // ... 
        return txSupport.invokeWithinTransaction(method, targetClass, invocation, txAttr, rtm);
    } else {
        PlatformTransactionManager ptm = this.asPlatformTransactionManager(tm);
        String joinpointIdentification = this.methodIdentification(method, targetClass, txAttr);
        if (txAttr != null && ptm instanceof CallbackPreferringPlatformTransactionManager cpptm) {
			// ptm类型是JdbcTransactionManager,所以不会执行到这里
            // ...
       
        } else {
            // 程序执行到这里 
            // 1 创建事务,【非常重要】 
            TransactionInfo txInfo = this.createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

            Object retVal;
            try {
                // 2 通过反射执行具体业务逻辑, invocation就是包装了业务方法UserSuuportImpl#saveUser()方法的调用对象 
                retVal = invocation.proceedWithInvocation();
            } catch (Throwable var22) {
                // 2.1 抛出异常 (或有)
                this.completeTransactionAfterThrowing(txInfo, var22);
                throw var22;
            } finally {
                // 3 最后清理事务信息 
                this.cleanupTransactionInfo(txInfo);
            }
			
			// 4 判断返回值是否不为null 且 事务属性是否不为null 
            if (retVal != null && txAttr != null) {
                TransactionStatus status = txInfo.getTransactionStatus();
                if (status != null) {
                    label185: {
                        if (retVal instanceof Future) {
                            // 5 若返回值为 Future类型(异步线程执行结果类)
                            Future<?> future = (Future)retVal;
                            if (future.isDone()) {
                                try {
                                    future.get();
                                } catch (ExecutionException var27) {
                                    if (txAttr.rollbackOn(var27.getCause())) {
                                        status.setRollbackOnly();
                                    }
                                } catch (InterruptedException var28) {
                                    Thread.currentThread().interrupt();
                                }
                                break label185;
                            }
                        }

                        if (vavrPresent && TransactionAspectSupport.VavrDelegate.isVavrTry(retVal)) {
                            retVal = TransactionAspectSupport.VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
                        }
                    }
                }
            }
			// 5 提交事务 
            this.commitTransactionAfterReturning(txInfo);
            return retVal;
        }
    }
}


【步骤9.1】按需创建事务-createTransactionIfNecessary()

TransactionAspectSupport#createTransactionIfNecessary()详情参见 spring声明式事务原理02-调用第1层@Transactional方法-按需创建事务createTransactionIfNecessary
在这里插入图片描述根据事务管理器+事务属性+aop连接点(切点)创建事务,如下:

protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm, @Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
    if (txAttr != null && ((TransactionAttribute)txAttr).getName() == null) {
        txAttr = new DelegatingTransactionAttribute((TransactionAttribute)txAttr) {
            public String getName() {
                return joinpointIdentification;
            }
        };
    }

    TransactionStatus status = null;
    if (txAttr != null) {
        if (tm != null) {
            // 通过事务管理器获取事务
            status = tm.getTransaction((TransactionDefinition)txAttr); 
        } else if (this.logger.isDebugEnabled()) {
            this.logger.debug("Skipping transactional joinpoint [" + joinpointIdentification + "] because no transaction manager has been configured");
        }
    }
    // 准备事务信息TransactionInfo 
    return this.prepareTransactionInfo(tm, (TransactionAttribute)txAttr, joinpointIdentification, status);
}

在这里插入图片描述


【补充】TransactionInfo事务信息定义

1)TransactionInfo是TransactionAspectSupport的 静态内部类,是一个聚合类,封装了事务相关的多个组件:

  • transactionManager:事务管理器(封装了事务提交,回滚功能)
  • TransactionAttribute:事务属性 (@Transaction注解元数据)
  • joinpointIdentification:切点标识(被@Transaction标注的目标方法的全限定名称)
  • TransactionStatus:事务状态 (封装获取事务状态的方法,如是否只读,是否回滚,是否保存点,事务是否完成,当前线程是否存在事务等)
  • oldTransactionInfo-上一个事务信息对象;

其中 oldTransactionInfo-上一个事务信息对象,是bindToThread()方法与restoreThreadLocalStatus() 需要用到的;

protected static final class TransactionInfo {
    @Nullable
    private final PlatformTransactionManager transactionManager;
    @Nullable
    private final TransactionAttribute transactionAttribute;
    private final String joinpointIdentification;
    @Nullable
    private TransactionStatus transactionStatus;
    @Nullable
    private TransactionInfo oldTransactionInfo;

    public TransactionInfo(@Nullable PlatformTransactionManager transactionManager, @Nullable TransactionAttribute transactionAttribute, String joinpointIdentification) {
        this.transactionManager = transactionManager;
        this.transactionAttribute = transactionAttribute;
        this.joinpointIdentification = joinpointIdentification;
    }

    public PlatformTransactionManager getTransactionManager() {
        Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
        return this.transactionManager;
    }

    @Nullable
    public TransactionAttribute getTransactionAttribute() {
        return this.transactionAttribute;
    }

    public String getJoinpointIdentification() {
        return this.joinpointIdentification;
    }

    public void newTransactionStatus(@Nullable TransactionStatus status) {
        this.transactionStatus = status;
    }

    @Nullable
    public TransactionStatus getTransactionStatus() {
        return this.transactionStatus;
    }

    public boolean hasTransaction() {
        return this.transactionStatus != null;
    }

    private void bindToThread() {
        this.oldTransactionInfo = (TransactionInfo)TransactionAspectSupport.transactionInfoHolder.get();
        TransactionAspectSupport.transactionInfoHolder.set(this);
    }

    private void restoreThreadLocalStatus() {
        TransactionAspectSupport.transactionInfoHolder.set(this.oldTransactionInfo);
    }    
}

// 事务信息持有器(transactionInfoHolder)是一个ThreadLocal对象,
// 用于保存当前事务信息,及上一个事务信息(挂起时),以便恢复上一个事务信息;
private static final ThreadLocal<TransactionInfo> transactionInfoHolder = 
    new NamedThreadLocal("Current aspect-driven transaction");

事务信息持有器(transactionInfoHolder):是一个ThreadLocal对象,用于保存当前事务信息,及上一个事务信息(挂起时),以便恢复上一个事务信息;

【补充】TransactionStatus事务状态定义

1)TransactionStatus继承自 TransactionExecution, SavepointManager

2)TransactionStatus封装了事务状态方法,包括是否有保存点,当前线程是否存在事务,当前线程是否存在新事务,是否只读,设置回滚状态或判断是否回滚,事务是否完成等状态方法;

3)封装的事务方法包括:

  • 保存点相关方法:包括 hasSavepoint , createSavepoint, rollbackToSavepoint, releaseSavepoint
  • 事务相关方法: 包括hasTransaction, isNewTransaction, isReadOnly, setRollbackOnly, isRollbackOnly, isCompleted;
  • 嵌套传播模式相关方法:isNested (嵌套模式底层原理是保存点)
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
    default boolean hasSavepoint() {
        return false;
    }

    default void flush() {
    }
}
// 事务执行 
public interface TransactionExecution {
    default String getTransactionName() {
        return "";
    }

    default boolean hasTransaction() {
        return true;
    }

    default boolean isNewTransaction() {
        return true;
    }

    default boolean isNested() {
        return false;
    }

    default boolean isReadOnly() {
        return false;
    }

    default void setRollbackOnly() {
        throw new UnsupportedOperationException("setRollbackOnly not supported");
    }

    default boolean isRollbackOnly() {
        return false;
    }

    default boolean isCompleted() {
        return false;
    }
}
// 保存点管理器
public interface SavepointManager {
    Object createSavepoint() throws TransactionException;

    void rollbackToSavepoint(Object savepoint) throws TransactionException;

    void releaseSavepoint(Object savepoint) throws TransactionException;
}


【步骤9.2】执行目标方法

1)调用 invocation.proceedWithInvocation() 调用目标方法; invocation的类是 CglibMethodInvocation,即cglib代理方法调用类;

在这里插入图片描述

2)invocation.proceedWithInvocation():调用CglibMethodInvocation#proceed() 方法,又CglibMethodInvocation是封装了目标方法的cglib方法调用代理对象,所以接着会调用目标方法,即UserSupportImpl#saveNewUser()方法;

在这里插入图片描述

3)接着调用父类ReflectiveMethodInvocation的proceed()方法;

在这里插入图片描述

4)接着执行ReflectiveMethodInvocation#invokeJoinpoint方法,如下。

在这里插入图片描述

5)接着执行 AopUtils#invokeJoinpointUsingReflection() 方法

其中 originalMethod.invoke(target, args) 是通过反射调用具体的目标方法;

在这里插入图片描述

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

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

相关文章

Qt-D指针与Q指针的设计哲学

文章目录 前言PIMLP与二进制兼容性D指针Q指针优化d指针继承Q_D和Q_Q 前言 在探索Qt源码的过程中会看到类的成员有一个d指针&#xff0c;d指针类型是一个private的类&#xff0c;这种设计模式称为PIMPL&#xff08;pointer to implementation&#xff09;&#xff0c;本文根据Q…

数据结构——单链表list

前言&#xff1a;大家好&#x1f60d;&#xff0c;本文主要介绍数据结构——单链表 目录 一、单链表 二、使用步骤 1.结构体定义 2.初始化 3.插入 3.1 头插 3.2 尾插 3.3 按位置插 四.删除 4.1头删 4.2 尾删 4.3 按位置删 4.4按值删 五 统计有效值个数 六 销毁…

基于PHP的网店进销存管理系统(源码+lw+部署文档+讲解),源码可白嫖!

摘要 相比于以前的传统进销存管理方式&#xff0c;智能化的管理方式可以大幅降低进销存管理的运营人员成本&#xff0c;实现了进销存管理的标准化、制度化、程序化的管理&#xff0c;有效地防止了商品信息及仓库信息的随意管理&#xff0c;提高了信息的处理速度和精确度&#…

Vue3 Pinia $subscribe localStorage的用法 Store的组合式写法

Vue3 Pinia $subscribe 可以用来监视Stroe数据的变化 localStorage的用法 localStorage中只能存字符串&#xff0c;所有对象要选转成json字符串 定义store时&#xff0c;从localStorage中读取数据talkList可能是字符串也可能是空数组 Store的组合式写法 直接使用reactiv…

【PHP】获取PHP-FPM的状态信息

文章目录 一、前言二、环境三、过程1&#xff09;修改PHP-FPM配置文件2&#xff09;修改Nginx配置文件3&#xff09;访问页面4&#xff09;修改状态页面端口 一、前言 PHP-FPM内置有一个状态页面&#xff0c;通过这个页面可以获取到FPM的一些状态信息&#xff08;见下图&#…

(性能测试)性能测试工具 2.jmeter的环境搭建 3jmeter元件和4使用实例 5jmeter元件和参数化

目录 性能测试工具 性能测试工具 jemeter环境搭建 jmeter的常用目录介绍 jmeter修改语言和主题--jmeter界面的汉化 jmeter元件 jmeter元件和组件的介绍 jmeter的作用域原则 jmeter的执行顺序 案例&#xff1a;执行顺序 jmeter使用案例 jmeter线程组的介绍 jmeter…

Java 大视界 -- 基于 Java 的大数据实时流处理中的窗口操作与时间语义详解(135)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

数据库的基本知识

目录 一、创建数据库和数据表1.1 创建数据库相关代码1.2 创建数据表1.3 约束条件1.3.1 主键约束1.3.2 非空约束1.3.3 唯一性约束1.3.4 默认约束1.3.5 自增字段 1.4 手工建表 二、数据查询功能2.1 sql 查询的7个关键词2.1.1 select2.1.2 from2.1.3 where2.1.4 group by2.1.5 hav…

失败的面试经历(ʘ̥∧ʘ̥)

一.面向对象的三大特性 1.封装&#xff1a;将对象内部的属性私有化&#xff0c;外部对象不能够直接访问&#xff0c;但是可以提供一些可以使外部对象操作内部属性的方法。 2.继承&#xff1a;类与类之间会有一些相似之处&#xff0c;但也会有一些异处&#xff0c;使得他们与众…

Android 7 及以上夜神模拟器,Fiddler 抓 https 包

文章目录 问题描述解决方案环境准备操作步骤1、导出 Fiddler 证书并修改成 .pem 和 .0 文件2、修改夜神模拟器配置3、打开夜神模拟器设备的 USB 调试选项4、将0725b47c.0证书放入夜神模拟器系统证书目录5、夜神模拟器 cmd 环境配置6、给 0725b47c.0 证书赋予权限7、打开 fiddle…

全国医院数据可视化分析系统

【大数据】全国医院数据可视化分析系统 &#xff08;完整系统源码开发笔记详细部署教程&#xff09;✅ 目录 一、项目简介二、项目界面展示三、项目视频展示 一、项目简介 &#x1f3e5; 项目名&#xff1a;医疗导航神器&#xff01;——《基于大数据的微医挂号网医院数据可视…

音视频入门基础:RTCP专题(1)——RTCP官方文档下载

一、引言 实时传输控制协议&#xff08;Real-time Transport Control Protocol或RTP Control Protocol或简写RTCP&#xff09;是实时传输协议&#xff08;RTP&#xff09;的一个姐妹协议。RTCP由《RFC 3550》定义&#xff08;取代废弃的《RFC 1889》&#xff09;。RTP使用一个…

蓝桥杯专项复习——结构体、输入输出

目录 结构体&#xff1a;排序 输入输出 结构体&#xff1a;排序 [NOIP2007]奖学金 #include<iostream> #include<cstring> #include<algorithm>using namespace std;const int N310; int n;struct Student {int chinese,math,eng,sum;int idx; }Stu[N];//定…

工作记录 2017-01-06

工作记录 2017-01-06 序号 工作 相关人员 1 协助BPO进行Billing的工作。 修改CSV、EDI837的导入。 修改邮件上的问题。 更新RD服务器。 郝 修改的问题&#xff1a; 1、 In “Full Job Summary” (patient info.), sometime, the Visit->Facility is missed, then …

LLM(2):准备构建 LLM

在了解大语言模型一文中&#xff0c;对 LLM 做了初步介绍&#xff0c;本文接续前一篇文章的内容&#xff0c;简要介绍 LLM 的应用和构建过程。 1.2 LLM 的应用 由于大型语言模型&#xff08;LLMs&#xff09;在解析和理解非结构化文本数据方面具备先进能力&#xff0c;它们在…

pytest+allure+jenkins

本地运行参考&#xff1a;pytestallure 入门-CSDN博客 jenkins运行如下&#xff1a; 安装插件&#xff1a;allure 配置allure安装目录 配置pytest、allure 环境变量 配置流水线 进行build,结果如下 ,点击allure report 查看结果

【linux篇】--linux常见指令

文章目录 一、Linux基本概念 二、Linux入门 1.目录结构 2.Linux命令 *Linux基础命令 ls命令的选项&#xff1a; 3.目录切换相关命令&#xff08;cd & pwd) 4.相对&绝对路径和特殊路径符 4.1相对路径 4.2绝对路径 4.3 你特殊路径符 5.创建目录命令&#xff08;mkdir) 6.…

Kubernetes的组成和架构

Kubernetes&#xff08;K8s&#xff09;是一个开源的容器编排平台&#xff0c;用于自动化部署、扩展和管理容器化应用程序。它由多个组件组成&#xff0c;这些组件可以分为两类&#xff1a;控制平面&#xff08;Control Plane&#xff09;组件和节点&#xff08;Node&#xff0…

Android之RecyclerView列表拖动排序

文章目录 前言一、效果图二、实现步骤1.xml布局2.activity代码3.adapter 总结 前言 随着需求的变化&#xff0c;很多地方需要加拖动改变顺序的需求&#xff0c;用RecyclerView就可以实现列表拖动排序&#xff0c;列如像朋友圈图片拖动排序&#xff0c;或者音乐播放器列表拖动排…

C# WPF 基础知识学习(一)

一、WPF 简介 Windows Presentation Foundation&#xff08;WPF&#xff09;是微软推出的一款用于构建用户界面的框架&#xff0c;它为开发 Windows 桌面应用程序提供了统一的编程模型、语言和框架。WPF 将用户界面的设计与业务逻辑分离开来&#xff0c;采用了 XAML&#xff0…