Spring事务(一) @EnableTransactionManagement注解生效流程

Spring事务(一) @EnableTransactionManagement注解生效流程

说明 spring事务数据库默认支持的是mysql数据库,其他数据库接入spring事务体系需要对应驱动提供自己的事务实现

使用

在开发中如果我们要开启spring事务支持,目前spring推荐注解驱动开发,减少xml配置文件。一般我们要在spring-boot的启动类或着在配置类中添加**@EnableTransactionManagement**注解,开启事务体系。同时要注入DataSource(数据库连接对象),TransactionManager(事务管理器对象)来提供支持。

java 复制代码
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
	/**
	 * 事务管理器
	 */
    @Bean
    public TransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
	/**
	 * 数据库链接
	 * @return
	 */
	@Bean
	public DataSource dataSource() {
		DruidDataSource data = new DruidDataSource();
		data.setDriverClassName(driverClassname);
		data.setUrl(url);
		data.setUsername(username);
		data.setPassword(password);
		return data;
	}
}

然后再对应需要事务的方法上加上@Transactional注解之后,spirng事务就启动完成。

设计思想

  1. 一个数据库事务,首先我们要建立数据库连接,开启事务,写入SQL语句,提交事务或者回滚。这是一个基础前提。
  2. 如果我们作为spring的事务设计者,如何为spirng这个开源框架设置事务,并达到以下效果,实现通过spring来完成事务管理
    1. 使用spring的人不用把事务控制代码耦合到业务代码中,以注解方式或者配置的方式能快速开启事务,方法内只写业务代码。
    2. 正常流程下没有异常能够正常提交事务。
    3. 如果业务代码出现异常能够回滚事务。
    4. 业务代码如果会出现方法之间的调用,比如A方法调用B方法,A方法上有事务注解,B方法有事务注解。那么根据不同场景的业务需要,有时候A方法单独需要一个事务,B方法单独需要一个事务;有时候A和B方法共用一个事务;如何实现?(这也就是我们常说的事务传播特性)
  3. 数据库产品众多,如何支持MySQL,MongoDB等。

所以,我们在实现过程最重要的是要能够提炼开启事务,关闭事务,回滚事务等功能到框架层面,因为这些代码流程都是一样的,业务开发无需关注这些,提升整体开发项目,这也是开源项目的一个初衷。

实现逻辑

  1. 整体事务体系基础是动态代理增强,事务是建立在这个基础上进行二次开发实现。
  2. 以注解驱动开发,通过@EnableTransactionManagement注解开启spring事务处理。
  3. 通过@EnableTransactionManagement注解导入TransactionManagementConfigurationSelector类,通过TransactionManagementConfigurationSelector导入事务依赖bean(BeanFactoryTransactionAttributeSourceAdvisor,TransactionInterceptor,TransactionAttributeSource,AutoProxyRegistrar)的同时开启容器动态代理支持。
  4. 因为提前开启了动态代理,所以在进行创建对应的bean时会判断当前bean是否存在@Transactional注解,如果存在就会给当前类织入事务增强逻辑创建代理类并返回。
  5. 事务处理体系入口是TransactionInterceptor类,在调用@Transactional修饰的方法会进入TransactionInterceptor,然后由框架进行事务管理。

源码分析

前置交代

spring中对于@Configuration注解修饰的配置类处理流程

  1. 在容器刷新refresh()方法流程中有一步invokeBeanFactoryPostProcessors(beanFactory)调用所有BeanFactoryPostProcessor的实现类;这里会进入ConfigurationClassPostProcessor进行@Configuration注解解析,然后由ConfigurationClassParser的parse方法预处理,然后最终由ConfigurationClassParser的doProcessConfigurationClass方法完成最终的解析。下面的processImports方法就是doProcessConfigurationClass中的其中一个步骤。
  2. 这个方法的主要操作
    1. 遍历所有通过@Import注解导入的类,分别进行不同处理。
    2. 如果导入类是ImportSelector子类并实现了selectImports方法就调用selectImports方法,得到想要导入的importClassNames列表,然后将importClassNames列表再次调用processImports方法进行处理。实现递归处理。
    3. 如果导入类是ImportBeanDefinitionRegistrar子类,在后面进行统一处理。
