当前位置: 首页 > news >正文

Spring提供了很好事务管理机制

事务管理在系统开发中是不可缺少的一部分,Spring提供了很好事务管理机制

分类

主要分为编程式事务和声明式事务两种。

编程式事务

是指在代码中手动的管理事务的提交、回滚等操作,代码侵入性比较强,如下示例:

try {//TODO somethingtransactionManager.commit(status);
} catch (Exception e) {transactionManager.rollback(status);throw new InvoiceApplyException("异常失败");
}

声明式事务

基于AOP面向切面的,它将具体业务与事务处理部分解耦,代码侵入性很低,所以在实际开发中声明式事务用的比较多。声明式事务也有两种实现方式,一是基于TX和AOP的xml配置文件方式,第二种就是基于@Transactional注解了。

@Transactional
@GetMapping("/test")
public String test() {
int insert = cityInfoDictMapper.insert(cityInfoDict);
}

事务传播方式propagation

Spring的传播特性(行为)指的是:当一个事务方法调用另一个事务方法时,事务该如何进行。Spring的传播行为有七种,可以分为三大类类型。

场景:以下都会使用这个例子,方便理解;有A类内部有个a方法、B类内部有个b方法

支持当前事务

  1. REQUIRED:如果当前方法存在一个事务,则加入这个事务。若当前方法不存在事务,则新建一个事务。
    例:b方法的传播特性是REQUIRED,当a方法调用到b方法时,若a方法存在事务,则b方法加入这个事务,与a共用一个事务。若a方法不存在事务,则b方法新建一个事务。回滚怎么判断:前者两个方法使用同一个事务,当a或者b任意一个方法出现异常,a和b都会回滚;后者a没有事务不会回滚,b方法有事务,会回滚。

  2. SUPPORTS:如果当前方法存在事务,则加入事务。反之,则以非事务方式运行。
    例:b方法的传播特性是SUPPORTS,当a方法调用到b方法时,若a方法存在事务,则b方法加入这个事务。若a方法不存在事务,则b方法以非事务进行。

  3. MANDATORY:如果当前方法存在事务则加入事务,若不存在则抛出异常。
    例:b方法的传播特性是MANDATORY,当a方法调用到b方法时,若a方法存在事务,则b方法加入这个事务。若a方法不存在事务,则抛出异常。

不支持当前事务

  1. REQUIRES_NEW:新建一个事务,如果当前方法存在事务,则把当前事务挂起。
    例:b方法的传播特性是REQUIRES_NEW,当a方法调用到b方法时,b方法新建一个事务,如果a方法存在事务,则把a方法事务挂起。回滚怎么判断:要明白一点就是a和b的事务没有关系。当a没有事务时,只有b有事务,a方法不会回滚,b方法会回滚。后者a和b有不同事务,当b方法出现异常时b会回滚。而a方法会不会回滚需要分为两种情况:若b中的异常自己捕获则a不会回滚,若b中是是抛出异常,则a也会回滚。

  2. NOT_SUPPORTED:以非事务方式执行,若存在当前事务,则把当前事务挂起。
    例:b方法的传播特性是NOT_SUPPORTED,当a方法调用到b方法时,b方法以非事务方式执行,若a方法存在事务,则挂起a方法事务。

  3. NEVER:以非事务方式执行,若存在当前事务,则会抛出异常。
    例:b方法的传播特性是NEVER,当a方法调用到b方法时,b方法以非事务方式执行,若a方法存在事务,则抛出异常。

嵌套事务

NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与REQUIRED类似的操作。

例:b方法的传播特性是NESTED,当a方法内调用到b方法时。若a方法存在事务,则在a方法内嵌套b方法的事务,两者是有联系的,b事务相当于a事务的子事务。若a方法不存在事务,则b方法新建一个事务。回滚怎么判断:前者a和b都有事务,并且是相关联的事务(a的事务相当于父事务、b的事务相当于子事务),当a方法出现异常时,a和b都会回滚。当b方法出现异常时则要分两种情况分析:若b中的异常自己捕获则a不会回滚,若b中是是抛出异常,则a也会回滚。

其他属性

isolation 属性:隔离级别

isolation :事务的隔离级别,默认值为 Isolation.DEFAULT。开发中基本都是 default 级别

  • Isolation.DEFAULT:使用底层数据库默认的隔离级别

  • Isolation.READ_COMMITTED:读已提交

  • Isolation.READ_UNCOMMITTED:读未提交

  • Isolation.REPEATABLE_READ:可重复读

  • Isolation.SERIALIZABLE:串行化

timeout 属性

timeout :事务的超时时间,。如果超过该时间限制但事务还没有完成,则自动回滚事务。默认值为 -1,默认不限制时间

readOnly 属性

readOnly :指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。

rollbackFor 属性

rollbackFor :用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。

noRollbackFor属性

noRollbackFor:抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。

@Transactional

作用范围

@Transactional 可以作用在接口、类、类方法。

  • 作用于类:当把@Transactional 注解放在类上时,表示所有该类的public方法都配置相同的事务属性信息。

  • 作用于方法:当类配置了@Transactional,方法也配置了@Transactional,方法的事务会覆盖类的事务配置信息。

  • 作用于接口:不推荐这种使用方法,因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理,将会导致@Transactional注解失效

错误使用场景

无需事务的业务

在没有事务操作的业务方法上使用 @Transactional 注解;

例如,在只有查询的操作上,或者在只操作单表的情况下 使用 @Transactional 注解。

  • 虽然对业务功能无影响,但从编码角度看是不规范的。其他开发者可能会认为该方法实际需要事务支持,从而增加理解代码的复杂性。
  • @Transactional是通过动态代理实现的,每次调用带有 @Transactional 注解的方法时,事务管理器都会检查是否需要启动一个新事务,造成性能开销。虽然事务管理的开销在大多数现代数据库和应用服务器中相对较小,但仍然存在。

java

@Transactional
public String testQuery() {standardBak2Service.getById(1L);return "testB";
}
事务范围过大

有些同学为了省事直接将 @Transactional 注解加在了类上或者抽象类上,这样做导致的问题就是类内的方法或抽象类的实现类中所有方法全部都被事务管理。增加了不必要的性能开销或复杂性,建议按需使用,只在有事务逻辑的方法上加@Transactional。

以下是事务范围过大可能引发的问题及其相关处理建议:

  • 锁竞争:事务范围过大可能会导致较长时间持有数据库锁,从而增加锁竞争的可能性。其他事务在等待锁释放期间,可能会导致响应时间变长或出现超时。
  • 死锁:长时间持有锁的事务增加了死锁的风险。当多个事务相互持有对方需要的资源时,可能会陷入死锁状态。

失效场景

应用在非 public 修饰的方法上

之所以会失效是因为@Transactional 注解依赖于Spring AOP切面来增强事务行为,这个 AOP 是通过代理来实现的

而无论是JDK动态代理还是CGLIB代理,Spring AOP的默认行为都是只代理public方法。

被用 final 、static 修饰方法

