当前位置:首页 > 文章列表 > 数据库 > MySQL > 万字长文,带你从源码认识Spring事务原理,让Spring事务不再是面试噩梦

万字长文,带你从源码认识Spring事务原理,让Spring事务不再是面试噩梦

来源:SegmentFault 2023-01-26 17:51:28 0浏览 收藏

来到golang学习网的大家,相信都是编程学习爱好者,希望在这里学习数据库相关编程知识。下面本篇文章就来带大家聊聊《万字长文,带你从源码认识Spring事务原理,让Spring事务不再是面试噩梦》,介绍一下MySQL、Redis、Java、springboot、后端,希望对大家的知识积累有所帮助,助力实战开发!

gif5新文件(1).gif

create table test.user(
id int auto_increment    
primary key,
name varchar(20) null, age int(3) null)
engine=InnoDB charset=utf8;

2.创建对应数据库表的 PO

public class JdbcUser {

    private Integer id;

    private String name;

    private Integer age;
    
    ...(使用 ctrl + N 进行代码补全 setter 和 getter)
}

3.创建表与实体间的映射

在使用

public class UserRowMapper implements RowMapper {


    @Override
    public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
        JdbcUser user = new JdbcUser();
        user.setId(rs.getInt("id"));
        user.setName(rs.getString("name"));
        user.setAge(rs.getInt("age"));
        return user;
    }
}

4.创建数据操作接口

public interface UserDao {

    /**
     * 插入
     * @param user    用户信息
     */
    void insertUser(JdbcUser user);

    /**
     * 根据 id 进行删除
     * @param id    主键
     */
    void deleteById(Integer id);

    /**
     * 查询
     * @return    全部
     */
    List selectAll();
}

5.创建数据操作接口实现类

跟书中例子不一样,没有在接口上加入事务注解,而是在公共方法上进行添加,可以在每个方法上自定义传播事件、隔离级别。

public class UserJdbcTemplate implements UserDao {

    private DataSource dataSource;

    private JdbcTemplate jdbcTemplate;


    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void insertUser(JdbcUser user) {
        String sql = "insert into user (id, name, age) values (?, ?, ?)";
        jdbcTemplate.update(sql, user.getId(), user.getName(), user.getAge());
        System.out.println("Create record : " + user.toString());
    }

    @Override
    @Transactional
    public void deleteById(Integer id) {
        String sql = "delete from user where id = ?";
        jdbcTemplate.update(sql, id);
        System.out.println("Delete record, id = " + id);
        // 事务测试,抛出异常,让上面的插入操作回滚
        throw new RuntimeException("aa");
    }


    @Override
    public List selectAll() {
        String sql = "select * from user";
        List users = jdbcTemplate.query(sql, new UserRowMapper());
        return users;
    }



    public void setDataSource(DataSource dataSource) {
        // 使用 setter 注入参数时,同时初始化 jdbcTemplate
        this.dataSource = dataSource;
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

}

6.创建配置文件

7.添加依赖

记得添加数据库连接和

optional(project(":spring-jdbc"))  // for Quartz support
optional(project(":spring-tx"))  // for Quartz support
compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.6'

8.启动代码

public class TransactionBootstrap {

    public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("transaction/transaction.xml");
            UserJdbcTemplate jdbcTemplate = (UserJdbcTemplate) context.getBean("userJdbcTemplate");
            System.out.println("--- Records Creation start ----");
            JdbcUser user = new JdbcUser(4, "test", 21);
            jdbcTemplate.insertUser(user);
    }
}