java 复制代码
//在ConfigurationClassParser类中
private void  processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
			Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
			boolean checkForCircularImports) {

		if (importCandidates.isEmpty()) {
			return;
		}

		// 通过ConfigurationClass对象的importStack属性判断是否存在循环依赖,如果存在则抛出异常
		if (checkForCircularImports && isChainedImportOnStack(configClass)) {
			this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
		}
		else {
			// 将当前的ConfigurationClass对象压入importStack属性中
			this.importStack.push(configClass);
			try {
				// 遍历通过@Import注解导入的所有类
				for (SourceClass candidate : importCandidates) {
					// 判断导入的类是否实现了ImportSelector接口,如果实现了,那么就会调用selectImports方法,返回需要导入的配置类
          // TransactionManagementConfigurationSelector是ImportSelector接口的实现子类
					if (candidate.isAssignable(ImportSelector.class)) {
						// 获取全类名
						Class<?> candidateClass = candidate.loadClass();
						// 通过反射创建ImportSelector对象
						ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
								this.environment, this.resourceLoader, this.registry);
						// 获取selector的过滤器
						Predicate<String> selectorFilter = selector.getExclusionFilter();
						if (selectorFilter != null) {
							exclusionFilter = exclusionFilter.or(selectorFilter);
						}
						// 处理DeferredImportSelector类型,TransactionManagementConfigurationSelector不是DeferredImportSelector实现子类
						if (selector instanceof DeferredImportSelector) {
							this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
						}
						else {
							// 获取引入的类,然后递归解析通过@Import导入的类,并调用导入类的selectImports方法,获取调用后得到的获取目标导入类的beanName数组
              // 这里会调用到TransactionManagementConfigurationSelector的selectImports方法获取对应的importClassNames
							String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
              // 将获取到的importClassNames转为SourceClass类型
							Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
							// 递归调用processImports方法处理前面转换的importSourceClasses数组
							processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
						}
					}
					// 判断导入的类是否实现了ImportBeanDefinitionRegistrar接口,AutoProxyRegistrar类实现了ImportBeanDefinitionRegistrar接口,这里就直接处理了AutoProxyRegistrar
					else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
						Class<?> candidateClass = candidate.loadClass();
						ImportBeanDefinitionRegistrar registrar =
								ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
										this.environment, this.resourceLoader, this.registry);
						//将AutoProxyRegistrar放入importBeanDefinitionRegistrars集合中,在后面进行统一处理
						configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
					}
					else {
						this.importStack.registerImport(
								currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
						// 递归调用processConfigurationClass方法处理导入的类,放入configurationClasses中
            // ProxyTransactionManagementConfiguration会进入下面方法,放入configurationClasses集合中,后面进行统一处理
						processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
					}
				}
			}
			catch (BeanDefinitionStoreException ex) {
				throw ex;
			}
			catch (Throwable ex) {
				throw new BeanDefinitionStoreException(
						"Failed to process import candidates for configuration class [" +
						configClass.getMetadata().getClassName() + "]", ex);
			}
			finally {
				this.importStack.pop();
			}
		}
	}

上面有个关键步骤是

java 复制代码
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());

这一步很关键,容器启动走到这里就会调用TransactionManagementConfigurationSelector的selectImports方法,这样@EnableTransactionManagement注解就和容器产生联动。spring容器就开始接管事务处理。

@Transactional注解生效流程

@EnableTransactionManagement注解

java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
    boolean proxyTargetClass() default false;
    AdviceMode mode() default AdviceMode.PROXY;
    int order() default Ordered.LOWEST_PRECEDENCE;
}

这个注解的功能就是通过@Import注解导入TransactionManagementConfigurationSelector类,后续处理都是建立在TransactionManagementConfigurationSelector这个类基础上进行的。

TransactionManagementConfigurationSelector类

java 复制代码
public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {
    @Override
    protected String[] selectImports(AdviceMode adviceMode) {
       switch (adviceMode) {
          case PROXY:
             return new String[] {AutoProxyRegistrar.class.getName(),
                   ProxyTransactionManagementConfiguration.class.getName()};
          case ASPECTJ:
             return new String[] {determineTransactionAspectClass()};
          default:
             return null;
       }
    }

    private String determineTransactionAspectClass() {
       return (ClassUtils.isPresent("javax.transaction.Transactional", getClass().getClassLoader()) ?
             TransactionManagementConfigUtils.JTA_TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME :
             TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME);
    }

}

这个类的主要作用就是通过selectImports方法让容器启动过程中进行调用,激活容器的事务处理功能。

AutoProxyRegistrar类

  1. 实际上只干了一件事,判断导入当前类的注解所在的类上的所有注解是否支持开启动态代理,如果支持开启则注入动态代理的内部类internalAutoProxyCreator,用于后面代理处理流程;如果不支持开启动态代理进行日志打印。
  2. spring事务体系中是基于动态代理实现的,如果不开启动态代理,实际上是没办法实现事务体系的。但是spring又想在使用过程中只需要一个注解就能开启事务,所以通过AutoProxyRegistrar开启容器动态代理功能。
  3. AutoProxyRegistrar实现了ImportBeanDefinitionRegistrar接口,在ConfigurationClassParse的processImports方法专门处理了实现ImportBeanDefinitionRegistrar接口的类。会把这个类统一放到一个map集合中,后续统一进行调用registerBeanDefinitions方法。
java 复制代码
public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
	private final Log logger = LogFactory.getLog(getClass());
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		boolean candidateFound = false;
		// 获取导入当前配置类上的所有注解
		Set<String> annTypes = importingClassMetadata.getAnnotationTypes();
		// 遍历注解
		for (String annType : annTypes) {
			// 获取注解上的所有属性
			AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);
			if (candidate == null) {
				continue;
			}
			// 获取注解上的mode和proxyTargetClass属性
			Object mode = candidate.get("mode");
			Object proxyTargetClass = candidate.get("proxyTargetClass");
			// 如果mode和proxyTargetClass都不为空,且mode是AdviceMode类型,proxyTargetClass是Boolean类型,说明当前要开启AOP
			if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&
					Boolean.class == proxyTargetClass.getClass()) {
				candidateFound = true;
				// 如果mode是PROXY,且proxyTargetClass是true,说明要使用CGLIB代理
				if (mode == AdviceMode.PROXY) {
					// 注册aop的自动代理创建器,后续开cdlib代理用得到
					AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
					if ((Boolean) proxyTargetClass) {
						AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
						return;
					}
				}
			}
		}
		// 没有找到合适的注解,打印日志
		if (!candidateFound && logger.isInfoEnabled()) {
			String name = getClass().getSimpleName();
			logger.info(String.format("%s was imported but no annotations were found " +
					"having both 'mode' and 'proxyTargetClass' attributes of type " +
					"AdviceMode and boolean respectively. This means that auto proxy " +
					"creator registration and configuration may not have occurred as " +
					"intended, and components may not be proxied as expected. Check to " +
					"ensure that %s has been @Import'ed on the same class where these " +
					"annotations are declared; otherwise remove the import of %s " +
					"altogether.", name, name, name));
		}
	}
}

