目录

一、概述

二、@Transactional事务属性的解析


一、概述

前面一篇文章,我们介绍了@EnableTransactionManagement注解在Spring内部的实现,它通过TransactionManagementConfigurationSelector组件引入AutoProxyRegistrar、ProxyTransactionManagementConfiguration,帮我们注册了几个重要的bean:

  • InfrastructureAdvisorAutoProxyCreator自动代理创建器
  • BeanFactoryTransactionAttributeSourceAdvisor增强器
  • TransactionAttributeSource事务属性源
  • TransactionInterceptor事务方法拦截器

本篇文章将介绍Spring是如何扫描到@Transactional注解以及事务注解的属性是如何解析的。

二、@Transactional事务属性的解析

InfrastructureAdvisorAutoProxyCreator间接实现了BeanPostProcessor接口。

该接口有2个方法postProcessBeforeInitialization()和postProcessAfterInitialization(),在bean初始化之后会执行postProcessAfterInitialization(该方法创建Aop和事务的代理对象)方法。

InfrastructureAdvisorAutoProxyCreator的父类AbstractAutoProxyCreator实现了postProcessAfterInitialization()方法:

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {// 如果bean非空的话,判断是否需要进行代理,需要代理的话则会进行包装if (bean != null) {// 获取缓存key:// a.如果beanName非空的话,则还会判断是否是FactoryBean,是FactoryBean的话使用"&+beanName"作为缓存key,否则直接使用beanName;// b.如果beanName为空,直接使用beanClass作为缓存key;Object cacheKey = getCacheKey(bean.getClass(), beanName);if (this.earlyProxyReferences.remove(cacheKey) != bean) {// 如果有必要的话,则执行具体的包装return wrapIfNecessary(bean, beanName, cacheKey);}}// 如果bean为空,则直接返回return bean;
}

然后接下来是调用wrapIfNecessary方法。AOP生成代理的核心逻辑在wrapIfNecessary()方法,其源码如下:

// org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.wrapIfNecessary
// 如果bean符合被代理的条件,则进行代理
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {// 1.判断targetSourcedBeans缓存中是否存在当前beanName(即已处理过),如果已经存在,则直接返回当前bean// private final Set<String> targetSourcedBeans = Collections.newSetFromMap(new ConcurrentHashMap<>(16));if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {return bean;}// 2.如果在advisedBeans缓存中存在,并且value为false,则直接返回当前bean//     private final Map<Object, Boolean> advisedBeans = new ConcurrentHashMap<>(256);if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {return bean;}// 3.如果当前bean类是否是基础设施类(Advice、Pointcut、Advisor、AopInfrastructureBean)或者需跳过当前bean的代理的话,满足其中一个条件则直接返回当前beanif (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {// 将当前bean加入advisedBeans缓存中,并且值为falsethis.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}// Create proxy if we have advice.// 如果包含通知,创建当前bean对应的代理对象// 4.获取当前bean对应的Advices和Advisors(增强方法)Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);//  如果specificInterceptors不为null的话(存在增强方法),则需要针对增强创建代理if (specificInterceptors != DO_NOT_PROXY) {// 标记当前bean已经处理过,即保存当前bean在advisedBeans缓存中,并且值为truethis.advisedBeans.put(cacheKey, Boolean.TRUE);// 5.创建代理对象// new SingletonTargetSource(bean): 目标对象,方便获取被代理的原始对象Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));// 6.创建完代理后,将cacheKey -> 代理类的class放到proxyTypes缓存中// private final Map<Object, Class<?>> proxyTypes = new ConcurrentHashMap<>(16);this.proxyTypes.put(cacheKey, proxy.getClass());// 给容器中返回当前bean增强之后的代理对象, 以后容器中获取到的就是这个bean的代理对象,执行目标方法的时候,代理对象就会执行通知方法的流程return proxy;}// 7.将当前bean加入advisedBeans缓存中,并且值为falsethis.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;
}

