Spring事务(三)——传播属性之REQUIRED

2023-11-03

        事务是与连接对象紧密相关的,事务属性用来控制事务流转。 

        Spring事务的传播属性有以下几种:

  • Propagation.REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,则加入到这个事务中。默认属性,也是最常使用。
  • Propagation.NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与Propagation.REQUIRED类似的操作。
  • Propagation.REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
  • Propagation.SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
  • Propagation.MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常。
  • Propagation.NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

        前面三种使用居多。

        本篇看REQUIRED传播属性。

        通过案例代码进入本篇内容,有如下事务方法: 

    @Transactional
    @Override
    public void transation(ConsultConfigArea area, ZgGoods zgGoods) {
        areaService.addArea(area);
        goodsService.addGoods(zgGoods);
    }

         transation()方法事务执行的大致流程就是下面,为了便于理解,用伪代码来表示:

        // transation()方法执行
        // 开启事务
        createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
        try {

            // 执行被代理方法
            invocation.proceedWithInvocation();
            // 相当于执行下面方法:
            @Transactional
            public void transation(ConsultConfigArea area, ZgGoods zgGoods) throws JamesException {
                areaService.addArea(area);
                goodsService.addGoods(zgGoods);
            }

        } catch (Exception e) {
            // 事务回滚
            completeTransactionAfterThrowing(txInfo, ex);
        }
        // 事务提交
        commitTransactionAfterReturning(txInfo);
        // transation()方法结束

         调用的业务方法同样也有@Transactional注解:

    @Transactional
    @Override
    public int addArea(ConsultConfigArea area) {
        int i = commonMapper.addArea(area);
        return i;
    }
    @Transactional
    @Override
    public void addGoods(ZgGoods zgGoods) throws JamesException {
        commonMapper.addGood(zgGoods);
    }

        那么,伪代码就会变成这样:

        // transation()方法执行
        // 开启事务
        createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
        try {

            // 执行被代理方法
            invocation.proceedWithInvocation();
            // 相当于执行下面方法:

            // 1.areaService.addArea(area)开始执行
            // 开启事务
            createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
            try {

                // 执行被代理方法
                invocation.proceedWithInvocation();
                // 相当于执行下面方法:
                commonMapper.addArea(area);

            } catch (Exception e) {
                // 事务回滚
                completeTransactionAfterThrowing(txInfo, ex);
                throw new Exception(e);
            }
            // 事务提交
            commitTransactionAfterReturning(txInfo);
            // areaService.addArea(area)执行结束

         ————————————————————————————————————————————————————————————

            // 2.goodsService.addGoods(zgGoods)开始执行
            // 开启事务
            createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
            try {

                // 执行被代理方法
                invocation.proceedWithInvocation();
                // 相当于执行下面方法:
                commonMapper.addGood(zgGoods);

            } catch (Exception e) {
                // 事务回滚
                completeTransactionAfterThrowing(txInfo, ex);
                throw new Exception(e);
            }
            // 事务提交
            commitTransactionAfterReturning(txInfo);
            // goodsService.addGoods(zgGoods)执行结束

        } catch (Exception e) {
            // 事务回滚
            completeTransactionAfterThrowing(txInfo, ex);
        }
        // 事务提交
        commitTransactionAfterReturning(txInfo);
        // transation()方法结束

        如果是PROPAGATION_REQUIRED属性,示例代理中的三个方法使用的是同一个事务。

    @Autowired
    private DataSource dataSource;

    ConnectionHolder resource = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);

        这样,就可以拿到连接对象。上面代码执行得到的连接对象为同一个:

         分析一下执行过程,三个方法transation(ConsultConfigArea area, ZgGoods zgGoods)、       areaService.addArea(area)、goodsService.addGoods(zgGoods)是通过不同的代理对象去执行,那么TransactionInterceptor#invoke()会执行三次,在AbstractPlatformTransactionManager#getTransaction()方法:

	public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
			throws TransactionException {

		// Use defaults if no transaction definition given.
		TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());

		// 得到一个DataSourcetranctionObject类(就是事务对象)****
		Object transaction = doGetTransaction();

		// 判断之前是否已存在事务对象(第一次进来为空)
		if (isExistingTransaction(transaction)) {
			// Existing transaction found -> check propagation behavior to find out how to behave.
			// 如果不是第一次进来, 则走这里,表示之前已开启事务,则为现有事务创建TransactionStatus。
			return handleExistingTransaction(def, transaction, debugEnabled);
		}

      ----省略无关代码---- 

        获取连接对象doGetTransaction():

        所属类org.springframework.jdbc.datasource.DataSourceTransactionManager

	protected Object doGetTransaction() {
		// 创建一个事务对象
		DataSourceTransactionObject txObject = new DataSourceTransactionObject();
		// 是否创建回滚点,默认为true
		txObject.setSavepointAllowed(isNestedTransactionAllowed());
		// 根据DataSource对象去ThreadLocal中拿连接对象ConnectionHolder,第一次进来拿的时候是空的
		ConnectionHolder conHolder =
				(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
		txObject.setConnectionHolder(conHolder, false);
		return txObject;
	}

        Spring获取连接对象就是从ThreadLocal中获取的,第一次进来为空,那么就会进入后面的逻辑, 在startTransaction()中的doBegin()方法中创建连接对象:

        所属类:org.springframework.jdbc.datasource.DataSourceTransactionManager

	protected void doBegin(Object transaction, TransactionDefinition definition) {
		DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
		Connection con = null;

		try {
			// 检查事务中是否有连接对象(DataSource)
			if (!txObject.hasConnectionHolder() ||
					txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
				// 没有的话通过数据源管理器拿DataSource对象,再从DataSource中获取连接对象
				Connection newCon = obtainDataSource().getConnection();
				if (logger.isDebugEnabled()) {
					logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
				}
				// 把连接对象包装成一个ConnectionHolder,并设置到事务对象(txObject)中
				txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
			}
			// 设置有事务的标识
			txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
			con = txObject.getConnectionHolder().getConnection();

			// 根据@Transactional注解中的配置的隔离级别,设置Connection的readonly与隔离级别
			Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
			txObject.setPreviousIsolationLevel(previousIsolationLevel);
			txObject.setReadOnly(definition.isReadOnly());

			// Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
			// so we don't want to do it unnecessarily (for example if we've explicitly
			// configured the connection pool to set it already).
			if (con.getAutoCommit()) {
				txObject.setMustRestoreAutoCommit(true);
				if (logger.isDebugEnabled()) {
					logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
				}
				// 关闭自动提交,提交由Spring控制
				con.setAutoCommit(false);
			}
			// 判断事务为只读事务
			prepareTransactionalConnection(con, definition);
			// 设置事务状态(是否为活跃的)为true
			txObject.getConnectionHolder().setTransactionActive(true);

			// 设置事务(数据库连接)超时时间
			int timeout = determineTimeout(definition);
			if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
				txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
			}

			// Bind the connection holder to the thread.
			// 把连接对象和当前线程进行绑定,把数据源和连接的绑定关系设置到ThreadLocal<Map<数据源,连接对象>>,为threadId和Map的映射关系
			// 后面就可以通过threadId拿到Map,进而通过相同的数据源对象拿到数据库连接
			if (txObject.isNewConnectionHolder()) {
				TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
			}
		}

       把连接对象包装成ConnectionHolder,赋值给DataSourceTransactionObject事务对象,然后关闭自动提交,最后数据源对象和连接对象建立绑定关系,放到ThreadLocal中:

        所属类:org.springframework.transaction.support.TransactionSynchronizationManager

	public static void bindResource(Object key, Object value) throws IllegalStateException {
		Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
		Assert.notNull(value, "Value must not be null");
		Map<Object, Object> map = resources.get();
		// set ThreadLocal Map if none found
		if (map == null) {
			map = new HashMap<>();
			resources.set(map);
		}
		Object oldValue = map.put(actualKey, value);
		// Transparently suppress a ResourceHolder that was marked as void...
		if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
			oldValue = null;
		}
		if (oldValue != null) {
			throw new IllegalStateException("Already value [" + oldValue + "] for key [" +
					actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
		}
		if (logger.isTraceEnabled()) {
			logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" +
					Thread.currentThread().getName() + "]");
		}
	}

        这就是绑定过程,resources就是ThreadLocal:

	// key为DataSource对象,value为ConnectionHolder对象
	private static final ThreadLocal<Map<Object, Object>> resources =
			new NamedThreadLocal<>("Transactional resources");

        上面实例代码会执行三次事务的方法,总结一下:

        第一次:执行外层transation() 方法:

AbstractPlatformTransactionManager.getTransaction()——>

DataSourceTransactionManager.doGetTransaction()返回null(ThreadLocal中没有)——>

AbstractPlatformTransactionManager.startTransaction()中的doBegin()方法从DataSource获取连接对象,包装成ConnectionHolder(newTransaction属性为true),放到事务对象DataSourceTransactionObject中,建立绑定关系,放到ThreadLocal中;

        第二次:执行addArea()方法:

AbstractPlatformTransactionManager.getTransaction()——>

DataSourceTransactionManager.doGetTransaction()通过ThreadId拿到连接对象,但newTransaction属性为false(因为是之前的连接对象,不是新建的)——>

存在事务对象进入AbstractPlatformTransactionManager.handleExistingTransaction()执行最后一行,创建DefaultTransactionStatus事务状态对象(newTransaction属性为false)——>

执行被代理方法addArea()——>

TransactionAspectSupport.commitTransactionAfterReturning()事务提交——>

AbstractPlatformTransactionManager.processCommit()通过status.isNewTransaction()判断是否为新事务,是则提交,此时为false,所以不提交。

private void processCommit(DefaultTransactionStatus status) throws TransactionException {
      ----省略无关代码---- 
			try {
				
				// 如果是新事务(前面提交的newTransaction属性),则提交
				else if (status.isNewTransaction()) {
					if (status.isDebug()) {
						logger.debug("Initiating transaction commit");
					}
					unexpectedRollback = status.isGlobalRollbackOnly();
					// 事务提交
					doCommit(status);
				}
      ----省略无关代码---- 

        第三次:执行addGoods()方法:

        流程和第二次进来一样,也没有提交事务;

        事务提交是最外层的transation() 方法执行完被代理方法之后进行,此时事务状态TransactionStatus的newTransaction属性为true:

        接着进入AbstractPlatformTransactionManager.processCommit(),进行事务提交:

        所属类:org.springframework.jdbc.datasource.DataSourceTransactionManager

	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 {
			// 调用JDK的事务提交方法
			con.commit();
		}
		catch (SQLException ex) {
			throw new TransactionSystemException("Could not commit JDBC transaction", ex);
		}
	}

        异常情况处理:

        1. 前面执行的addArea()方法出现异常:

    @Transactional()
    public int addArea(ConsultConfigArea area) {
        ConnectionHolder resource = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
        System.out.println(resource.getConnection());
        int i = commonMapper.addArea(area);
        if (true) throw new RuntimeException("ssf");
        return i;
    }

        那么就会被TransactionAspectSupport.invokeWithinTransaction()的try/catch捕获,并将异常向上抛出;后面的addGoods()方法就不再执行,抛出的异常被最外层的transation() 方法捕获,执行回滚操作,所以数据库的两张表都没有数据。

        2. 后面执行的addGoods()方法出现异常:

    @Transactional
    public void addGoods(ZgGoods zgGoods) {
        ConnectionHolder resource = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
        System.out.println(resource.getConnection());
        int i = commonMapper.addGood(zgGoods);
        if (true) throw new RuntimeException("ssf");
    }

        虽然前面的addArea()执行成功,但事务状态newTransaction属性为false,不会提交事务;addGoods()出现异常,被外层的transation() 方法捕获,执行回滚操作,所以数据库的两张表也没有数据。

       3. 内部方法addGoods()出现异常,外层transation() try/catch异常,未向上抛:

    @Transactional
    @Override
    public void transation(ConsultConfigArea area, ZgGoods zgGoods) throws JamesException {
        try {
            areaService.addArea(area);
            goodsService.addGoods(zgGoods);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

        这种情况,addArea()执行成功,但事务状态newTransaction属性为false,不会提交事务;addGoods()出现异常,不会执行commitTransactionAfterReturning()事务提交;

        异常被自己的try/catch捕获,但未做任何处理;

        继续执行外层的transation() 方法的commitTransactionAfterReturning()事务提交,进入commit()方法,在这里执行了回滚操作:

        所属类:org.springframework.transaction.support.AbstractPlatformTransactionManager

        commit()重执行回滚操作???数据库没有插入数据!和我们的预期不一样!什么鬼?

        因为addGoods()方法出现异常,会执行completeTransactionAfterThrowing()回滚操作:

        所属类:org.springframework.transaction.interceptor.TransactionAspectSupport

	protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
		if (txInfo != null && txInfo.getTransactionStatus() != null) {
			if (logger.isTraceEnabled()) {
				logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
						"] after exception: " + ex);
			}

			// transactionAttribute的实现类为RuledBasedTransactionAttribute,父类为DefaultTransactionAttribute
			// 把异常传进来,执行RuledBasedTransactionAttribute.rollbackOn()方法
			if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
				try {
					// 执行回滚逻辑
					txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
				}
				catch (TransactionSystemException ex2) {
					logger.error("Application exception overridden by rollback exception", ex);
					ex2.initApplicationException(ex);
					throw ex2;
				}
				catch (RuntimeException | Error ex2) {
					logger.error("Application exception overridden by rollback exception", ex);
					throw ex2;
				}
			}
			else {
				// We don't roll back on this exception.
				// Will still roll back if TransactionStatus.isRollbackOnly() is true.
				try {
					txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
				}
				catch (TransactionSystemException ex2) {
					logger.error("Application exception overridden by commit exception", ex);
					ex2.initApplicationException(ex);
					throw ex2;
				}
				catch (RuntimeException | Error ex2) {
					logger.error("Application exception overridden by commit exception", ex);
					throw ex2;
				}
			}
		}
	}

        进入rollback(TransactionStatus status) 方法:

        所属类:org.springframework.transaction.support.AbstractPlatformTransactionManager

	public final void rollback(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;
		// 回滚方法
		processRollback(defStatus, false);
	}
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
		try {
			boolean unexpectedRollback = unexpected;

			try {
				triggerBeforeCompletion(status);
				// 是否存在回滚点,如mysql的savepoint
				if (status.hasSavepoint()) {
					if (status.isDebug()) {
						logger.debug("Rolling back transaction to savepoint");
					}
					// 回滚到上一个savepoint位置
					status.rollbackToHeldSavepoint();
				}
				else if (status.isNewTransaction()) {
					if (status.isDebug()) {
						logger.debug("Initiating transaction rollback");
					}
					// 如果当前执行的方法是新开了一个事务,则直接回滚
					doRollback(status);
				}
				else {
					// Participating in larger transaction
					// 如果当前执行的方法,共用了之前已存在的事务,而当前方法抛出异常,则判断整个事务是否要回滚
					if (status.hasTransaction()) {
						// 如果一个事务有两个方法,第二个抛异常了,则第二个方法执行失败进行回滚
						if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
							if (status.isDebug()) {
								logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
							}
							// 直接将rollbackOnly设置到ConnectionHolder中取,表示整个事务都要回滚
							doSetRollbackOnly(status);
						}
						else {
							if (status.isDebug()) {
								logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
							}
						}
					}
					else {
						logger.debug("Should roll back transaction but cannot - no transaction available");
					}
					// Unexpected rollback only matters here if we're asked to fail early
					if (!isFailEarlyOnGlobalRollbackOnly()) {
						unexpectedRollback = false;
					}
				}
			}
			catch (RuntimeException | Error ex) {
				triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
				throw ex;
			}

			triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);

			// Raise UnexpectedRollbackException if we had a global rollback-only marker
			if (unexpectedRollback) {
				throw new UnexpectedRollbackException(
						"Transaction rolled back because it has been marked as rollback-only");
			}
		}
		finally {
			cleanupAfterCompletion(status);
		}
	}

        在doSetRollbackOnly(DefaultTransactionStatus status)设置事务对象的setRollbackOnly()属性为true。 

         这里需要注意的是, 如果当前执行的方法,已存在事务,而当前方法抛出异常,则判断整个事务是否要回滚,如果一个事务有两个方法,第二个抛异常了,则第二个方法执行失败进行回滚,会将rollbackOnly设置到ConnectionHolder中,表示整个事务都要回滚,看doSetRollbackOnly(status)方法:
        所属类:org.springframework.jdbc.datasource.DataSourceTransactionManager

	protected void doSetRollbackOnly(DefaultTransactionStatus status) {
		DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
		if (status.isDebug()) {
			logger.debug("Setting JDBC transaction [" + txObject.getConnectionHolder().getConnection() +
					"] rollback-only");
		}
		txObject.setRollbackOnly();
	}

        进入本类的setRollbackOnly()方法:

		public void setRollbackOnly() {
			getConnectionHolder().setRollbackOnly();
		}

         拿到连接对象ConnectionHolder,并设置rollbackOnly属性为true,所以虽然是事务提交,但是前面将TransactionStatus的rollbackOnly属性设置为true,所以在这里进行了回滚操作:

public final void commit(TransactionStatus status) throws TransactionException {
      ----省略无关代码---- 
		// 判断此事务在此之前是否设置了回滚,并且isGlobalRollbackOnly()为true
		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);
	}

        所以外层的transation() 方法不论是否将异常向上抛,都进行了回滚,导致数据库没数据。

        4. 内部方法addGoods()方法try/catch异常,未向上抛:

    @Transactional
    public void addGoods(ZgGoods zgGoods) {
        try {
            commonMapper.addGood(zgGoods);
            if (true) throw new RuntimeException("ssf");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

        此时数据库却有数据。因为虽然有异常,但是却吞掉了,没有抛出,所以外层的transation() 方法未捕捉到异常,执行正常的事务提交了。

        总结:内部方法出现异常被吞掉,不会回滚;外部方法异常抛出或吞掉,都会回滚。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring事务(三)——传播属性之REQUIRED 的相关文章

随机推荐

  • 注解 @AutoConfigureBefore 和 @AutoConfigureAfter 的用途

    介绍 AutoConfigureBefore 和 AutoConfigureAfter 是 spring boot autoconfigure 包下的注解 用途 AutoConfigureBefore AAAA class 或 AutoCo
  • 利用eNSP的Cloud连接物理机防火墙(web)

    防火墙一般用USG6000V的 双击打开后一般会出现会出现以下提示 需要导入一个USG6000V的设备包 可以自行下载 下载好后 点击浏览找到对应设备包导入即可 然后就可以正常开机了 开机后输入默认的账号密码 账号 admin 密码 Adm
  • Qt/C++编写视频监控系统80-远程回放视频流

    一 前言 远程回放NVR或者服务器上的视频文件 一般有三种方式 第一种是调用厂家的SDK 这个功能最全 但是缺点明显就是每个厂家的设备都有自己的SDK 只兼容自家的设备 如果你的软件需要接入多个厂家的 那就意味着要写好多套SDK接入 而且一
  • ​​​苹果上架app需要什么条件​​​

    当我们开发完app后 需要将ipa ap 上传IPA 使用一门Ipa上传小助手把安装包上传到appstoreconnect等待审核 专用密码上传IPA IPA上传小助手可以在windows环境下把IPA上传到appuploader 1 登录
  • Ubuntu包管理工具介绍及本地源配置(三)

    不论是在学习还是在做Linux运维的过程中都需要安装各种软件包以及使用包管理工具 但由于很多内网环境几乎不允许生产环境的服务器连接互联网 这样就造成内网服务器无法使用网上的各种源 而且如果使用来回拷贝软件包安装还得解决依赖问题 所以就需要搭
  • Flowable的DurationHelper时间计算工具类

    Flowable的DurationHelper时间计算工具类 PnYnMnDTnHnMnS时间规则 Obtain a new instance of a Duration specifying the Duration as its str
  • 构建Hadoop集群实验

    1 在 后输入cd home 命令 进入home目录下 在 后输入vi Dockerfile命令 创建并编辑一个Dockerfile文件 示例代码如下 root xxx home vi Dockerfile 按i 小写 键 进入编辑模式 将
  • SpringBoot 中间件设计和开发

    作者介绍 小傅哥 一线互联网 java 工程师 架构师 开发过交易 营销 写过运营 活动 设计过中间件也倒腾过中继器 IO板卡 不只是写Java语言 也搞过C PHP 是一个技术活跃的折腾者 我的博客 bugstack cn 我的Githu
  • React-Router v6 新特性解读及迁移指南

    前言 18年初 React Router的主要开发人员创建一个名为Reach Router的轻量级替代方案 原来是相互抗衡的 却没想React Router直接拿来合并 真香 目前 v6已是测试最后一版 估计新的特性不出意外就是下面这些了
  • fig, ax = plt.subplots(figsize = (a, b))解析 与 plt.subplot()函数解析

    文章目录 1 fig ax plt subplots figsize a b 解析 2 plt subplot 函数解析 可视化基础 这个链接非常重要 1 fig ax plt subplots figsize a b 解析 在matplo
  • rabbitmq基础5——集群节点类型、集群基础运维,集群管理命令,API接口工具

    文章目录 一 集群节点类型 1 1 内存节点 1 2 磁盘节点 二 集群基础运维 2 1 剔除单个节点 2 1 1 集群正常踢出正常节点 2 1 2 服务器异常宕机踢出节点 2 1 3 集群正常重置并踢出节点 2 2 集群节点版本升级 2
  • Redis漏洞总结

    Redis简介 redis是一个key value存储系统 和Memcached类似 它支持存储的value类型相对更多 包括string 字符串 list 链表 set 集合 zset sorted set 有序集合 和hash 哈希类型
  • 通过执行在 shell脚本中的命令,进入指定目录:source命令、点命令

    目录 在shell脚本里切换目录 执行shell脚本进入指定目录 Linux下通过Shell脚本快速进入指定目录 参考 Linux下通过Shell脚本快速进入指定目录 https blog csdn net han8040laixin ar
  • 智能制造中的高频应用技术之回归模型

    前言 当我们把基于数据驱动的方法和人工智能等技术引入工业 制造业领域时 会发现这样的应用场景与一般应用场景的一个重要差异是重视回归模型 一般AI应用场景与商业数据分析场景下 我们相对重视分类 聚类模型 这些模型对我们希望认知的目标形成定性判
  • 海外版“咸鱼”Carousell是什么?

    做跨境的都知道 一定不能只在一颗树上吊死 潮流总是把你推着向前的 现在跨境电商平台一样层出不穷 今天就来跟大家分享最近发现比价好做的外贸电商平台APP Carousell 号称海外版 咸鱼 它的功能也与咸鱼是差不多的 细心的老板会发现 咸鱼
  • 在c语言编程中float类型怎么比较大小?

    比较实数的大小时 由于一般情况下一个实数不能用有限位的二进制表示 所以会存在误差 当我们要比较二个实数是否相等时 不要使用 的比较运算符 而是用二个实数差的绝对值是否小于一个我们限定的值 比如比较一个float型与0的大小 应该用 if x
  • Error in created hook: “Error: Initialize failed: invalid dom.“

    报错解析 初始化时没有获取到DOM元素 原因分析 我这边在 created l里做了一个refresh的一个定时刷新方法 但因为我将 refresh 方法放到后面就导致先是加载了 生产统计 能源监控 质量墙 生产进度监控 等这几个方法 最后
  • Go语言面试题--进阶语法(31)

    文章目录 1 关于 channel 下面描述正确的是 2 下面的代码有几处问题 请详细说明 3 下面的代码有什么问题 4 下面的代码输出什么 1 关于 channel 下面描述正确的是 A 向已关闭的通道发送数据会引发 panic B 从已
  • vue遍历输出列表中以逗号隔开的字符串

    问题描述 后台接口返回一个以逗号隔开的字符串 在列表中需要将逗号隔开的字符串便利并将对应的用户编号转换成为用户姓名 代码展示 列表代码
  • Spring事务(三)——传播属性之REQUIRED

    事务是与连接对象紧密相关的 事务属性用来控制事务流转 Spring事务的传播属性有以下几种 Propagation REQUIRED 如果当前没有事务 就新建一个事务 如果已经存在一个事务中 则加入到这个事务中 默认属性 也是最常使用 Pr