ProxyTransactionManagementConfiguration类

java 复制代码
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
    @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    // 事务切面增强器,持有transactionAttributeSource和transactionInterceptor信息,
    public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
          TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) {
				// 这个advisor很重要,在aop处理过程中会用到,通过这个advisor判断当前bean是否需要进行事务代理增强并完成对应的代理类的生成。
       BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
       // 设置TransactionAttributeSource,用于后面进行@Transactional注解的解析
       advisor.setTransactionAttributeSource(transactionAttributeSource);
       // 设置TransactionInterceptor,用于后面进行事务的拦截增强
       advisor.setAdvice(transactionInterceptor);
       if (this.enableTx != null) {
          advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
       }
       return advisor;
    }
    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
		// 主要完成@Transactional注解的解析,用在TransactionAttributeSourcePointcut的matches方法中
    public TransactionAttributeSource transactionAttributeSource() {
       return new AnnotationTransactionAttributeSource();
    }

    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	  // 事务切面增强器,代理了@Transactional注解的方法,进行事务的拦截增强
    public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource) {
       TransactionInterceptor interceptor = new TransactionInterceptor();
       interceptor.setTransactionAttributeSource(transactionAttributeSource);
       if (this.txManager != null) {
          interceptor.setTransactionManager(this.txManager);
       }
       return interceptor;
    }

}

先说说这个类名,代理事务管理配置。通过类名就能发现这个和代理,事务都有关系。也是事务体系中很重要的一个类。这个类本身被@Configuration注解修饰,里面又存在被@Bean修饰的方法。在容器启动过程中进行配置解析的时候会调用被@Bean注解修饰的方法注入对应的bean实例。

BeanFactoryTransactionAttributeSourceAdvisor类
java 复制代码
// 事务增强advisor
public class BeanFactoryTransactionAttributeSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {
	@Nullable
	private TransactionAttributeSource transactionAttributeSource;
  
  // 这个pointcut很重要,后面在进行切面处理的时候要通过这个pointcut进行@Transational注解解析
	private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() {
		@Override
		@Nullable
		protected TransactionAttributeSource getTransactionAttributeSource() {
			return transactionAttributeSource;
		}
	};
	public void setTransactionAttributeSource(TransactionAttributeSource transactionAttributeSource) {
		this.transactionAttributeSource = transactionAttributeSource;
	}
	public void setClassFilter(ClassFilter classFilter) {
		this.pointcut.setClassFilter(classFilter);
	}

	@Override
	public Pointcut getPointcut() {
		return this.pointcut;
	}

}
TransactionAttributeSourcePointcut类
java 复制代码
abstract class TransactionAttributeSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {

    protected TransactionAttributeSourcePointcut() {
       setClassFilter(new TransactionAttributeSourceClassFilter());
    }
  	// 关键方法,非常关键
    @Override
    public boolean matches(Method method, Class<?> targetClass) {
       TransactionAttributeSource tas = getTransactionAttributeSource();
       // 进行事务注解解析
       // 若事务属性原为null或者解析出来的事务注解属性不为空,表示方法匹配
       // 实际进入AbstractFallbackTransactionAttributeSource的getTransactionAttribute方法
       return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
    }

    @Override
    public boolean equals(@Nullable Object other) {
       if (this == other) {
          return true;
       }
       if (!(other instanceof TransactionAttributeSourcePointcut)) {
          return false;
       }
       TransactionAttributeSourcePointcut otherPc = (TransactionAttributeSourcePointcut) other;
       return ObjectUtils.nullSafeEquals(getTransactionAttributeSource(), otherPc.getTransactionAttributeSource());
    }

    @Override
    public int hashCode() {
       return TransactionAttributeSourcePointcut.class.hashCode();
    }

    @Override
    public String toString() {
       return getClass().getName() + ": " + getTransactionAttributeSource();
    }
    @Nullable
    protected abstract TransactionAttributeSource getTransactionAttributeSource();
    private class TransactionAttributeSourceClassFilter implements ClassFilter {