和上边的原因类似,被用 final 、static 修饰的方法上加 @Transactional 也不会生效。

  • static 静态方法属于类本身的而非实例,因此代理机制是无法对静态方法进行代理或拦截的
  • final 修饰的方法不能被子类重写,事务相关的逻辑无法插入到 final 方法中,代理机制无法对 final 方法进行拦截或增强。
同类中非事务方法调用事务方法

比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。但是如果是A声明了事务,A的事务是会生效的。

失效原因:事务是基于动态代理实现的,但本类中调用另一个方法默认是this调用关系,并非动态代理,故失效
解决方案:要么将操作移动到事务中,要么调用另一个Service中的事务方法

Bean 未被 spring 管理

上边我们知道 @Transactional 注解通过 AOP 来管理事务,而 AOP 依赖于代理机制。因此,Bean 必须由Spring管理实例! 要确保为类加上如 @Controller@Service 或 @Component注解,让其被Spring所管理,这很容易忽视。

异步线程调用
  • 例1:

如果我们在 testMerge() 方法中使用异步线程执行事务操作,通常也是无法成功回滚的,来个具体的例子。

假设testMerge() 方法在事务中调用了 testA(),testA() 方法中开启了事务。接着,在 testMerge() 方法中,我们通过一个新线程调用了 testB(),testB() 中也开启了事务,并且在 testB() 中抛出了异常。此时,testA() 不会回滚 和 testB() 回滚。

testA() 无法回滚是因为没有捕获到新线程中 testB()抛出的异常;testB()方法正常回滚。

在多线程环境下,Spring 的事务管理器不会跨线程传播事务,事务的状态(如事务是否已开启)是存储在线程本地的 ThreadLocal 来存储和管理事务上下文信息。这意味着每个线程都有一个独立的事务上下文,事务信息在不同线程之间不会共享。

  • 例2:

java

@Transactional
public void transactionalMethod() {new Thread(() ->{Valuation v  = new Valuation();v.setUserName("张三");valuationMapper.insert(v);}).start();
}

Spring的事务是通过数据库连接来实现不同线程使用不同的数据库连接,且放在ThreadLocal中,基于同一个数据库连接的事务才能同时提交或回滚,多线程场景下,拿到的数据库连接不是同一个

解决方案:

  1. 采用分布式事务保证
  2. 自己实现事务回滚
数据库引擎不支持事务

事务能否生效数据库引擎是否支持事务是关键。常用的MySQL数据库默认使用支持事务的innodb引擎。一旦数据库引擎切换成不支持事务的myisam,那事务就从根本上失效了。

propagation 设置错误

若是错误的配置以下三种 propagation,事务将不会发生回滚。

  • TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。

  • TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

rollbackFor 设置错误

rollbackFor 可以指定能够触发事务回滚的异常类型。Spring 默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;其他异常(如IOException)不会触发回滚事务。如果在事务中抛出其他类型的异常,例如 checked exceptions(检查型异常),但却期望 Spring 能够回滚事务,就需要指定 rollbackFor属性。

失效原因:@Transactional 注解默认处理RuntimeException,即只有抛出运行时异常,才会触发事务回滚
解决方案:@Transactional 设置为 @Transactional(rollbackFor =Exception.class) 或者直接抛出运行时异常

异常被 catch了

spring的事务是在调用业务方法之前开始的,业务方法执行完毕之后才执行commit or rollback,事务是否执行取决于是否抛出runtime异常。如果抛出runtime exception 并在你的业务方法中没有catch到的话,事务会回滚。

在业务方法中一般不需要catch异常,如果非要catch一定要抛出throw new RuntimeException(),或者注解中指定抛异常类型@Transactional(rollbackFor=Exception.class),否则会导致事务失效,数据commit造成数据不一致,所以有些时候try catch反倒会画蛇添足。

嵌套事务问题

还有一种场景就是嵌套事务问题,比如,我们在 testMerge() 方法中调用了事务方法 testA() 和事务方法 testB(),此时不希望 testB() 抛出异常让整个 testMerge() 都跟着回滚;这就需要单独 try catch 处理 testB() 的异常,不让异常在向上抛。

java

@RequiredArgsConstructor
@Slf4j
@Service
public class TestMergeService {private final TestBService testBService;private final TestAService testAService;@Transactionalpublic String testMerge() {testAService.testA();try {testBService.testB();} catch (Exception e) {log.error("testMerge error:{}", e);}return "ok";}
}@Service
public class TestAService {@Transactionalpublic String testA() {standardBakService.save(entity);return "ok";}
}@Service
public class TestBService {@Transactionalpublic String testB() {standardBakService.save(entity2);throw new RuntimeException("test2");}
}

源码分析

以下源码均基于Spring4.3.12版本。主要从 创建事务、开启事务、提交事务、事务回滚 的维度来详细分析声明式事务。

事务简易流程图

代理类生成

在Spring框架中,当配置了事务管理器并声明了@Transactional注解时,Spring会在实例化bean时生成事务增强的代理类。创建代理类参考源码路径如下:

java

AbstractAutowireCapableBeanFactory.createBean=>doCreateBean()=>initializeBean()=>applyBeanPostProcessorsAfterInitialization()=>postProcessAfterInitialization()(BeanPostProcessor内接口)=> AbstractAutoProxyCreator.postProcessAfterInitialization()=>wrapIfNecessary()=>createProxy() 中  proxyFactory.setProxyTargetClass(true); //是否对类进行代理的设置,true为cglib代理

代理类中方法执行入口

TransactionInterceptor.invoke()方法开始分析 (获取代理类,调用父类TransactionAspectSupport.invokeWithinTransaction()方法,该方法会将代理类的方法纳入事务中)。

java