wrapIfNecessary()方法的执行流程:

  1. 判断targetSourcedBeans缓存中是否存在当前beanName(即已处理过),如果已经存在,则直接返回当前bean;
  2. 如果在advisedBeans缓存中存在,并且value为false,则直接返回当前bean;
  3. 如果当前bean类是否是基础设施类(Advice、Pointcut、Advisor、AopInfrastructureBean)或者需跳过当前bean的代理的话,满足其中一个条件则直接返回当前bean;
  4. 获取当前bean对应的Advices和Advisors(增强方法);
  5. 创建代理对象;
  6. 创建完代理后,将cacheKey -> 代理类的class放到proxyTypes缓存中;
  7. 将当前bean加入advisedBeans缓存中,并且值为false;

核心逻辑主要有三个:

(一)、isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)

判断当前bean类是否是基础设施类(Advice、Pointcut、Advisor、AopInfrastructureBean)或者需跳过当前bean的代理的话,满足其中一个条件则直接返回当前bean。

这一部分的代码已经在Spring AOP原理分析文章详细分析过,读者朋友可以参考前面的文章阅读。

(二)、getAdvicesAndAdvisorsForBean()

获取当前bean对应的Advices和Advisors(增强方法)。我们跟踪getAdvicesAndAdvisorsForBean()方法的源码:

// org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.getAdvicesAndAdvisorsForBean
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {// 获取符合条件的Advisor增强器List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);// 如果不存在对应的增强器,则返回nullif (advisors.isEmpty()) {return DO_NOT_PROXY;}// 如果增强器非空,返回Advisor数组return advisors.toArray();
}

可以看到,getAdvicesAndAdvisorsForBean()内部通过调用findEligibleAdvisors()方法获取符合条件的Advisor增强器,如果不存在对应的增强器,则直接返回null,否则返回获取到的Advisor数组。

// 寻找合适的增强器对象
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {// 1.获取所有候选的Advisor增强器(通知方法)List<Advisor> candidateAdvisors = findCandidateAdvisors();// 2.从候选的Advisor增强器集合中筛选出真正能应用到当前bean的增强器(找哪些通知方法是需要切入当前bean方法的)List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);// 3.Spring提供的一个扩展点,留给子类添加额外的Advisor增强器,默认是空实现extendAdvisors(eligibleAdvisors);// 4.对真正能应用的增强器进行排序,Spring允许子类重写sortAdvisors()方法来自定义排序的策略if (!eligibleAdvisors.isEmpty()) {eligibleAdvisors = sortAdvisors(eligibleAdvisors);}// 5.返回真正能应用的Advisor增强器集合return eligibleAdvisors;
}

findEligibleAdvisors()方法的执行流程:

  1. 获取所有候选的Advisor增强器(通知方法);
  2. 从候选的Advisor增强器集合中筛选出真正能应用到当前bean的增强器(找哪些通知方法是需要切入当前bean方法的);
  3. Spring提供的一个扩展点,留给子类添加额外的Advisor增强器,默认是空实现;
  4. 对真正能应用的增强器进行排序,Spring允许子类重写sortAdvisors()方法来自定义排序的策略;
  5. 返回真正能应用的Advisor增强器集合;

首先来看下是如何获取所有候选的Advisor增强器(通知方法)的,具体是在findCandidateAdvisors()方法,同样的,在前面的文章中已经详细分析,这里不再重复分析,读者朋友可以查看Spring AOP原理分析部分的文章。

获取到所有候选的Advisor增强器(通知方法)之后,就需要筛选出真正能应用到当前bean的增强器,具体逻辑是在findAdvisorsThatCanApply()方法:

// org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.findAdvisorsThatCanApply
protected List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {// 记录当前正在被代理的bean,保存在ThreadLocal中ProxyCreationContext.setCurrentProxiedBeanName(beanName);try {// 从候选的通知器中找到合适的增强器(能应用到当前bean的)return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);} finally {// 清空ThreadLocalProxyCreationContext.setCurrentProxiedBeanName(null);}
}

核心逻辑在findAdvisorsThatCanApply()方法:

// org.springframework.aop.support.AopUtils.findAdvisorsThatCanApply
public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {// 如果候选的增强器为空,则直接返回空if (candidateAdvisors.isEmpty()) {return candidateAdvisors;}// 用于存放符合条件的增强器集合对象List<Advisor> eligibleAdvisors = new ArrayList<>();// 循环所有候选的增强器对象,判断增强器对象是否实现了IntroductionAdvisor接口 && 是否能应用for (Advisor candidate : candidateAdvisors) {if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {eligibleAdvisors.add(candidate);}}boolean hasIntroductions = !eligibleAdvisors.isEmpty();for (Advisor candidate : candidateAdvisors) {// 前面已经处理过,需要跳过if (candidate instanceof IntroductionAdvisor) {// already processedcontinue;}// canApply():真正判断当前增强器是否能应用到当前bean上if (canApply(candidate, clazz, hasIntroductions)) {eligibleAdvisors.add(candidate);}}return eligibleAdvisors;
}

findAdvisorsThatCanApply()方法主要的流程就是循环遍历所有候选的增强器candidateAdvisors,然后挨个调用canApply()方法判断当前增强器是否能应用到当前bean上。

// org.springframework.aop.support.AopUtils.canApply(org.springframework.aop.Advisor, java.lang.Class<?>, boolean)
public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {if (advisor instanceof IntroductionAdvisor) {// IntroductionAdvisor类型的增强器的处理return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);}else if (advisor instanceof PointcutAdvisor) {// PointcutAdvisor类型的增强器的处理PointcutAdvisor pca = (PointcutAdvisor) advisor;return canApply(pca.getPointcut(), targetClass, hasIntroductions);}else {// It doesn't have a pointcut so we assume it applies.return true;}
}

canApply()方法的逻辑比较清晰,分别对IntroductionAdvisor类型、PointcutAdvisor类型的增强器的进行处理,这里我们以PointcutAdvisor类型的增强器处理为例,看下如何匹配的:

// org.springframework.aop.support.AopUtils.canApply(org.springframework.aop.Pointcut, java.lang.Class<?>, boolean)
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {Assert.notNull(pc, "Pointcut must not be null");if (!pc.getClassFilter().matches(targetClass)) {return false;}// 通过切点获取到MethodMatcher方法匹配器对象MethodMatcher methodMatcher = pc.getMethodMatcher();if (methodMatcher == MethodMatcher.TRUE) {// No need to iterate the methods if we're matching any method anyway...return true;}IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;if (methodMatcher instanceof IntroductionAwareMethodMatcher) {introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;}// 存放目标对象class的集合Set<Class<?>> classes = new LinkedHashSet<>();// 判断目标对象class是不是代理的class对象if (!Proxy.isProxyClass(targetClass)) {classes.add(ClassUtils.getUserClass(targetClass));}// 获取到目标对象实现的接口的class对象,然后加入到classes集合中classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));// 遍历所有的class对象for (Class<?> clazz : classes) {// 通过反射获取到所有的方法Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);// 循环所有的方法for (Method method : methods) {// 通过methodMatcher.matches来匹配,如果返回true就表示匹配,就添加到集合eligibleAdvisors中if (introductionAwareMethodMatcher != null ?introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :methodMatcher.matches(method, targetClass)) {return true;}}}return false;
}

调用methodMatcher.matches()方法会执行到TransactionAttributeSourcePointcut#matches()方法。

在匹配 match 操作中,区别的是 AOP 识别的是 @Before 、@After,而我们的事务TX识别的是 @Transactional 标签。

// org.springframework.transaction.interceptor.TransactionAttributeSourcePointcut#matches
public boolean matches(Method method, Class<?> targetClass) {if (TransactionalProxy.class.isAssignableFrom(targetClass) ||PlatformTransactionManager.class.isAssignableFrom(targetClass) ||PersistenceExceptionTranslator.class.isAssignableFrom(targetClass)) {return false;}// 获取@EnableTransactionManagement注解导入的ProxyTransactionManagementConfiguration配置类中的TransactionAttributeSource对象TransactionAttributeSource tas = getTransactionAttributeSource();// 如果事务属性源为null 或者 解析出来的事务注解属性不为空,则表示方法匹配// getTransactionAttribute():解析@Transactional注解以及配置的属性,如隔离级别、超时时间、传播行为等return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
}

TransactionAttributeSourcePointcut#matches()方法的执行流程:

  1. 通过getTransactionAttributeSource()方法获取@EnableTransactionManagement注解导入的ProxyTransactionManagementConfiguration配置类中的TransactionAttributeSource对象;
  2. 通过getTransactionAttribute()方法解析@Transactional注解以及配置的属性,如隔离级别、超时时间、传播行为等;

下面我们主要来看看getTransactionAttribute()方法的实现。

// org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource#getTransactionAttribute
public TransactionAttribute getTransactionAttribute(Method method, @Nullable Class<?> targetClass) {// 如果method所在的class是Object类型的话,直接返回null  method: public void com.wsh.transaction.UserService.insert()if (method.getDeclaringClass() == Object.class) {return null;}// First, see if we have a cached value.// 构建缓存KeyObject cacheKey = getCacheKey(method, targetClass);// 尝试先从缓存中获取事务属性TransactionAttributeTransactionAttribute 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.// 解析获取事务的属性TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass);// Put it in the cache.if (txAttr == null) {// 如果解析出来的事务注解属性为空,则缓存空事务注解属性this.attributeCache.put(cacheKey, NULL_TRANSACTION_ATTRIBUTE);} else {// 返回给定方法的限定名,组成的完全限定的接口/类名+“.”+方法名称, 如com.wsh.transaction.UserService.insertString methodIdentification = ClassUtils.getQualifiedMethodName(method, targetClass);if (txAttr instanceof DefaultTransactionAttribute) {// 将方法全限定名设置到事务属性中((DefaultTransactionAttribute) txAttr).setDescriptor(methodIdentification);}if (logger.isTraceEnabled()) {logger.trace("Adding transactional method '" + methodIdentification + "' with attribute: " + txAttr);}// 将事务注解属性存入缓存中this.attributeCache.put(cacheKey, txAttr);}return txAttr;}
}

解析事务属性具体是在computeTransactionAttribute(method, targetClass)中完成:

// org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource#computeTransactionAttribute
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {// Don't allow no-public methods as required.// 如果事务方法上的修饰符不是public的话,则直接返回null// 也就是说,Spring的事务只支持public公开访问修饰符的方法if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {return null;}// The method may be on an interface, but we need attributes from the target class.// If the target class is null, the method will be unchanged.// method代表接口中的方法// specificMethod代表实现类中的方法Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);// First try is the method in the target class.// 去目标对象的方法上找事务注解TransactionAttribute txAttr = findTransactionAttribute(specificMethod);if (txAttr != null) {return txAttr;}// Second try is the transaction attribute on the target class.// 去方法所在类上面找事务注解txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {return txAttr;}// 如果存在接口,则去接口中查找if (specificMethod != method) {// Fallback is to look at the original method.// 去接口的方法找事务注解txAttr = findTransactionAttribute(method);if (txAttr != null) {return txAttr;}// Last fallback is the class of the original method.// 去接口中的类上面找事务注解txAttr = findTransactionAttribute(method.getDeclaringClass());if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {return txAttr;}}return null;
}

查找@Transactional注解具体是在 findTransactionAttribute(specificMethod)方法中实现:

// org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#findTransactionAttribute
protected TransactionAttribute findTransactionAttribute(Method method) {return determineTransactionAttribute(method);
}// org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#determineTransactionAttribute
protected TransactionAttribute determineTransactionAttribute(AnnotatedElement element) {// annotationParsers在构造方法初始化for (TransactionAnnotationParser annotationParser : this.annotationParsers) {// 通过注解解析器,解析方法或者类上面的注解TransactionAttribute attr = annotationParser.parseTransactionAnnotation(element);if (attr != null) {return attr;}}return null;
}// org.springframework.transaction.annotation.SpringTransactionAnnotationParser#parseTransactionAnnotation
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {// 从element对象中获取@Transactional注解,然后把注解属性封装到了AnnotationAttributesAnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(element, Transactional.class, false, false);if (attributes != null) {// 解析事务注解中的属性return parseTransactionAnnotation(attributes);}else {return null;}
}

通过注解解析器解析@Transactional注解上配置的属性:

// org.springframework.transaction.annotation.SpringTransactionAnnotationParser#parseTransactionAnnotation
protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {// 基础规则的事务属性对象RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();// 事务传播行为Propagation propagation = attributes.getEnum("propagation");rbta.setPropagationBehavior(propagation.value());// 事务隔离级别Isolation isolation = attributes.getEnum("isolation");rbta.setIsolationLevel(isolation.value());// 超时时间rbta.setTimeout(attributes.getNumber("timeout").intValue());// 是否只读事务rbta.setReadOnly(attributes.getBoolean("readOnly"));// 指定事务管理器的名称rbta.setQualifier(attributes.getString("value"));// 指定事务针对哪种异常进行回滚操作List<RollbackRuleAttribute> rollbackRules = new ArrayList<>();for (Class<?> rbRule : attributes.getClassArray("rollbackFor")) {rollbackRules.add(new RollbackRuleAttribute(rbRule));}// 指定事务针对哪种异常进行回滚操作for (String rbRule : attributes.getStringArray("rollbackForClassName")) {rollbackRules.add(new RollbackRuleAttribute(rbRule));}// 指定事务针对哪种异常不进行回滚操作for (Class<?> rbRule : attributes.getClassArray("noRollbackFor")) {rollbackRules.add(new NoRollbackRuleAttribute(rbRule));}// 指定事务针对哪种异常不进行回滚操作for (String rbRule : attributes.getStringArray("noRollbackForClassName")) {rollbackRules.add(new NoRollbackRuleAttribute(rbRule));}rbta.setRollbackRules(rollbackRules);return rbta;
}

到这里,@Transactional事务注解的属性已经解析好了,也就是说到这里已经从候选的Advisor增强器集合中筛选出了真正能应用到当前bean的增强器,并且Spring还提供了一个扩展点允许我们添加额外的增强器,我们可以通过重写extendAdvisors()方法实现。最后对真正能应用的增强器进行排序后,返回所有真正能应用到当前bean的增强器。获取到当前bean的增强方法后,便调用createProxy方法,创建代理。

(三)、createProxy()

createProxy()方法是真正创建代理对象的地方。

这一部分的代码已经在Spring AOP原理分析文章详细分析过,读者朋友可以参考前面的文章阅读。

Spring事务原理分析(二)--@Transactional事务属性的解析相关推荐

  1. Spring事务原理分析(一)--@EnableTransactionManagement 到底做了什么?

    目录 一.概述 二.事务的ACID属性 三.事务的隔离级别 四.事务的传播行为 五.Spring声明式事务环境搭建 六.@EnableTransactionManagement分析 七.AutoPro ...

  2. Spring事务原理分析-部分一

    Spring事务原理分析-部分一 什么事务 事务:逻辑上的一组操作,组成这组操作的各个单元,要么全都成功,要么全都失败. 事务基本特性 ⑴ 原子性(Atomicity) 原子性是指事务包含的所有操作要 ...

  3. MySQL事务原理分析(ACID特性、隔离级别、锁、MVCC、并发读异常、并发死锁以及如何避免死锁)

    MySQL事务原理分析(ACID特性.隔离级别.锁.MVCC.并发读异常.并发死锁以及如何避免死锁) 一.事务 目的 组成 特征 事务空间语句 二.ACID特性 原子性(A) 隔离性(I) 持久性(d ...

  4. Linux服务器开发【有用知识】—MySQL事务原理分析

    前言 今天的目标是学习MySQL事务原理分析,但是却似乎总是非常不顺利,概念和实操实在多到令人发指,故干脆轻松学完一节课,等到时机到了再重新刷一遍吧! 一.事务是什么? 将数据库从一致性状态转化成另一 ...

  5. 浅谈:Spring Boot原理分析,切换内置web服务器,SpringBoot监听项目(使用springboot-admin),将springboot的项目打成war包

    浅谈:Spring Boot原理分析(更多细节解释在代码注释中) 通过@EnableAutoConfiguration注解加载Springboot内置的自动初始化类(加载什么类是配置在spring.f ...

  6. spring ioc原理分析

    spring ioc原理分析 spring ioc 的概念 简单工厂方法 spirng ioc实现原理 spring ioc的概念 ioc: 控制反转 将对象的创建由spring管理.比如,我们以前用 ...

  7. 【笔记】Spring 事务原理分析和源码剖析

    文章目录 概述 源码解析 xml 配置解析 事务代理类的创建 事务拦截器的实现 切面实现 事务处理实现 总结: 资料 概述 事务处理是一个重要并且涉及范围很广的领域,涉及到并发和数据一致性方面的问题. ...

  8. Spring的事务原理分析、与mysql的事务关系

    spring事务?mysql事务? 系统中到底谁的事务是在保证数据的一致性,两个事务有什么关系? spring事务的基本原理 Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,s ...

  9. 数据库之mysql事务原理分析与锁机制 详解

    1.事务 1.2.目的 事务将数据库从一种一致性状态转换为另一种一致性状态: 1.3.组成 事务可由一条非常简单的SQL语句组成,也可以由一组复杂的SQL语句组成: 其中单条语句会默认自动添加事务控制 ...

  10. MYSQL事务原理分析

    目录 事务是什么 ACID特性 原子性(A) 隔离性(I) 持久性(D) 一致性(C) 隔离级别 简介 有些什么 READ UNCOMMITTED(读未提交) READ COMMITTED(读已提交) ...

最新文章

  1. 第8章4节《MonkeyRunner源码剖析》MonkeyRunner启动运行过程-启动6
  2. 原 c++中map与unordered_map的区别
  3. 转:iFire:玩聚 SRBacks 自定义脚本及样式表
  4. 了解HTML 元素分类
  5. linux kvm usb设备,KVM客户机使用主机USB设备
  6. 二级(多级)指针,二级指针和二维数组的避坑,指针面试考题
  7. ftp连接 java.net.ConnectException: Connection refused
  8. 机器学习——人工神经网络之后向传播算法(BP算法)
  9. Linux中Shell的算数运算符和位运算符用法笔记
  10. Linux 下的DMA浅析
  11. 华为harmonyos发布会,海雀科技首款HarmonyOS智能摄像头Pro亮相华为智慧屏新品发布会...
  12. spring 使用小记
  13. VMware下 Fedora 14安装VMWare Tools
  14. pyplot中文手册_Matplotlib中文手册 PDF 下载
  15. 计算机表格斜杠怎么打,excel斜杠_excel表格打斜杠的方法步骤详解
  16. java 单机传奇_Win7/Win10系统架设单机传奇手游教程[战神引擎]
  17. Python提取pcap文件中原始数据
  18. Postman批量参数化测试
  19. SCI-EI-收录-检索-出版商 之间的关系
  20. 如何将excel表中的多行数据合并到一行

热门文章

  1. python立方体类_python学习12类
  2. 倍增(LCA与ST表)
  3. 写jsx_使用Vue 3.0做JSX(TSX)风格的组件开发
  4. The evolved Transformer,进化的变换器
  5. 282.给表达式添加运算符
  6. Android Studio3.5 JAVA调用C++源码方法总结
  7. 利用反射给属性赋值,调用方法,调用构造器--getDeclaredField,getDeclaredMethod,getDeclardConstructor
  8. Lucene的几种分词系统
  9. 最新隐马尔可夫模型HMM详解
  10. scala 主构造函数_Scala主构造器和辅助构造器