       @Override
       public boolean matches(Class<?> clazz) {
          if (TransactionalProxy.class.isAssignableFrom(clazz) ||
                TransactionManager.class.isAssignableFrom(clazz) ||
                PersistenceExceptionTranslator.class.isAssignableFrom(clazz)) {
             return false;
          }
          TransactionAttributeSource tas = getTransactionAttributeSource();
          return (tas == null || tas.isCandidateClass(clazz));
       }
    }

}
  1. 在上面通过AutoProxyRegistrar开启容器代理功能之后,容器在进行bean创建过程中,在调用容器内所有BeanPostProcessor的postProcessAfterInitialization的方法时会进入aop处理流程。
  2. 在aop处理流程中,会首先找出所有的容器内的Advisor子类,这里会找到BeanFactoryTransactionAttributeSourceAdvisor。然后遍历所有的Advisor,在遍历过程中找出能够被当前bean使用的Advisor。而判断是否能够被当前bean使用就是通过TransactionAttributeSourcePointcut的matches方法来实现的。
  3. 在TransactionAttributeSourcePointcut的matches方法,获取了TransactionAttributeSource对象实例,然后通过tas.getTransactionAttribute(method, targetClass)方法来解析当前方法上的@Transactional注解并获取注解信息,返回组装好的TransactionAttribute对象
    1. 实际上这里调用的是AbstractFallbackTransactionAttributeSource的getTransactionAttribute方法完成解析,虽然在前面ProxyTransactionManagementConfiguration方法中我们向容器中注入的是AnnotationTransactionAttributeSource对象,但是AnnotationTransactionAttributeSource本身继承了AbstractFallbackTransactionAttributeSource。
  4. 如果返回的TransactionAttributeSource不为空,说明当前方法存在@Transactional注解,也就意味着当前点类需要进行代理增强。然后会为当前类生成代理类,后面调用方法的时候就进入代理类进行事务处理。
AbstractFallbackTransactionAttributeSource的getTransactionAttribute方法
java 复制代码
// 解析当前方法上的
@Override
@Nullable
public TransactionAttribute getTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
    if (method.getDeclaringClass() == Object.class) {
       return null;
    }

    // First, see if we have a cached value.
    // 缓存键 方法名和类名,记录当前方法上的事务注解传播级别
    Object cacheKey = getCacheKey(method, targetClass);
    TransactionAttribute cached = this.attributeCache.get(cacheKey);
    if (cached != null) {
       // Value will either be canonical value indicating there is no transaction attribute,
       // or an actual transaction attribute.
       if (cached == NULL_TRANSACTION_ATTRIBUTE) {
          return null;
       }
       else {
          return cached;
       }
    }
    else {
       // We need to work it out.
       // 获取@Transactional事务注解属性
       TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass);
       // Put it in the cache.
       if (txAttr == null) {
          // 往缓存中存放空事务注解属性
          this.attributeCache.put(cacheKey, NULL_TRANSACTION_ATTRIBUTE);
       }
       else {
          // 我们执行方法的描述符:包名+类名+方法名
          String methodIdentification = ClassUtils.getQualifiedMethodName(method, targetClass);
          if (txAttr instanceof DefaultTransactionAttribute) {
             // 把方法描述设置到事务属性上去
             DefaultTransactionAttribute dta = (DefaultTransactionAttribute) txAttr;
             dta.setDescriptor(methodIdentification);
             dta.resolveAttributeStrings(this.embeddedValueResolver);
          }
          if (logger.isTraceEnabled()) {
             logger.trace("Adding transactional method '" + methodIdentification + "' with attribute: " + txAttr);
          }
          this.attributeCache.put(cacheKey, txAttr);
       }
       return txAttr;
    }
}

补充说明

AOP代理和启动流程联动入口

AbstractAutoProxyCreator的postProcessAfterInitialization方法

AbstractAutoProxyCreator重写了postProcessAfterInitialization后置处理方法。这里会通过wrapIfNecessary方法进行判断当前类是否需要进行代理增强,需要的话会将增强需要的Advisor和当前bean一起生成代理类实例化并返回给容器。

java 复制代码
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
       Object cacheKey = getCacheKey(bean.getClass(), beanName);
       if (this.earlyProxyReferences.remove(cacheKey) != bean) {
          return wrapIfNecessary(bean, beanName, cacheKey);
       }
    }
    return bean;
}
AbstractAutoProxyCreator的wrapIfNecessary方法
java 复制代码
	protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
		// 如果已经处理过,直接返回
		if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
			return bean;
		}
		// 这里advisedBeans缓存了已经进行了代理的bean,如果缓存中存在,则可以直接返回
		if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
			return bean;
		}
		// 这里isInfrastructureClass()用于判断当前bean是否为Spring系统自带的bean,自带的bean是
		// 不用进行代理的;shouldSkip()则用于判断当前bean是否应该被略过
		if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
			this.advisedBeans.put(cacheKey, Boolean.FALSE);
			return bean;
		}

		// Create proxy if we have advice.
		// 这里获取了所有当前bean能用的符合条件的通知器Advisor
		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
		// 如果获取到的通知器不为空,对当前类进行代理处理。
		if (specificInterceptors != DO_NOT_PROXY) {
			this.advisedBeans.put(cacheKey, Boolean.TRUE);
			// 根据获取到的Advisors为和当前bean一起生成封装并生成代理对象返回
			Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
			// 缓存生成的代理bean的类型,并且返回生成的代理bean
			this.proxyTypes.put(cacheKey, proxy.getClass());
			return proxy;
		}

		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return bean;
	}
```# Spring事务(一) @EnableTransactionManagement注解生效流程

**说明 spring事务数据库默认支持的是mysql数据库,其他数据库接入spring事务体系需要对应驱动提供自己的事务实现**

## 使用

在开发中如果我们要开启spring事务支持,目前spring推荐注解驱动开发,减少xml配置文件。一般我们要在spring-boot的启动类或着在配置类中添加**@EnableTransactionManagement**注解,开启事务体系。同时要注入DataSource(数据库连接对象),TransactionManager(事务管理器对象)来提供支持。

```java
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
	/**
	 * 事务管理器
	 */
    @Bean
    public TransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
	/**
	 * 数据库链接
	 * @return
	 */
	@Bean
	public DataSource dataSource() {
		DruidDataSource data = new DruidDataSource();
		data.setDriverClassName(driverClassname);
		data.setUrl(url);
		data.setUsername(username);
		data.setPassword(password);
		return data;
	}
}