通过上面的代码,我做了两个测试:

  1. 配置文件中,没开启事务。也就是
    @Override
    public void init() {
        registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser());
        // 使用 AnnotationDrivenBeanDefinitionParser 解析器,解析 annotation-driven 标签
        registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
        registerBeanDefinitionParser("jta-transaction-manager", new JtaTransactionManagerBeanDefinitionParser());
    }

    根据上面的方法,

    public BeanDefinition parse(Element element, ParserContext parserContext) {
        registerTransactionalEventListenerFactory(parserContext);
        String mode = element.getAttribute("mode");
        // AspectJ 另外处理
        if ("aspectj".equals(mode)) {
            // mode="aspectj"
            registerTransactionAspect(element, parserContext);
            if (ClassUtils.isPresent("javax.transaction.Transactional", getClass().getClassLoader())) {
                registerJtaTransactionAspect(element, parserContext);
            }
        }
        else {
            // mode="proxy"
            AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext);
        }
        return null;
    }

    本篇笔记主要围绕着默认实现方式,动态

    private static class AopAutoProxyConfigurer {
        public static void configureAutoProxyCreator(Element element, ParserContext parserContext) {
            // 注册 InfrastructureAdvisorAutoProxyCreator 自动创建代理器
            AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);
            // txAdvisorBeanName = org.springframework.transaction.config.internalTransactionAdvisor
            String txAdvisorBeanName = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME;
            if (!parserContext.getRegistry().containsBeanDefinition(txAdvisorBeanName)) {
                Object eleSource = parserContext.extractSource(element);
                // Create the TransactionAttributeSource definition.
                // 创建 TransactionAttributeSource 的 bean
                RootBeanDefinition sourceDef = new RootBeanDefinition(
                        "org.springframework.transaction.annotation.AnnotationTransactionAttributeSource");
                // 注册 bean,并使用 Spring 中的定义规则生成 beanName
                String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef);
                // 创建 TransactionInterceptor 的 bean
                RootBeanDefinition interceptorDef = new RootBeanDefinition(TransactionInterceptor.class);
                interceptorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));
                String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef);
                // 创建 TransactionAttributeSourceAdvisor 的 bean
                RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryTransactionAttributeSourceAdvisor.class);
                // 将 sourceName 的 bean 注入 advisor 的 transactionAttributeSource 属性中
                advisorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));
                // 将 interceptorName 的 bean 注入到 advisor 的 adviceBeanName 属性中
                advisorDef.getPropertyValues().add("adviceBeanName", interceptorName);
                if (element.hasAttribute("order")) {
                    // 如果配置了 order 属性,则加入到 bean 中
                    advisorDef.getPropertyValues().add("order", element.getAttribute("order"));
                }
                // 以 txAdvisorBeanName 名字注册 advisorDef
                parserContext.getRegistry().registerBeanDefinition(txAdvisorBeanName, advisorDef);
                // 创建 CompositeComponentDefinition
                CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), eleSource);
                compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName));
                compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName));
                compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, txAdvisorBeanName));
                parserContext.registerComponent(compositeDef);
            }
        }
    }

    在这里注册了代理类和三个

    public static void registerAutoProxyCreatorIfNecessary(
            ParserContext parserContext, Element sourceElement) {
        BeanDefinition beanDefinition = AopConfigUtils.registerAutoProxyCreatorIfNecessary(
                parserContext.getRegistry(), parserContext.extractSource(sourceElement));
        useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
        registerComponentIfNecessary(beanDefinition, parserContext);
    }
    
    private static void registerComponentIfNecessary(@Nullable BeanDefinition beanDefinition, ParserContext parserContext) {
        if (beanDefinition != null) {
            // 注册的 beanName 是 org.springframework.aop.config.internalAutoProxyCreator
            parserContext.registerComponent(
                    new BeanComponentDefinition(beanDefinition, AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME));
        }
    }

    在这一步中,注册了一个

    public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
        if (bean != null) {
            // 组装 key
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                // 如果适合被代理,则需要封装指定的 bean
                return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }
        return bean;
    }

    其中关于

    public static boolean canApply(Advisor advisor, Class> targetClass, boolean hasIntroductions) {
        if (advisor instanceof IntroductionAdvisor) {
            return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
        }
        else if (advisor instanceof 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;
        }
    }

    我们在前面看到,

    private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() {
        @Override
        @Nullable
        protected TransactionAttributeSource getTransactionAttributeSource() {
            // 实现父类的方法,在子类中进行了扩展,返回之前在标签注册时的AnnotationTransactionAttributeSource
            return transactionAttributeSource;
        }
    };

    匹配标签 match

    在匹配

    @Override
    public boolean matches(Method method, Class> targetClass) {
        // 事务切点匹配的方法
        TransactionAttributeSource tas = getTransactionAttributeSource();
        return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
    }

    那它到底到哪一步解析事务注解的呢,继续跟踪代码,答案是:

    AnnotationTransactionAttributeSource#determineTransactionAttribute

    protected TransactionAttribute determineTransactionAttribute(AnnotatedElement element) {
        for (TransactionAnnotationParser parser : this.annotationParsers) {
            TransactionAttribute attr = parser.parseTransactionAnnotation(element);
            if (attr != null) {
                return attr;
            }
        }
        return null;
    }

    在这一步中,遍历注册的注解解析器进行解析,由于我们关注的是事务解析,所以直接定位到事务注解的解析器:

    SpringTransactionAnnotationParser#parseTransactionAnnotation(AnnotatedElement)

    public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {
        // 解析事务注解的属性
        AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
                element, Transactional.class, false, false);
        if (attributes != null) {
            return parseTransactionAnnotation(attributes);
        }
        else {
            return null;
        }
    }

    首先判断是否含有

    protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
        RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
        // 注释 9.4 解析事务注解的每一个属性
        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 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;
    }

    小结

    通过上面的步骤,完成了对应类或者方法的事务属性解析。

    主要步骤在于寻找增强器,以及判断这些增强器是否与方法或者类匹配。

    如果某个

    public Object invoke(MethodInvocation invocation) throws Throwable {
        // 注释 9.5 执行事务拦截器,完成整个事务的逻辑
        Class> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
        // Adapt to TransactionAspectSupport's invokeWithinTransaction...
        return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
    }

    实际调用了父类的方法:

    protected Object invokeWithinTransaction(Method method, @Nullable Class> targetClass,
            final InvocationCallback invocation) throws Throwable {
        // 如果transaction属性为null,则该方法是非事务性的
        TransactionAttributeSource tas = getTransactionAttributeSource();
        // 获取对应事务属性
        final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
        // 获取事务管理器
        final PlatformTransactionManager tm = determineTransactionManager(txAttr);
        // 构造方法唯一标识(类.方法)
        final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
        if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
            // 声明式事务处理
            // 标准事务划分 : 使用 getTransaction 和 commit / rollback 调用
            TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
            Object retVal;
            try {
                //传入的是回调函数对象: invocation.proceed。 执行被增强的方法
                retVal = invocation.proceedWithInvocation();
            }
            catch (Throwable ex) {
                // 异常回滚
                completeTransactionAfterThrowing(txInfo, ex);
                throw ex;
            }
            finally {
                // 清除信息
                cleanupTransactionInfo(txInfo);
            }
            // 提交事务
            commitTransactionAfterReturning(txInfo);
            return retVal;
        }
        else {
            // 编程式事务处理
            final ThrowableHolder throwableHolder = new ThrowableHolder();
            // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
            try {
                Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {
                    TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
                ...
                return result;
            }
        }
    }

    贴出的代码有删减,简略了错误异常的

    protected PlatformTransactionManager determineTransactionManager(@Nullable TransactionAttribute txAttr) {
        // Do not attempt to lookup tx manager if no tx attributes are set
        // 注释 9.6 寻找事务管理器
        if (txAttr == null || this.beanFactory == null) {
            // 如果没有事务属性或者 BeanFactory 为空时,从缓存里面寻找
            return asPlatformTransactionManager(getTransactionManager());
        }
    
        String qualifier = txAttr.getQualifier();
        // 如果注解配置中指定了事务管理器,直接取出使用
        if (StringUtils.hasText(qualifier)) {
            return determineQualifiedTransactionManager(this.beanFactory, qualifier);
        }
        else if (StringUtils.hasText(this.transactionManagerBeanName)) {
            return determineQualifiedTransactionManager(this.beanFactory, this.transactionManagerBeanName);
        }
        else {
            // 上面步骤都没找到,最后才去容器中,根据 className 来寻找
            PlatformTransactionManager defaultTransactionManager = asPlatformTransactionManager(getTransactionManager());
            ...
            return defaultTransactionManager;
        }
    }

    由于最开始我们在

    protected TransactionInfo createTransactionIfNecessary(PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) {
        // 如果没有名称指定则使用方法唯一标识,并使用 DelegatingTransactionAttribute 包装 txAttr
        if (txAttr != null && txAttr.getName() == null) {
            txAttr = new DelegatingTransactionAttribute(txAttr) {
                @Override
                public String getName() {
                    return joinpointIdentification;
                }
            };
        }
    
        TransactionStatus status = null;
        if (txAttr != null) {
            if (tm != null) {
                // 获取 TransactionStatus
                status = tm.getTransaction(txAttr);
            }
        }
        // 根据指定的属性与 status 准备一个 TransactionInfo
        return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
    }

    在创建事务方法中,主要完成以下三件事:

    1. 使用
      protected Object doGetTransaction() {
          DataSourceTransactionObject txObject = new DataSourceTransactionObject();
          txObject.setSavepointAllowed(isNestedTransactionAllowed());
          // 如果当前线程已经记录数据库链接则使用原有链接
          ConnectionHolder conHolder =
                  (ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
          // false 表示非新创建连接
          txObject.setConnectionHolder(conHolder, false);
          return txObject;
      }

      其中在同一个线程中,判断是否有重复的事务,是在

      private static final ThreadLocal> resources =
                  new NamedThreadLocal("Transactional resources");
                  
      private static Object doGetResource(Object actualKey) {
          Map map = resources.get();
          if (map == null) {
              return null;
          }
          Object value = map.get(actualKey);
          // Transparently remove ResourceHolder that was marked as void...
          if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
              map.remove(actualKey);
              // Remove entire ThreadLocal if empty...
              if (map.isEmpty()) {
                  resources.remove();
              }
              value = null;
          }
          return value;
      }

      结论:

      if (isExistingTransaction(transaction)) {
          // Existing transaction found -> check propagation behavior to find out how to behave.
          // 当前线程存在事务,分情况进行处理
          return handleExistingTransaction(def, transaction, debugEnabled);
      }

      PROPAGATION_NEVER

      在配置中配置设定为

      if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
              throw new IllegalTransactionStateException(
                      "Existing transaction found for transaction marked with propagation 'never'");
          }

      PROPAGATION_NOT_SUPPORTED

      如果有事务存在,将事务挂起,而不是抛出异常:

      if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
          Object suspendedResources = suspend(transaction);
          boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
          return prepareTransactionStatus(
                  definition, null, false, newSynchronization, debugEnabled, suspendedResources);
      }

      事务挂起

      对于挂起操作,主要目的是记录原有事务的状态,以便于后续操作对事务的恢复:

      实际上,

      protected Object doSuspend(Object transaction) {
          DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
          //  将数据库连接设置为 null
          txObject.setConnectionHolder(null);
          return TransactionSynchronizationManager.unbindResource(obtainDataSource());
      }

      最后调用的关键方法是

      private static Object doUnbindResource(Object actualKey) {
          Map map = resources.get();
          if (map == null) {
              return null;
          }
          Object value = map.remove(actualKey);
          if (map.isEmpty()) {
              resources.remove();
          }
          if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
              value = null;
          }
          if (value != null && logger.isTraceEnabled()) {
              Thread.currentThread().getName() + "]");
          }
          return value;
      }

      看了第七条参考资料中的文章,结合代码理解了事务挂起的操作:移除当前线程、数据源活动事务对象的一个过程

      那它是如何实现事务挂起的呢,答案是在

      SuspendedResourcesHolder suspendedResources = suspend(transaction);
      try {
          boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
          DefaultTransactionStatus status = newTransactionStatus(
                  definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
          // 新事务的建立
          doBegin(transaction, definition);
          prepareSynchronization(status, definition);
          return status;
      }
      catch (RuntimeException | Error beginEx) {
          resumeAfterBeginException(transaction, suspendedResources, beginEx);
          throw beginEx;
      }

      与前一个方法相同的是,在

      if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
          // 嵌入式事务的处理
          if (useSavepointForNestedTransaction()) {
              DefaultTransactionStatus status =
                      prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
              // 创建 savepoint
              status.createAndHoldSavepoint();
              return status;
          }
      }

      学习过数据库的朋友应该清楚

      protected void doBegin(Object transaction, TransactionDefinition definition) {
          DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
          Connection con = null;
          if (!txObject.hasConnectionHolder() ||
                  txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
              Connection newCon = obtainDataSource().getConnection();
      
              txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
          }
      
          txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
          con = txObject.getConnectionHolder().getConnection();
          // 设置隔离级别
          Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
          txObject.setPreviousIsolationLevel(previousIsolationLevel);
      
          // configured the connection pool to set it already).
          // 更改自动提交设置,由 spring 进行控制
          if (con.getAutoCommit()) {
              txObject.setMustRestoreAutoCommit(true);
              con.setAutoCommit(false);
          }
          // 准备事务连接
          prepareTransactionalConnection(con, definition);
          // 设置判断当前线程是否存在事务的依据
          txObject.getConnectionHolder().setTransactionActive(true);
      
          int timeout = determineTimeout(definition);
          if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
              txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
          }
      
          // Bind the connection holder to the thread.
          if (txObject.isNewConnectionHolder()) {
              // 将当前获取到的连接绑定到当前线程
              TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
              }
          }
      }
      结论:Spring 事务的开启,就是将数据库自动提交属性设置为 false

      小结

      在声明式的事务处理中,主要有以下几个处理步骤:

      1. 获取事务的属性
        tas.getTransactionAttribute(method, targetClass)
      2. 加载配置中配置的
        TransactionManager
        determineTransactionManager(txAttr);
      3. 不同的事务处理方式使用不同的逻辑:关于声明式事务和编程式事务,可以查看这篇文章-Spring编程式和声明式事务实例讲解
      4. 在目标方法执行前获取事务并收集事务信息:
        createTransactionIfNecessary(tm, txAttr, joinpointIdentification)
      5. 执行目标方法
        invocation.proceed()
      6. 出现异常,尝试异常处理
        completeTransactionAfterThrowing(txInfo, ex);
      7. 提交事务前的事务信息消除
        cleanupTransactionInfo(txInfo)
      8. 提交事务
        commitTransactionAfterReturning(txInfo)

      事务回滚 & 提交

      这两步操作,主要调用了底层数据库连接的

      API
      ,所以没有细说。

      总结

      本篇文章简单记录了如何使用

      Spring
      的事务,以及在代码中如何实现。

      在之前的使用场景中,只用到了默认配置的声明式事务

      @Transactional
      ,不了解其它属性设置的含义,也不知道在默认配置下,如果是同一个类中的方法自调用是不支持事务。

      所以,经过这一次学习和总结,在下一次使用时,就能够知道不同属性设置能解决什么问题,例如修改广播特性

      PROPAGATION
      ,让事务支持方法自调用,还有设置事务超时时间
      timeout
      、隔离级别等属性。

      看到这儿了,说明你是个坚持努力的人,求个点赞关注分享,顺便关注下公众号,里面有4000G免费视频资源,等你来拿哦。

      gif5新文件(1).gif

    终于介绍完啦!小伙伴们,这篇关于《万字长文,带你从源码认识Spring事务原理,让Spring事务不再是面试噩梦》的介绍应该让你收获多多了吧!欢迎大家收藏或分享给更多需要学习的朋友吧~golang学习网公众号也会发布数据库相关知识,快来关注吧!

版本声明
本文转载于:SegmentFault 如有侵犯,请联系study_golang@163.com删除
聊聊rocketmq-mysql的ColumnParser聊聊rocketmq-mysql的ColumnParser
上一篇
聊聊rocketmq-mysql的ColumnParser
CRM是怎样帮助企业带来效益的?
下一篇
CRM是怎样帮助企业带来效益的?
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • ChatExcel酷表:告别Excel难题,北大团队AI助手助您轻松处理数据
    ChatExcel酷表
    ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
    6116次使用
  • Any绘本:开源免费AI绘本创作工具深度解析
    Any绘本
    探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
    6531次使用
  • 可赞AI:AI驱动办公可视化智能工具,一键高效生成文档图表脑图
    可赞AI
    可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
    6345次使用
  • 星月写作:AI网文创作神器,助力爆款小说速成
    星月写作
    星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
    8307次使用
  • MagicLight.ai:叙事驱动AI动画视频创作平台 | 高效生成专业级故事动画
    MagicLight
    MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
    6938次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码