public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {public Object invoke(final MethodInvocation invocation) throws Throwable {// 返回代理类的目标类Class<?> targetClass = invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null;//事务中执行被代理的方法return this.invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {public Object proceedWithInvocation() throws Throwable {return invocation.proceed();}});}
}

主要核心逻辑

TransactionAspectSupport.invokeWithinTransaction()方法负责获取事务属性和事务管理器,然后针对声明式事务和编程式事务区分处理流程(此处源码忽略编程式事务)。

java

protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final TransactionAspectSupport.InvocationCallback invocation) throws Throwable {// 获取事务属性 TransactionDefinition对象(回顾规则,隔离级别,只读等)final TransactionAttribute txAttr = this.getTransactionAttributeSource().getTransactionAttribute(method, targetClass);// 根据事务属性和方法,获取对应的事务管理器,(后续用于做事务的提交,回滚等操作),数据库的一些信息,final PlatformTransactionManager tm = this.determineTransactionManager(txAttr);// 获取事务方法全路径,final String joinpointIdentification = this.methodIdentification(method, targetClass, txAttr);//响应式编程事务,大多数情况下都会执行到 else中的语句;// CallbackPreferringPlatformTransactionManager 可以通过回掉函数来处理事务的提交和回滚操作, 此处不考虑,此处源码可以忽略if (txAttr != null && tm instanceof CallbackPreferringPlatformTransactionManager) {// 此处省略,此处为编程式事务 处理逻辑} else {//创建事务,事务属性等信息会被保存进 TransactionInfo,便于后续流程中的提交和回滚操作,详情见下文TransactionAspectSupport.TransactionInfo txInfo = this.createTransactionIfNecessary(tm, txAttr, joinpointIdentification);Object retVal = null;try {// 执行目标的方法 (执行具体的业务逻辑)retVal = invocation.proceedWithInvocation();} catch (Throwable var15) {//异常处理this.completeTransactionAfterThrowing(txInfo, var15);throw var15;} finally {//清除当前节点的事务消息,将旧事务节点消息通过ThreadLoacl更新到当前线程(事务的挂起操作就是在这执行)this.cleanupTransactionInfo(txInfo);}//提交事务this.commitTransactionAfterReturning(txInfo);return retVal;}}

开启事务

TransactionAspectSupport.createTransactionIfNecessary() 方法作用是检查当前是否存在事务,如果存在,则根据一定的规则创建一个新的事务。

java

protected TransactionAspectSupport.TransactionInfo createTransactionIfNecessary(PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) {//如果事务名称不为空,则使用方法唯一标识。并使用 DelegatingTransactionAttribute 封装 txAttr if (txAttr != null && ((TransactionAttributerollbackOn)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");}}//构建事务消息,根据指定的属性与状态status 构建一个 TransactionInfo。将已经建立连接的事务所有信息,都记录在ThreadLocal下的TransactionInfo 实例中,包括目标方法的所有状态信息,如果事务执行失败,spring会根据TransactionInfo中的信息来进行回滚等后续操作return this.prepareTransactionInfo(tm, (TransactionAttribute)txAttr, joinpointIdentification, status);}
获取当前事务对象

AbstractPlatformTransactionManager.getTransaction() 获取当前事务对象。通过这个方法,可以获取到关于事务的详细信息,如事务的状态、相关属性等。

java

public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)throws TransactionException {//definition 中存储的事务的注解信息,超时时间和隔离级别等TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());// 获取当前事务Object transaction = doGetTransaction();boolean debugEnabled = logger.isDebugEnabled();// 判断当前线程是否存在事务 if (isExistingTransaction(transaction)) {// 处理已经存在的事务return handleExistingTransaction(def, transaction, debugEnabled);}// 事务超时设置验证,超时时间小于-1 抛异常if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());}// 如果当前线程不存在事务且 事务传播行为是 MANDATORY(用当前事务,如果当前没有事务,则抛出异常) 抛异常if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {throw new IllegalTransactionStateException("No existing transaction found for transaction marked with propagation 'mandatory'");}//以下三种事务传播行为 需要开启新事务else if (def.getPropagationBehavior() == TransactionDefinition.propagation_required ||def.getPropagationBehavior() == TransactionDefinition.propagation_requires_new ||def.getPropagationBehavior() == TransactionDefinition.propagation_nested) {//挂起原事务,因为这里不存在原事务 故设置为null。//当一个事务方法内部调用了另一个事务方法时,如果第二个事务方法需要独立于第一个事务方法,那么可以使用 suspend 方法来挂起当前事务,然后再开始一个新的事务AbstractPlatformTransactionManager.SuspendedResourcesHolder suspendedResources = this.suspend((Object)null);try {boolean newSynchronization = this.getTransactionSynchronization() != 2;DefaultTransactionStatus status = this.newTransactionStatus((TransactionDefinition)definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);//开启事务this.doBegin(transaction, (TransactionDefinition)definition);//同步事务状态及书屋属性this.prepareSynchronization(status, (TransactionDefinition)definition);return status;} catch (RuntimeException var7) {this.resume((Object)null, suspendedResources);throw var7;}}else {boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);//0//创建默认状态 详情见 下文return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);}}
执行获取事务的具体操作

AbstractPlatformTransactionManager.doGetTransaction() 方法用于执行获取事务的具体操作。它可能会根据一些条件或规则,去查找和获取当前的事务对象,并进行相应的处理。

java

 protected Object doGetTransaction() {DataSourceTransactionManager.DataSourceTransactionObject txObject = new DataSourceTransactionManager.DataSourceTransactionObject();//是否允许在一个事务内部开启另一个事务。txObject.setSavepointAllowed(this.isNestedTransactionAllowed());// this.dataSource数据源 配置//判断当前线程如果已经记录数据库连接则使用原连接ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(this.dataSource);//false 表示不是新创建连接txObject.setConnectionHolder(conHolder, false);return txObject;}
  • this.dataSource() 是我们配置DataSourceTransactionManager时传入的。

java

 <bean id="valuationTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="valuationDataSource"/></bean>
  • TransactionSynchronizationManager.getResource() 方法的作用主要是获取与当前事务相关联的资源。TransactionSynchronizationManager 持有一个ThreadLocal的实例,存在一个key为dataSource ,value为ConnectionHolder 的Map信息。

java

//ThreadLocal 存放 ConnectionHolder 信息,ConnectionHolder 可以理解为Connection(数据库连接)的包装类,其中最主要属性为  Connection
private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources");// 获取ConnectionHolderpublic static Object getResource(Object key) {Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);//获取连接信息Object value = doGetResource(actualKey);return value;}//具体执行获取连接信息操作private static Object doGetResource(Object actualKey) {//从 ThreadLoacl中获取Map<Object, Object> map = (Map)resources.get();if (map == null) {return null;} else {Object value = map.get(actualKey);if (value instanceof ResourceHolder && ((ResourceHolder)value).isVoid()) {map.remove(actualKey);if (map.isEmpty()) {resources.remove();}value = null;}return value;}}
判断是否存在正在进行的事务

AbstractPlatformTransactionManager.isExistingTransaction() 方法用于判断是否存在正在进行的事务。它可以帮助我们确定当前的执行环境是否处于事务中,以便进行相应的处理。

java

protected boolean isExistingTransaction(Object transaction) {DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject)transaction;return txObject.getConnectionHolder() != null && txObject.getConnectionHolder().isTransactionActive();}
挂起事务

AbstractPlatformTransactionManager.suspend() 挂起事务,对有无同步的事务采取不同方案,doSuspend()执行挂起具体操作。

java

protected final AbstractPlatformTransactionManager.SuspendedResourcesHolder suspend(Object transaction) throws TransactionException {//如果有同步的事务,则优先挂起同步事务if (TransactionSynchronizationManager.isSynchronizationActive()) {List suspendedSynchronizations = this.doSuspendSynchronization();try {Object suspendedResources = null;if (transaction != null) {//执行挂起操作suspendedResources = this.doSuspend(transaction);}//重置事务名称String name = TransactionSynchronizationManager.getCurrentTransactionName();TransactionSynchronizationManager.setCurrentTransactionName((String)null);//重置只读状态boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();TransactionSynchronizationManager.setCurrentTransactionReadOnly(false);//重置隔离级别Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();TransactionSynchronizationManager.setCurrentTransactionIsolationLevel((Integer)null);//重置事务激活状态boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive();TransactionSynchronizationManager.setActualTransactionActive(false);//返回挂起的事务return new AbstractPlatformTransactionManager.SuspendedResourcesHolder(suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive);} catch (RuntimeException var8) {this.doResumeSynchronization(suspendedSynchronizations);throw var8;} } else if (transaction != null) {Object suspendedResources = this.doSuspend(transaction);return new AbstractPlatformTransactionManager.SuspendedResourcesHolder(suspendedResources);} else {return null;}}
  • AbstractPlatformTransactionManager.doSuspend()执行挂起操作只是将当前ConnectionHolder设置为null,返回原有事务消息,方便后续恢复原有事务消息,并将当前正在进行的事务信息进行重置。

java

protected Object doSuspend(Object transaction) {DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject)transaction;txObject.setConnectionHolder((ConnectionHolder)null);//接触绑定 return TransactionSynchronizationManager.unbindResource(this.dataSource);}//解除绑定操作,将现有的事务消息remove并返回上一级public static Object unbindResource(Object key) throws IllegalStateException {Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);//解绑操作,移除资源Object value = doUnbindResource(actualKey);if (value == null) {throw new IllegalStateException("No value for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");} else {return value;}}
  • AbstractPlatformTransactionManager.doBegin()数据库连接获取,当新事务时,则获取新的数据库连接,并为其设置隔离级别,是否只读等属性。

java

protected void doBegin(Object transaction, TransactionDefinition definition) {DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject)transaction;Connection con = null;try {//新事务开启时将 ConnectionHolder 设置为nullif (txObject.getConnectionHolder() == null || txObject.getConnectionHolder().isSynchronizedWithTransaction()) {//获取新的数据库连接Connection newCon = this.dataSource.getConnection();txObject.setConnectionHolder(new ConnectionHolder(newCon), true);}txObject.getConnectionHolder().setSynchronizedWithTransaction(true);con = txObject.getConnectionHolder().getConnection();//设置事务隔离级别 和readOnly属性Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);txObject.setPreviousIsolationLevel(previousIsolationLevel);if (con.getAutoCommit()) {txObject.setMustRestoreAutoCommit(true);// 交给Spring控制事务提交con.setAutoCommit(false);}this.prepareTransactionalConnection(con, definition);//设置当前线程的事务激活状态txObject.getConnectionHolder().setTransactionActive(true);int timeout = this.determineTimeout(definition);if (timeout != -1) {// 设置超时时间txObject.getConnectionHolder().setTimeoutInSeconds(timeout);}if (txObject.isNewConnectionHolder()) {TransactionSynchronizationManager.bindResource(this.getDataSource(), txObject.getConnectionHolder());}} catch (Throwable var7) {if (txObject.isNewConnectionHolder()) {DataSourceUtils.releaseConnection(con, this.dataSource);txObject.setConnectionHolder((ConnectionHolder)null, false);}throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", var7);}}
  • AbstractPlatformTransactionManager.prepareTransactionStatus()创建默认Status,如果不需要开始事务 (比如SUPPORTS),则返回一个默认的状态。

java

protected final DefaultTransactionStatus prepareTransactionStatus(TransactionDefinition definition, Object transaction, boolean newTransaction, boolean newSynchronization, boolean debug, Object suspendedResources) {DefaultTransactionStatus status = this.newTransactionStatus(definition, transaction, newTransaction, newSynchronization, debug, suspendedResources);this.prepareSynchronization(status, definition);return status;}protected DefaultTransactionStatus newTransactionStatus(TransactionDefinition definition, Object transaction, boolean newTransaction, boolean newSynchronization, boolean debug, Object suspendedResources) {boolean actualNewSynchronization = newSynchronization && !TransactionSynchronizationManager.isSynchronizationActive();//创建 DefaultTransactionStatus 对象return new DefaultTransactionStatus(transaction, newTransaction, actualNewSynchronization, definition.isReadOnly(), debug, suspendedResources);}
  • AbstractPlatformTransactionManager.handleExistingTransaction()针对不同的传播行为做不同的处理方法,比如挂起原事务开启新事务等等。

java

 private TransactionStatus handleExistingTransaction(TransactionDefinition definition, Object transaction, boolean debugEnabled) throws TransactionException {//当传播行为是 NEVER 时抛出异常if (definition.getPropagationBehavior() == 5) {throw new IllegalTransactionStateException("Existing transaction found for transaction marked with propagation 'never'");} else {AbstractPlatformTransactionManager.SuspendedResourcesHolder suspendedResources;boolean newSynchronization;//当传播方式为NOT_SUPPORTED 时挂起当前事务,然后在无事务的状态下运行if (definition.getPropagationBehavior() == 4) {//挂起事务suspendedResources = this.suspend(transaction);newSynchronization = this.getTransactionSynchronization() == 0;//返回默认statusreturn this.prepareTransactionStatus(definition, (Object)null, false, newSynchronization, debugEnabled, suspendedResources);//当传播方式为REQUIRES_NEW时,挂起当前事务,然后启动新事务} else if (definition.getPropagationBehavior() == 3) {//挂起原事务suspendedResources = this.suspend(transaction);try {newSynchronization = this.getTransactionSynchronization() != 2;DefaultTransactionStatus status = this.newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);//启动新的事务this.doBegin(transaction, definition);this.prepareSynchronization(status, definition);return status;} catch (Error|RuntimeException var7) {this.resumeAfterBeginException(transaction, suspendedResources, var7);throw var7;} } else {boolean newSynchronization;//当传播方式为NESTED时,设置事务的保存点//存在事务,将该事务标注保存点,形成嵌套事务//嵌套事务中的子事务出现异常不会影响到父事务保存点之前的操作if (definition.getPropagationBehavior() == 6) {if (!this.isNestedTransactionAllowed()) {throw new NestedTransactionNotSupportedException("Transaction manager does not allow nested transactions by default - specify 'nestedTransactionAllowed' property with value 'true'");} else {if (this.useSavepointForNestedTransaction()) {DefaultTransactionStatus status = this.prepareTransactionStatus(definition, transaction, false, false, debugEnabled, (Object)null);//创建保存点,回滚时,只回滚到该保存点status.createAndHoldSavepoint();return status;} else {newSynchronization = this.getTransactionSynchronization() != 2;DefaultTransactionStatus status = this.newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, (Object)null);//如果不支持保存点,就启动新的事务this.doBegin(transaction, definition);this.prepareSynchronization(status, definition);return status;}}} else {newSynchronization = this.getTransactionSynchronization() != 2;return this.prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, (Object)null);}}}}

回滚事务

TransactionAspectSupport.completeTransactionAfterThrowing() 判断事务是否存在,如不存在就不需要回滚,如果存在则在判断是否满足回滚条件。

java

protected void completeTransactionAfterThrowing(TransactionAspectSupport.TransactionInfo txInfo, Throwable ex) {//判断是否存在事务if (txInfo != null && txInfo.hasTransaction()) {// 判断是否满足回滚条件。抛出的异常类型,和定义的回滚规则进行匹配if (txInfo.transactionAttribute.rollbackOn(ex)) {try {// 回滚处理txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());} //省略代码} else {try {//不满足回滚条件 出现异常 txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());} //省略代码}}}

AbstractPlatformTransactionManager.rollback()当在事务执行过程中出现异常或其他需要回滚的情况时,就会调用这个方法,将事务进行回滚操作,撤销之前所做的数据库操作,以保证数据的一致性。

java

 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");} else {DefaultTransactionStatus defStatus = (DefaultTransactionStatus)status;// 执行回滚操作。this.processRollback(defStatus);}}

AbstractPlatformTransactionManager.processRollback()方法主要用于处理事务的回滚操作。通过这个方法,可以确保事务在需要回滚时能够正确地执行回滚操作,保持数据的完整性。

java

private void processRollback(DefaultTransactionStatus status) {try {try {//解绑线程和会话绑定关系this.triggerBeforeCompletion(status);if (status.hasSavepoint()) {//如果有保存点(当前事务为单独的线程则会退到保存点)status.rollbackToHeldSavepoint();} else if (status.isNewTransaction()) {//如果是新事务直接回滚。调用数据库连接并调用rollback方法进行回滚。使用底层数据库连接提供的APIthis.doRollback(status);} else if (status.hasTransaction()) {if (status.isLocalRollbackOnly() || !this.isGlobalRollbackOnParticipationFailure()) {//如果当前事务不是独立的事务,则只能等待事务链执行完成后再做回滚操作this.doSetRollbackOnly(status);} } } //catch 等代码// 关闭会话,重置属性this.triggerAfterCompletion(status, 1);} finally {//清理并恢复挂起的事务this.cleanupAfterCompletion(status);}}

提交事务

TransactionAspectSupport.commitTransactionAfterReturning() 基本上和回滚一样,都是先判断是否有事务,在操作提交。

java

  protected void commitTransactionAfterReturning(TransactionAspectSupport.TransactionInfo txInfo) {if (txInfo != null && txInfo.hasTransaction()) {//提交事务txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());}}

AbstractPlatformTransactionManager.commit() 创建默认Status prepareTransactionStatu,发现是否有回滚标记,然后进行回滚。如果判断无需回滚就可以直接提交。

java

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");} else {DefaultTransactionStatus defStatus = (DefaultTransactionStatus)status;//发现回滚标记if (defStatus.isLocalRollbackOnly()) {//回滚this.processRollback(defStatus);} else if (!this.shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {//回滚this.processRollback(defStatus);if (status.isNewTransaction() || this.isFailEarlyOnGlobalRollbackOnly()) {throw new UnexpectedRollbackException("Transaction rolled back because it has been marked as rollback-only");}} else {// 提交操作this.processCommit(defStatus);}}}

AbstractPlatformTransactionManager.processCommit()处理事务的提交操作

java

private void processCommit(DefaultTransactionStatus status) throws TransactionException {try {boolean beforeCompletionInvoked = false;try {this.prepareForCommit(status);this.triggerBeforeCommit(status);this.triggerBeforeCompletion(status);beforeCompletionInvoked = true;boolean globalRollbackOnly = false;if (status.isNewTransaction() || this.isFailEarlyOnGlobalRollbackOnly()) {globalRollbackOnly = status.isGlobalRollbackOnly();}if (status.hasSavepoint()) {//释放保存点信息status.releaseHeldSavepoint();} else if (status.isNewTransaction()) {// 是一个新的事务 则提交。 获取数据库连接后使用数据库API进行提交事务this.doCommit(status);}if (globalRollbackOnly) {throw new UnexpectedRollbackException("Transaction silently rolled back because it has been marked as rollback-only");}} catch (TransactionException var20) {if (this.isRollbackOnCommitFailure()) {//提交异常回滚this.doRollbackOnCommitException(status, var20);} else {this.triggerAfterCompletion(status, 2);}throw var20;} //省略其它异常拦截try {this.triggerAfterCommit(status);} finally {this.triggerAfterCompletion(status, 0);}} finally {// 清理事务消息this.cleanupAfterCompletion(status);}}

清除事务信息

AbstractPlatformTransactionManager.cleanupAfterCompletion() 这个方法主要用于在事务完成后进行清理工作。它会负责释放资源、清理临时数据等,以确保系统处于良好的状态。

java

 private void cleanupAfterCompletion(DefaultTransactionStatus status) {//将当前事务设置为完成状态status.setCompleted();if (status.isNewSynchronization()) {// 清空当前事务消息TransactionSynchronizationManager.clear();}if (status.isNewTransaction()) {//如果是新事务 则在事务完成之后做清理操作this.doCleanupAfterCompletion(status.getTransaction());}if (status.getSuspendedResources() != null) {// 将原事务从挂起状态恢复this.resume(status.getTransaction(), (AbstractPlatformTransactionManager.SuspendedResourcesHolder)status.getSuspendedResources());}}

AbstractPlatformTransactionManager.doCleanupAfterCompletion()在新事务完成后会调用resetConnectionAfterTransaction方法重置数据库连接信息,并判断如果是新的数据库连接则将其放回连接池。

java

protected void doCleanupAfterCompletion(Object transaction) {DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;if (txObject.isNewConnectionHolder()) {// 将数据库连接从当前线程中解除绑定TransactionSynchronizationManager.unbindResource(this.dataSource);}Connection con = txObject.getConnectionHolder().getConnection();try {// 恢复数据库连接的autoCommit状态if (txObject.isMustRestoreAutoCommit()) {con.setAutoCommit(true); }// 负责重置数据库连接信息,包括隔离级别、readOnly属性等DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel());}catch (Throwable ex) {logger.debug("Could not reset JDBC Connection after transaction", ex);}if (txObject.isNewConnectionHolder()) {// 如果是新的数据库连接则将数据库连接放回连接池DataSourceUtils.releaseConnection(con, this.dataSource);}txObject.getConnectionHolder().clear();
}

AbstractPlatformTransactionManager.resume() 如果事务执行前有事务挂起,那么当前事务执行结束后需要将挂起的事务恢复,挂起事务时保存了原事务信息,重置了当前事务信息,所以恢复操作就是将当前的事务信息设置为之前保存的原事务信息。

java

 protected final void resume(Object transaction, AbstractPlatformTransactionManager.SuspendedResourcesHolder resourcesHolder) throws TransactionException {if (resourcesHolder != null) {Object suspendedResources = resourcesHolder.suspendedResources;if (suspendedResources != null) {// 执行 恢复挂起事务 ,绑定资源bindResourcethis.doResume(transaction, suspendedResources);}List<TransactionSynchronization> suspendedSynchronizations = resourcesHolder.suspendedSynchronizations;if (suspendedSynchronizations != null) {TransactionSynchronizationManager.setActualTransactionActive(resourcesHolder.wasActive);TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(resourcesHolder.isolationLevel);TransactionSynchronizationManager.setCurrentTransactionReadOnly(resourcesHolder.readOnly);TransactionSynchronizationManager.setCurrentTransactionName(resourcesHolder.name);this.doResumeSynchronization(suspendedSynchronizations);}}}

TransactionAspectSupport.cleanupTransactionInfo()清除当前节点的事务消息,将旧事务节点信息通过thradLoacl更新到当前线程。

java

protected void cleanupTransactionInfo(TransactionAspectSupport.TransactionInfo txInfo) {if (txInfo != null) {//从当前线程的 ThreadLocal 获取上层的事务信息,将当前事务出栈,继续执行上层事务txInfo.restoreThreadLocalStatus();}}private void restoreThreadLocalStatus() {//当前事务处理完之后,恢复上层事务上下文 TransactionAspectSupport.transactionInfoHolder.set(this.oldTransactionInfo);}

小结

如果方法正常执行完成且没有异常,调用commitTransactionAfterReturning()方法。如果执行中出现异常,调用completeTransactionAfterThrowing()方法。

两个方法内部都会判断是否存在事务以及是否满足回滚条件来决定最终执行提交操作还是回滚操作。

相关文章:

Spring提供了很好事务管理机制

事务管理在系统开发中是不可缺少的一部分&#xff0c;Spring提供了很好事务管理机制 分类 主要分为编程式事务和声明式事务两种。 编程式事务 是指在代码中手动的管理事务的提交、回滚等操作&#xff0c;代码侵入性比较强&#xff0c;如下示例&#xff1a; try {//TODO so…...

Selenium 和 Playwright两大框架的不同之处

自动化测试工具百花齐放&#xff0c;其中 Selenium 和 Playwright 是两大热门框架&#xff0c;谁才是你的最佳选择&#xff1f;面对企业项目的真实需求&#xff0c;它们的差异究竟在哪儿&#xff1f; Selenium 和 Playwright 是两种流行的自动化测试工具&#xff0c;它们都被用…...

【计算机视觉】轮廓检测

一、轮廓检测 在计算机视觉中&#xff0c;轮廓检测是另一个比较重要的任务&#xff0c;不单是用来检测图像或者视频帧中物体的轮廓&#xff0c;而且还有其他操作与轮廓检测相关。 以下代码展示了如何使用 OpenCV 进行 图像阈值处理、寻找图像轮廓 和 绘制轮廓 的完整流程&…...

【Linux】深入Linux:GCC/G++编译器实用指南

Linux相关知识点可以通过点击以下链接进行学习一起加油&#xff01;初识指令指令进阶权限管理yum包管理与vim编辑器 在Linux系统中&#xff0c;理解和掌握GCC/G编译器是开发者不可或缺的技能之一。本文将深入探讨它们的工作原理和实际运用&#xff0c;帮助读者更好地利用这些强…...

【未来编程:AI如何通过合成复用原则优化设计】

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” 文章目录 前言合成复用原则含义 继承复用含义UML图实现代码运行结果及分析优缺点 合成复用&#xff08;我有这…...

【Rust自学】5.3. struct的方法(Method)

喜欢的话别忘了点赞、收藏加关注哦&#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵&#xff01;(&#xff65;ω&#xff65;) 5.3.1. 什么是方法(Method) 方法和函数类似&#xff0c;也是用fn关键字进行声明&#xff0c;方法也有名称&#xff0c;也有参数&#xff…...

单片机 STM32入门

一、什么是单片机 单片机&#xff08;Microcontroller Unit&#xff0c;MCU&#xff09;是一种集成电路芯片&#xff0c;它将计算机的CPU、存储器&#xff08;包括RAM和ROM&#xff09;、输入/输出接口等集成在一个芯片上。单片机通常用于嵌入式系统&#xff0c;能够执行特定的…...

OneCode:开启高效编程新时代——企业定制出码手册

一、概述 OneCode 的 DSM&#xff08;领域特定建模&#xff09;出码模块是一个强大的工具&#xff0c;它支持多种建模方式&#xff0c;并具有强大的模型转换与集成能力&#xff0c;能够提升开发效率和代码质量&#xff0c;同时方便团队协作与知识传承&#xff0c;还具备方便的仿…...

学python还是学java?哪个相对来说比较容易上手?

在比较Python和Java哪个更容易上手时&#xff0c;可以从多个维度进行分析&#xff0c;包括语法简洁性、学习资源、应用领域、学习曲线等。 一、语法简洁性 Python&#xff1a;Python的语法简洁明了&#xff0c;更接近自然语言&#xff0c;易于理解和记忆。它使用缩进来表示代…...

C语言项目 天天酷跑(上篇)

前言 这里讲述这个天天酷跑是怎么实现的&#xff0c;我会在天天酷跑的下篇添加源代码&#xff0c;这里会讲述天天酷跑这个项目是如何实现的每一个思路&#xff0c;都是作者自己学习于别人的代码而创作的项目和思路&#xff0c;这个代码和网上有些许不一样&#xff0c;因为掺杂了…...

Windows 11 安装 Dify 完整指南 非docker环境

# Windows 11 安装 Dify 完整指南## 前置要求- Python 3.11 - Node.js 18 - PostgreSQL 14 - Redis for Windows - Git - Ollama (可选&#xff0c;用于本地模型)## 详细安装步骤### 1. 安装必要软件1. **Python 3.11**- 从 https://www.python.org/downloads/release/python-…...

MySQL变量

文章目录 MySQL变量系统变量查看系统变量设置系统变量 自定义变量用户变量局部变量 MySQL变量 MySQL变量分为系统变量和自定义变量 系统变量 系统变量有全局变量和会话变量 查看系统变量 #查看全局系统变量 show global variables; #根据条件查询全局系统变量 show global …...

Ubuntu离线安装Docker容器

前言 使用安装的工具snap安装在沙箱中&#xff0c;并且该沙箱之外的权限有限。docker无法从其隔离的沙箱环境访问外部文件系统。 目录 前言准备环境卸载已安装的Docker环境快照安装的Dockerapt删除Docker 安装docker-compose下载执行文件将文件移到 /usr/local/bin赋予执行权限…...

ensp 关于acl的运用和讲解

ACL&#xff08;Access Control List&#xff0c;访问控制列表&#xff09;是一种常用于网络设备&#xff08;如路由器、交换机&#xff09;上的安全机制&#xff0c;用于控制数据包的流动与访问权限。ACL 可以指定哪些数据包允许进入或离开某个网络接口&#xff0c;基于不同的…...

Linux(Centos 7.6)yum源配置

yum是rpm包的管理工具&#xff0c;可以自动安装、升级、删除软件包的功能&#xff0c;可以自动解决软件包之间的依赖关系&#xff0c;使得用户更方便软件包的管理。要使用yum必须要进行配置&#xff0c;个人将其分为三类&#xff0c;本地yum源、局域网yum源、第三方yum源&#…...

[WASAPI]音频API:从Qt MultipleMedia走到WASAPI,相似与不同

[WASAPI] 从Qt MultipleMedia 来看WASAPI 最近在学习有关Windows上的音频驱动相关的知识&#xff0c;在正式开始说WASAPI之前&#xff0c;我想先说一说Qt的Multiple Media&#xff0c;为什么呢&#xff1f;因为Qt的MultipleMedia实际上是WASAPI的一层封装&#xff0c;它在是线…...

什么是MVCC?

MVCC&#xff08;多版本并发控制&#xff0c;Multi-Version Concurrency Control&#xff09;是一种用于数据库管理系统中的并发控制的技术。它允许多个事务同时对同一数据进行读取和修改&#xff0c;而不会相互干扰&#xff0c;从而提高了数据库的并发性能。以下是对MVCC的详细…...

C/C++基础错题归纳

文章目录 第1天1.下面程序段的运行结果是&#xff1a;答案知识补充 2.当一个类A 中没有声明任何成员变量与成员函数,这时sizeof(A)的值是多少&#xff1f;答案知识补充 3.下面程序输出是什么?答案其他讲解 第1天 1.下面程序段的运行结果是&#xff1a; char C[5]{‘a’,’b’…...

Nginx 常用安全头

Web 应用中配置 HTTP 安全响应头是提升网站安全性的重要一步。合理配置 Nginx 的安全头&#xff0c;可以抵御常见的安全威胁&#xff08;如 XSS、点击劫持、MIME 类型嗅探等&#xff09;&#xff0c;增强用户隐私保护和传输安全性。 常见的 HTTP 安全头及其作用 1. Content-Se…...

消息队列(一)消息队列的工作流程

什么是消息队列 首先&#xff0c;代入一个场景&#xff0c;我现在做一个多系统的集成&#xff0c;分别有系统A、B、C、D四个系统&#xff0c;A系统因为使用产生了业务数据&#xff0c;B、C、D需要使用这些数据做相关的业务处理和运算&#xff0c;最基本的做法就是通过接口通信…...

LeetCode 2605 从两个数字数组里生成最小数字

探寻两个数组数位关联下的最小数字问题 题目描述 给定两个只包含 1 到 9 之间数字的数组 nums1 和 nums2&#xff0c;并且每个数组中的元素都是互不相同的。我们需要返回最小的数字&#xff0c;要求这个数字满足两个数组都至少包含这个数字的某个数位。例如&#xff0c;若 nu…...

AI新书推荐:深度学习和大模型原理与实践(清华社)

本书简介 在这个信息爆炸、技术革新日新月异的时代&#xff0c;深度学习作为人工智能领域的重要分支&#xff0c;正引领着新一轮的技术革命。《深度学习和大模型原理与实践》一书&#xff0c;旨在为读者提供深度学习及其大模型技术的全面知识和实践应用的指南。 本书特色在于…...

32单片机串口数据接收、空闲IDLE中断详解

一、前提说明 一开始写单片机程序的时候不太清楚空闲中断这个东西&#xff0c;每次用串口接收数据&#xff0c;都要再开一个定时器&#xff0c;在定时器内进行倒计时&#xff0c;每次接收数据就重置计时时间&#xff0c;计时结束就触发中断&#xff0c;再判断所有接收的数据&am…...

WebRtc webrtc-streamer部署

文章目录 本文档只是为了留档方便以后工作运维&#xff0c;或者给同事分享文档内容比较简陋命令也不是特别全&#xff0c;不适合小白观看&#xff0c;如有不懂可以私信&#xff0c;上班期间都是在得 WebRtc webrtc-streamer 部署 docker run -p 8000:8000 -it mpromonet/webrt…...

shiro注入filter内存马(绕过长度限制)

shiro环境 https://github.com/yyhuni/shiroMemshell&#xff08;实验环境&#xff09; 这里用的 Client_memshell.java package com.example.demo;import javassist.ClassPool; import javassist.CtClass; import org.apache.shiro.crypto.AesCipherService; import org.ap…...

Springboot + vue3 实现大文件上传方案:秒传、断点续传、分片上传、前端异步上传

参考&#xff1a;https://juejin.cn/post/6870837414852886542#heading-9 一般计算大文件的md5都是前端来做&#xff0c;因为如果后端来做&#xff0c;那得等到上传成功后才能计算md5值&#xff0c;并且读取的时间也很长。 为了解决文件大传输慢的问题&#xff0c;前端可以通…...

渗透Vulnhub-DC-9靶机

本篇文章旨在为网络安全渗透测试行业靶机教学。通过阅读本文&#xff0c;读者将能够对渗透Vulnhub系列DC-6靶机有定的了解 一、信息收集阶段 DC-9靶场信息: DC-9靶场介绍&#xff1a; https://www.vulnhub.com/entry/dc-9,412/ DC-9靶场下载&#xff1a; https://download.vu…...

springboot477基于vue技术的农业设备租赁系统(论文+源码)_kaic

摘 要 使用旧方法对农业设备租赁系统的信息进行系统化管理已经不再让人们信赖了&#xff0c;把现在的网络信息技术运用在农业设备租赁系统的管理上面可以解决许多信息管理上面的难题&#xff0c;比如处理数据时间很长&#xff0c;数据存在错误不能及时纠正等问题。这次开发的农…...

CentOS常见命令

CentOS&#xff08;Community ENTerprise Operating System&#xff09;基于Red Hat Enterprise Linux&#xff08;RHEL&#xff09;源代码开发&#xff0c;是常用的Linux发行版之一。在CentOS系统中&#xff0c;有许多命令用于管理和操作系统&#xff0c;以下是一些CentOS系统…...

oracle 设置归档日志存放路径

oracle 设置归档日志存放路径 1、创建新目录 mkdir /archive chown -R oracle:oinstall /archive 注&#xff1a;条件允许的话&#xff0c;/archive 目录应独立挂载。1、便于监控目录使用率&#xff1b;2、避免和其它文件混淆&#xff0c;便于管理。 2、设置归档日志存放路…...

机器学习1-简单神经网络

相比传统的机器学习算法&#xff0c;深度学习做出了哪些改进呢&#xff1f;其实两者在理论结构上是一致的&#xff0c;即&#xff1a;模型假设、评价函数和优化算法&#xff0c;其根本差别在于假设的复杂度 构建简单神经网络&#xff08;未训练&#xff09;&#xff1a; # 封装…...

C++的侵入式链表

非侵入式链表 非侵入式链表是一种链表数据结构&#xff0c;其中每个元素&#xff08;节点&#xff09;并不需要自己包含指向前后节点的指针。链表的结构和节点的存储是分开的&#xff0c;链表容器会单独管理这些指针。 常见的非侵入式链表节点可以由以下所示&#xff0c;即&a…...

MFC案例:图片文件转图标(ico)格式

本案例程序目的是将一般图像文件转换成图标格式(ico)。实现起来不是很复杂&#xff0c;这里为了介绍MFC的具体使用方法&#xff0c;在程序界面上分成几个功能块&#xff0c;包括&#xff1a;打开图像文件、选择ICON大小、转换、预览、保存等。相关具体步骤如下&#xff1a; 一、…...

【从零开始入门unity游戏开发之——unity篇02】unity6基础入门——软件下载安装、Unity Hub配置、安装unity编辑器、许可证管理

文章目录 一、软件下载安装1、Unity官网2、下载Unity Hub 二、修改Unity Hub配置1、设置Unity Hub中文语言2、修改默认存储目录 三、安装unity编辑器1、点击安装编辑器2、版本选择3、关于版本号4、安装模块选择5、等待下载完成自动安装即可6、追加unity和模块 四、许可证管理专…...

东子生物完成A轮战略融资,数字商品交易全新升级为数商时代

2024年11月23日&#xff0c;东子生物数字时代正式上线&#xff0c;标志着公司全面迈入“数商时代”&#xff0c;作为国内领先的生物科技企业&#xff0c;东子生物在数字化浪潮中精准布局&#xff0c;以创新科技推动产业升级&#xff0c;以全新的思维引领健康产业&#xff0c;兼…...

数据结构经典算法总复习(上卷)

第一章&#xff1a;数据结构导论 无重要考点&#xff0c;仅需了解时间复杂度。 第二章&#xff1a;线性表 1.获得线性表第i个元素 void GetElem_sq(SqList L, int i, ElemType &e) {if (i<1 || i>L.length) ErrorMsg("Invalid i value"); //注意错误监…...

电脑使用CDR时弹出错误“计算机丢失mfc140u.dll”是什么原因?“计算机丢失mfc140u.dll”要怎么解决?

电脑使用CDR时弹出“计算机丢失mfc140u.dll”错误&#xff1a;原因与解决方案 在日常电脑使用中&#xff0c;我们时常会遇到各种系统报错和文件丢失问题。特别是当我们使用某些特定软件&#xff0c;如CorelDRAW&#xff08;简称CDR&#xff09;时&#xff0c;可能会遇到“计算…...

oracle使用imp命令导入dmp文件

需求&#xff1a; 增量导入 tbl_servicelegalclause 表数据&#xff08;dmp格式&#xff09;。 导入思路&#xff1a;使用 dba 创建一个 临时库&#xff0c;先将 tbl_servicelegalclause.dmp&#xff08;增量的数据&#xff09; 文件导入到 临时库&#xff0c;然后确认临时库数…...

电脑出现 0x0000007f 蓝屏问题怎么办,参考以下方法尝试解决

电脑蓝屏是让许多用户头疼的问题&#xff0c;其中出现 “0x0000007f” 错误代码更是较为常见且棘手。了解其背后成因并掌握修复方法&#xff0c;能帮我们快速恢复电脑正常运行。 一、可能的硬件原因 内存问题 内存条长时间使用可能出现物理损坏&#xff0c;如金手指氧化、芯片…...

Logback日志框架中的继承机制详解

在Logback框架中&#xff0c;logger的继承机制是基于层级结构&#xff08;hierarchical context&#xff09;工作的。每个logger都被分配一个名称&#xff0c;这个名称可以看作是一个路径或目录结构&#xff0c;从而形成了一个逻辑上的树状结构。这种结构使得日志记录具有很强的…...

[Unity]【图形渲染】【游戏开发】Shader数学基础4-更多矢量运算

在计算机图形学和着色器编程中,矢量运算是核心的数学工具之一。矢量用于描述空间中的位置、方向、速度等各种物理量,并在图形变换、光照计算、纹理映射等方面起着至关重要的作用。本篇文章将详细讲解矢量和标量之间的乘法与除法、矢量的加法与减法、矢量的模与单位矢量、点积…...

node.js的异步工作之---回调函数与回调地狱

回调函数&#xff1a;在 Node.js 中&#xff0c;很多 API 都是异步的&#xff0c;通常通过回调函数来处理操作完成后的结果。这种回调模式虽然非常高效&#xff0c;但会导致代码逐渐变得难以维护&#xff0c;尤其是当有多个异步操作嵌套时&#xff08;即回调地狱&#xff09;。…...

tcp 的三次握手与四次挥手

问1: 请你说一下tcp的三次握手一次握手两次握手三次握手问: 为什么不四(更多)次握手? 问 2: 请说一下 tcp 的 4 次挥手一次挥手两次挥手问题:能不能等到数据传输完成再返回 ack? 三次挥手四次挥手问: 为什么要等两个最大报文存在时间? bg: tcp 是可靠的连接,如何保证 建立连…...

《三角洲行动》游戏运行时提示“缺失kernel32.dll”:问题解析与解决方案

《三角洲行动》游戏运行时提示“缺失kernel32.dll”&#xff1a;问题解析与解决方案 作为软件开发领域的一名从业者&#xff0c;我深知电脑游戏运行过程中可能遇到的各种挑战&#xff0c;尤其是文件丢失、文件损坏以及系统报错等问题。今天&#xff0c;我将以经典游戏《三角洲…...

Android——自定义按钮button

项目中经常高频使用按钮&#xff0c;要求&#xff1a;可设置颜色&#xff0c;有圆角且有按下效果的Button 一、自定义按钮button button的代码为 package com.fslihua.clickeffectimport android.annotation.SuppressLint import android.content.Context import android.gra…...

Pandas基础学习(1)

之前看的pandas的教材和课程里&#xff0c;内容参差不齐&#xff0c;实际使用很少的方法的内容有点多&#xff0c;导致很乱而且记不住那么多&#xff0c;这个帖子尽量用最少的文字&#xff0c;最精炼的语言来总结比较实用的方法&#xff0c;内容主要来源于《利用python进行数据…...

20241224在Ubuntu20.04.6下给X99平台上的M6000显卡安装驱动程序

20241224在Ubuntu20.04.6下给X99平台上的M6000显卡安装驱动程序 2024/12/24 16:18 下载驱动程序&#xff1a; https://www.nvidia.cn/drivers/lookup/ https://www.nvidia.cn/drivers/results/ https://www.nvidia.cn/drivers/details/237923/ https://www.nvidia.cn/drivers/l…...

批量多线程给TXT文档插入相关腾讯AI【高质量无水印无版权】原创图片

给蜘蛛访问的网站文章插入相关图片&#xff0c;可以带来以下好处&#xff1a; ‌1、提升用户体验‌&#xff1a;图片能够直观地展示文章内容&#xff0c;帮助用户更好地理解和消化信息。对于阅读者来说&#xff0c;图文并茂的内容往往更具吸引力&#xff0c;也能提高他们的阅读…...

保护模式基本概念

CPU 架构 RISC&#xff08;Reduced Instruction Set Computer&#xff09; 中文即"精简指令集计算机”。RISC构架的指令格式和长度通常是固定的&#xff08;如ARM是32位的指令&#xff09;、且指令和寻址方式少而简单、大多数指令在一个周期内就可以执行完毕 CISC&…...

mysql 查询优化之字段建立全文索引

最近在接手一些老项目时发现表设计存在问题导致查询较慢 例如一张旧表的设计&#xff1a; 模糊匹配某个关键字时,需要十几秒左右,而且表的数据量不多 都知道mysql8.0版本InnoDB引擎都支持全文索引了,因此可以在content建立全文索引&#xff0c;但全文索引对中文支持并不完善…...