然后再对应需要事务的方法上加上@Transactional注解之后,spirng事务就启动完成。

设计思想

  1. 一个数据库事务,首先我们要建立数据库连接,开启事务,写入SQL语句,提交事务或者回滚。这是一个基础前提。
  2. 如果我们作为spring的事务设计者,如何为spirng这个开源框架设置事务,并达到以下效果,实现通过spring来完成事务管理
    1. 使用spring的人不用把事务控制代码耦合到业务代码中,以注解方式或者配置的方式能快速开启事务,方法内只写业务代码。
    2. 正常流程下没有异常能够正常提交事务。
    3. 如果业务代码出现异常能够回滚事务。
    4. 业务代码如果会出现方法之间的调用,比如A方法调用B方法,A方法上有事务注解,B方法有事务注解。那么根据不同场景的业务需要,有时候A方法单独需要一个事务,B方法单独需要一个事务;有时候A和B方法共用一个事务;如何实现?(这也就是我们常说的事务传播特性)
  3. 数据库产品众多,如何支持MySQL,MongoDB等。

所以,我们在实现过程最重要的是要能够提炼开启事务,关闭事务,回滚事务等功能到框架层面,因为这些代码流程都是一样的,业务开发无需关注这些,提升整体开发项目,这也是开源项目的一个初衷。

实现逻辑

  1. 整体事务体系基础是动态代理增强,事务是建立在这个基础上进行二次开发实现。
  2. 以注解驱动开发,通过@EnableTransactionManagement注解开启spring事务处理。
  3. 通过@EnableTransactionManagement注解导入TransactionManagementConfigurationSelector类,通过TransactionManagementConfigurationSelector导入事务依赖bean(BeanFactoryTransactionAttributeSourceAdvisor,TransactionInterceptor,TransactionAttributeSource,AutoProxyRegistrar)的同时开启容器动态代理支持。
  4. 因为提前开启了动态代理,所以在进行创建对应的bean时会判断当前bean是否存在@Transactional注解,如果存在就会给当前类织入事务增强逻辑创建代理类并返回。
  5. 事务处理体系入口是TransactionInterceptor类,在调用@Transactional修饰的方法会进入TransactionInterceptor,然后由框架进行事务管理。

源码分析

前置交代

spring中对于@Configuration注解修饰的配置类处理流程

  1. 在容器刷新refresh()方法流程中有一步invokeBeanFactoryPostProcessors(beanFactory)调用所有BeanFactoryPostProcessor的实现类;这里会进入ConfigurationClassPostProcessor进行@Configuration注解解析,然后由ConfigurationClassParser的parse方法预处理,然后最终由ConfigurationClassParser的doProcessConfigurationClass方法完成最终的解析。下面的processImports方法就是doProcessConfigurationClass中的其中一个步骤。
  2. 这个方法的主要操作
    1. 遍历所有通过@Import注解导入的类,分别进行不同处理。
    2. 如果导入类是ImportSelector子类并实现了selectImports方法就调用selectImports方法,得到想要导入的importClassNames列表,然后将importClassNames列表再次调用processImports方法进行处理。实现递归处理。
    3. 如果导入类是ImportBeanDefinitionRegistrar子类,在后面进行统一处理。
java 复制代码
//在ConfigurationClassParser类中
private void  processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
			Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
			boolean checkForCircularImports) {

		if (importCandidates.isEmpty()) {
			return;
		}

		// 通过ConfigurationClass对象的importStack属性判断是否存在循环依赖,如果存在则抛出异常
		if (checkForCircularImports && isChainedImportOnStack(configClass)) {
			this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
		}
		else {
			// 将当前的ConfigurationClass对象压入importStack属性中
			this.importStack.push(configClass);
			try {
				// 遍历通过@Import注解导入的所有类
				for (SourceClass candidate : importCandidates) {
					// 判断导入的类是否实现了ImportSelector接口,如果实现了,那么就会调用selectImports方法,返回需要导入的配置类
          // TransactionManagementConfigurationSelector是ImportSelector接口的实现子类
					if (candidate.isAssignable(ImportSelector.class)) {
						// 获取全类名
						Class<?> candidateClass = candidate.loadClass();
						// 通过反射创建ImportSelector对象
						ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
								this.environment, this.resourceLoader, this.registry);
						// 获取selector的过滤器
						Predicate<String> selectorFilter = selector.getExclusionFilter();
						if (selectorFilter != null) {
							exclusionFilter = exclusionFilter.or(selectorFilter);
						}
						// 处理DeferredImportSelector类型,TransactionManagementConfigurationSelector不是DeferredImportSelector实现子类
						if (selector instanceof DeferredImportSelector) {
							this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
						}
						else {
							// 获取引入的类,然后递归解析通过@Import导入的类,并调用导入类的selectImports方法,获取调用后得到的获取目标导入类的beanName数组
              // 这里会调用到TransactionManagementConfigurationSelector的selectImports方法获取对应的importClassNames
							String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
              // 将获取到的importClassNames转为SourceClass类型
							Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
							// 递归调用processImports方法处理前面转换的importSourceClasses数组
							processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
						}
					}
					// 判断导入的类是否实现了ImportBeanDefinitionRegistrar接口,AutoProxyRegistrar类实现了ImportBeanDefinitionRegistrar接口,这里就直接处理了AutoProxyRegistrar
					else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
						Class<?> candidateClass = candidate.loadClass();
						ImportBeanDefinitionRegistrar registrar =
								ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
										this.environment, this.resourceLoader, this.registry);
						//将AutoProxyRegistrar放入importBeanDefinitionRegistrars集合中,在后面进行统一处理
						configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
					}
					else {
						this.importStack.registerImport(
								currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
						// 递归调用processConfigurationClass方法处理导入的类,放入configurationClasses中
            // ProxyTransactionManagementConfiguration会进入下面方法,放入configurationClasses集合中,后面进行统一处理
						processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
					}
				}
			}
			catch (BeanDefinitionStoreException ex) {
				throw ex;
			}
			catch (Throwable ex) {
				throw new BeanDefinitionStoreException(
						"Failed to process import candidates for configuration class [" +
						configClass.getMetadata().getClassName() + "]", ex);
			}
			finally {
				this.importStack.pop();
			}
		}
	}

上面有个关键步骤是

java 复制代码
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());

这一步很关键,容器启动走到这里就会调用TransactionManagementConfigurationSelector的selectImports方法,这样@EnableTransactionManagement注解就和容器产生联动。spring容器就开始接管事务处理。

@Transactional注解生效流程

@EnableTransactionManagement注解

java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
    boolean proxyTargetClass() default false;
    AdviceMode mode() default AdviceMode.PROXY;
    int order() default Ordered.LOWEST_PRECEDENCE;
}

这个注解的功能就是通过@Import注解导入TransactionManagementConfigurationSelector类,后续处理都是建立在TransactionManagementConfigurationSelector这个类基础上进行的。

TransactionManagementConfigurationSelector类

java 复制代码
public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {
    @Override
    protected String[] selectImports(AdviceMode adviceMode) {
       switch (adviceMode) {
          case PROXY:
             return new String[] {AutoProxyRegistrar.class.getName(),
                   ProxyTransactionManagementConfiguration.class.getName()};
          case ASPECTJ:
             return new String[] {determineTransactionAspectClass()};
          default:
             return null;
       }
    }

    private String determineTransactionAspectClass() {
       return (ClassUtils.isPresent("javax.transaction.Transactional", getClass().getClassLoader()) ?
             TransactionManagementConfigUtils.JTA_TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME :
             TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME);
    }

}

这个类的主要作用就是通过selectImports方法让容器启动过程中进行调用,激活容器的事务处理功能。

AutoProxyRegistrar类

  1. 实际上只干了一件事,判断导入当前类的注解所在的类上的所有注解是否支持开启动态代理,如果支持开启则注入动态代理的内部类internalAutoProxyCreator,用于后面代理处理流程;如果不支持开启动态代理进行日志打印。
  2. spring事务体系中是基于动态代理实现的,如果不开启动态代理,实际上是没办法实现事务体系的。但是spring又想在使用过程中只需要一个注解就能开启事务,所以通过AutoProxyRegistrar开启容器动态代理功能。
  3. AutoProxyRegistrar实现了ImportBeanDefinitionRegistrar接口,在ConfigurationClassParse的processImports方法专门处理了实现ImportBeanDefinitionRegistrar接口的类。会把这个类统一放到一个map集合中,后续统一进行调用registerBeanDefinitions方法。
java 复制代码
public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
	private final Log logger = LogFactory.getLog(getClass());
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		boolean candidateFound = false;
		// 获取导入当前配置类上的所有注解
		Set<String> annTypes = importingClassMetadata.getAnnotationTypes();
		// 遍历注解
		for (String annType : annTypes) {
			// 获取注解上的所有属性
			AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);
			if (candidate == null) {
				continue;
			}
			// 获取注解上的mode和proxyTargetClass属性
			Object mode = candidate.get("mode");
			Object proxyTargetClass = candidate.get("proxyTargetClass");
			// 如果mode和proxyTargetClass都不为空,且mode是AdviceMode类型,proxyTargetClass是Boolean类型,说明当前要开启AOP
			if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&
					Boolean.class == proxyTargetClass.getClass()) {
				candidateFound = true;
				// 如果mode是PROXY,且proxyTargetClass是true,说明要使用CGLIB代理
				if (mode == AdviceMode.PROXY) {
					// 注册aop的自动代理创建器,后续开cdlib代理用得到
					AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
					if ((Boolean) proxyTargetClass) {
						AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
						return;
					}
				}
			}
		}
		// 没有找到合适的注解,打印日志
		if (!candidateFound && logger.isInfoEnabled()) {
			String name = getClass().getSimpleName();
			logger.info(String.format("%s was imported but no annotations were found " +
					"having both 'mode' and 'proxyTargetClass' attributes of type " +
					"AdviceMode and boolean respectively. This means that auto proxy " +
					"creator registration and configuration may not have occurred as " +
					"intended, and components may not be proxied as expected. Check to " +
					"ensure that %s has been @Import'ed on the same class where these " +
					"annotations are declared; otherwise remove the import of %s " +
					"altogether.", name, name, name));
		}
	}
}

ProxyTransactionManagementConfiguration类

java 复制代码
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
    @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    // 事务切面增强器,持有transactionAttributeSource和transactionInterceptor信息,
    public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
          TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) {
				// 这个advisor很重要,在aop处理过程中会用到,通过这个advisor判断当前bean是否需要进行事务代理增强并完成对应的代理类的生成。
       BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
       // 设置TransactionAttributeSource,用于后面进行@Transactional注解的解析
       advisor.setTransactionAttributeSource(transactionAttributeSource);
       // 设置TransactionInterceptor,用于后面进行事务的拦截增强
       advisor.setAdvice(transactionInterceptor);
       if (this.enableTx != null) {
          advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
       }
       return advisor;
    }
    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
		// 主要完成@Transactional注解的解析,用在TransactionAttributeSourcePointcut的matches方法中
    public TransactionAttributeSource transactionAttributeSource() {
       return new AnnotationTransactionAttributeSource();
    }

    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	  // 事务切面增强器,代理了@Transactional注解的方法,进行事务的拦截增强
    public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource) {
       TransactionInterceptor interceptor = new TransactionInterceptor();
       interceptor.setTransactionAttributeSource(transactionAttributeSource);
       if (this.txManager != null) {
          interceptor.setTransactionManager(this.txManager);
       }
       return interceptor;
    }

}

先说说这个类名,代理事务管理配置。通过类名就能发现这个和代理,事务都有关系。也是事务体系中很重要的一个类。这个类本身被@Configuration注解修饰,里面又存在被@Bean修饰的方法。在容器启动过程中进行配置解析的时候会调用被@Bean注解修饰的方法注入对应的bean实例。

BeanFactoryTransactionAttributeSourceAdvisor类
java 复制代码
// 事务增强advisor
public class BeanFactoryTransactionAttributeSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {
	@Nullable
	private TransactionAttributeSource transactionAttributeSource;
  
  // 这个pointcut很重要,后面在进行切面处理的时候要通过这个pointcut进行@Transational注解解析
	private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() {
		@Override
		@Nullable
		protected TransactionAttributeSource getTransactionAttributeSource() {
			return transactionAttributeSource;
		}
	};
	public void setTransactionAttributeSource(TransactionAttributeSource transactionAttributeSource) {
		this.transactionAttributeSource = transactionAttributeSource;
	}
	public void setClassFilter(ClassFilter classFilter) {
		this.pointcut.setClassFilter(classFilter);
	}

	@Override
	public Pointcut getPointcut() {
		return this.pointcut;
	}

}
TransactionAttributeSourcePointcut类
java 复制代码
abstract class TransactionAttributeSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {

    protected TransactionAttributeSourcePointcut() {
       setClassFilter(new TransactionAttributeSourceClassFilter());
    }
  	// 关键方法,非常关键
    @Override
    public boolean matches(Method method, Class<?> targetClass) {
       TransactionAttributeSource tas = getTransactionAttributeSource();
       // 进行事务注解解析
       // 若事务属性原为null或者解析出来的事务注解属性不为空,表示方法匹配
       // 实际进入AbstractFallbackTransactionAttributeSource的getTransactionAttribute方法
       return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
    }

    @Override
    public boolean equals(@Nullable Object other) {
       if (this == other) {
          return true;
       }
       if (!(other instanceof TransactionAttributeSourcePointcut)) {
          return false;
       }
       TransactionAttributeSourcePointcut otherPc = (TransactionAttributeSourcePointcut) other;
       return ObjectUtils.nullSafeEquals(getTransactionAttributeSource(), otherPc.getTransactionAttributeSource());
    }

    @Override
    public int hashCode() {
       return TransactionAttributeSourcePointcut.class.hashCode();
    }

    @Override
    public String toString() {
       return getClass().getName() + ": " + getTransactionAttributeSource();
    }
    @Nullable
    protected abstract TransactionAttributeSource getTransactionAttributeSource();
    private class TransactionAttributeSourceClassFilter implements ClassFilter {

       @Override
       public boolean matches(Class<?> clazz) {
          if (TransactionalProxy.class.isAssignableFrom(clazz) ||
                TransactionManager.class.isAssignableFrom(clazz) ||
                PersistenceExceptionTranslator.class.isAssignableFrom(clazz)) {
             return false;
          }
          TransactionAttributeSource tas = getTransactionAttributeSource();
          return (tas == null || tas.isCandidateClass(clazz));
       }
    }

}
  1. 在上面通过AutoProxyRegistrar开启容器代理功能之后,容器在进行bean创建过程中,在调用容器内所有BeanPostProcessor的postProcessAfterInitialization的方法时会进入aop处理流程。
  2. 在aop处理流程中,会首先找出所有的容器内的Advisor子类,这里会找到BeanFactoryTransactionAttributeSourceAdvisor。然后遍历所有的Advisor,在遍历过程中找出能够被当前bean使用的Advisor。而判断是否能够被当前bean使用就是通过TransactionAttributeSourcePointcut的matches方法来实现的。
  3. 在TransactionAttributeSourcePointcut的matches方法,获取了TransactionAttributeSource对象实例,然后通过tas.getTransactionAttribute(method, targetClass)方法来解析当前方法上的@Transactional注解并获取注解信息,返回组装好的TransactionAttribute对象
    1. 实际上这里调用的是AbstractFallbackTransactionAttributeSource的getTransactionAttribute方法完成解析,虽然在前面ProxyTransactionManagementConfiguration方法中我们向容器中注入的是AnnotationTransactionAttributeSource对象,但是AnnotationTransactionAttributeSource本身继承了AbstractFallbackTransactionAttributeSource。
  4. 如果返回的TransactionAttributeSource不为空,说明当前方法存在@Transactional注解,也就意味着当前点类需要进行代理增强。然后会为当前类生成代理类,后面调用方法的时候就进入代理类进行事务处理。
AbstractFallbackTransactionAttributeSource的getTransactionAttribute方法
java 复制代码
// 解析当前方法上的
@Override
@Nullable
public TransactionAttribute getTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
    if (method.getDeclaringClass() == Object.class) {
       return null;
    }

    // First, see if we have a cached value.
    // 缓存键 方法名和类名,记录当前方法上的事务注解传播级别
    Object cacheKey = getCacheKey(method, targetClass);
    TransactionAttribute cached = this.attributeCache.get(cacheKey);
    if (cached != null) {
       // Value will either be canonical value indicating there is no transaction attribute,
       // or an actual transaction attribute.
       if (cached == NULL_TRANSACTION_ATTRIBUTE) {
          return null;
       }
       else {
          return cached;
       }
    }
    else {
       // We need to work it out.
       // 获取@Transactional事务注解属性
       TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass);
       // Put it in the cache.
       if (txAttr == null) {
          // 往缓存中存放空事务注解属性
          this.attributeCache.put(cacheKey, NULL_TRANSACTION_ATTRIBUTE);
       }
       else {
          // 我们执行方法的描述符:包名+类名+方法名
          String methodIdentification = ClassUtils.getQualifiedMethodName(method, targetClass);
          if (txAttr instanceof DefaultTransactionAttribute) {
             // 把方法描述设置到事务属性上去
             DefaultTransactionAttribute dta = (DefaultTransactionAttribute) txAttr;
             dta.setDescriptor(methodIdentification);
             dta.resolveAttributeStrings(this.embeddedValueResolver);
          }
          if (logger.isTraceEnabled()) {
             logger.trace("Adding transactional method '" + methodIdentification + "' with attribute: " + txAttr);
          }
          this.attributeCache.put(cacheKey, txAttr);
       }
       return txAttr;
    }
}

补充说明

AOP代理和启动流程联动入口

AbstractAutoProxyCreator的postProcessAfterInitialization方法

AbstractAutoProxyCreator重写了postProcessAfterInitialization后置处理方法。这里会通过wrapIfNecessary方法进行判断当前类是否需要进行代理增强,需要的话会将增强需要的Advisor和当前bean一起生成代理类实例化并返回给容器。

java 复制代码
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
       Object cacheKey = getCacheKey(bean.getClass(), beanName);
       if (this.earlyProxyReferences.remove(cacheKey) != bean) {
          return wrapIfNecessary(bean, beanName, cacheKey);
       }
    }
    return bean;
}
AbstractAutoProxyCreator的wrapIfNecessary方法
java 复制代码
	protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
		// 如果已经处理过,直接返回
		if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
			return bean;
		}
		// 这里advisedBeans缓存了已经进行了代理的bean,如果缓存中存在,则可以直接返回
		if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
			return bean;
		}
		// 这里isInfrastructureClass()用于判断当前bean是否为Spring系统自带的bean,自带的bean是
		// 不用进行代理的;shouldSkip()则用于判断当前bean是否应该被略过
		if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
			this.advisedBeans.put(cacheKey, Boolean.FALSE);
			return bean;
		}

		// Create proxy if we have advice.
		// 这里获取了所有当前bean能用的符合条件的通知器Advisor
		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
		// 如果获取到的通知器不为空,对当前类进行代理处理。
		if (specificInterceptors != DO_NOT_PROXY) {
			this.advisedBeans.put(cacheKey, Boolean.TRUE);
			// 根据获取到的Advisors为和当前bean一起生成封装并生成代理对象返回
			Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
			// 缓存生成的代理bean的类型,并且返回生成的代理bean
			this.proxyTypes.put(cacheKey, proxy.getClass());
			return proxy;
		}

		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return bean;
	}

源码地址

github.com/JimmyButler...

相关推荐
2401_8576100314 分钟前
Spring Boot框架:电商系统的技术优势
java·spring boot·后端
杨哥带你写代码2 小时前
网上商城系统:Spring Boot框架的实现
java·spring boot·后端
camellias_2 小时前
SpringBoot(二十一)SpringBoot自定义CURL请求类
java·spring boot·后端
背水3 小时前
初识Spring
java·后端·spring
晴天飛 雪3 小时前
Spring Boot MySQL 分库分表
spring boot·后端·mysql
weixin_537590453 小时前
《Spring boot从入门到实战》第七章习题答案
数据库·spring boot·后端
AskHarries3 小时前
Spring Cloud Gateway快速入门Demo
java·后端·spring cloud
Qi妙代码4 小时前
MyBatisPlus(Spring Boot版)的基本使用
java·spring boot·后端
宇宙超级勇猛无敌暴龙战神4 小时前
Springboot整合xxl-job
java·spring boot·后端·xxl-job·定时任务