温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Spring是如何给事务实现传播特性的

发布时间:2021-10-29 20:20:19 来源:亿速云 阅读:170 作者:iii 栏目:编程语言

这篇文章主要介绍“Spring是如何给事务实现传播特性的”,在日常操作中,相信很多人在Spring是如何给事务实现传播特性的问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Spring是如何给事务实现传播特性的”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

事务

事务是什么?

它是一级原子性的SQL操作,一个独立的工作单元。如果工作单元中的SQL语句执行成功,那会全部成功,一个失败就会全部回滚。

与数据库的事务同时为大众熟知的是ACID。即数据库的原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。就像应用内为了线程之间的互斥,线程安全等,需要做大量的工作,数据库为了  ACID,也做了许多的工作。

隔离级别

SQL 的标准中定义了四种隔离级别,规定了哪些是事务之间可见的,哪些是事务内可见的。

READ UNCOMMITTED (未提交读) --> 两个事务间,一个的事务还没提交,但被另一个看到了。

READ COMMITTED (提交读) --> 两个事务间,只有一个事务提交之后,另一个事务才能看到。

REPEATABLE READ (可重复读)--> 一个事务执行中,多次读到的结果是一样的,即使其他事务做了修改,也先「看不见」

SERIALIZABLE (可串行化)--> 最高隔离级别,强制事务串行执行。

由于这些隔离级别,会造成所谓的「脏读」、「幻读」、「不可重复读」等问题,所以需要根据具体的场景选择适用的。

如何设置隔离级别

对于 MySQL 的隔离级别,可以全局的,也可以设置 Session 级别的。

官方文档设置语法如下:

Spring是如何给事务实现传播特性的

我们通过客户端连接到 MySQL Server 的时候,可以做一些配置,通过jdbc的 URL 你也能看的出来,这样设置的就是 Session  级别的。

参考上面的官方文档,设置 session 隔离级别的命令是它:

set session transaction isolation level SERIALIZABLE;

注意,MySQL 默认的隔离级别是 REPEATABLE READ,我的改过。同时,这里查询当前隔离级别的SQL,旧版本的是SELECT  @@TX_ISOLATION;,新版本的是SELECT @@Transaction_ISOLATION; 注意区分。

Spring是如何给事务实现传播特性的

Spring 事务又是怎么回事

看过了数据库的隔离级别之后,我们再来看 Spring 里的实现。

在 Spring 里,隔离级别的定义有五种,四个和数据库的一致,另外包含一个 DEFAULT,代表不具体设置,直接使用数据库定义的。

咱们看到数据库的隔离级别可以设置 GLOBAL 级别的,也可以设置 SESSION 级别的,每个 SESSION 则代表的是一个连接。而在Spring  内,我们又是通过一个个独立的「连接」来操作数据库,完成CRUD。到这儿咱们应该就能知道,在 Spring 层面的设置,是通过 session 级别的  隔离来设置的,从而和数据库里事务隔离级别做了对应来实现。

那传播特性又是怎么回事呢?既然数据库并不直接支持这样的特性,Spring 又根据不同的传播特性要求,来迂回实现了。

总结起来,对于级联操作中,如果是REQUIRED_NEW这种的情况,所谓的挂起当前事务,开启新的事务,是 Spring 又去申请了一个新的  Connection,这样就会对应到一个新的事务上,然后将这个连接绑定到当前线程,再继续执行;

对于嵌套事务,底层则是通过数据库的 SAVEPOINT 来实现所谓的「子事务」

Spring是如何给事务实现传播特性的

如果你熟悉代码 DEBUG 过程中每个方法对应的 Frame,可以类比一下,如果执行失败回滚的时候,可以指定回滚到当前事务的某个  SAVEPOINT,不需要全部回滚。

在 Spring 层面,是通过 JDBC 3.0来实现的,看下面这段代码注释。

* <p>This transaction manager supports nested transactions via the JDBC 3.0 * {@link java.sql.Savepoint} mechanism. The * {@link #setNestedTransactionAllowed "nestedTransactionAllowed"} flag defaults * to "true", since nested transactions will work without restrictions on JDBC * drivers that support savepoints (such as the Oracle JDBC driver).

事务挂起的部分代码如下:

/**    * Suspend the given transaction. Suspends transaction synchronization first,    * then delegates to the {@code doSuspend} template method.    * @param transaction the current transaction object    * (or {@code null} to just suspend active synchronizations, if any)    * @return an object that holds suspended resources    * (or {@code null} if neither transaction nor synchronization active)    * @see #doSuspend    * @see #resume    */   @Nullable   protected final SuspendedResourcesHolder suspend(@Nullable Object transaction) throws TransactionException {     if (TransactionSynchronizationManager.isSynchronizationActive()) {       List<TransactionSynchronization> suspendedSynchronizations = doSuspendSynchronization();       try {         Object suspendedResources = null;         if (transaction != null) {           suspendedResources = doSuspend(transaction);         }         String name = TransactionSynchronizationManager.getCurrentTransactionName();         TransactionSynchronizationManager.setCurrentTransactionName(null);         boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();         TransactionSynchronizationManager.setCurrentTransactionReadOnly(false);         Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();         TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null);         boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive();         TransactionSynchronizationManager.setActualTransactionActive(false);         return new SuspendedResourcesHolder(             suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive);       }   }

逻辑在 doSuspend里,继续看

@Override   protected Object doSuspend(Object transaction) {     DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;     txObject.setConnectionHolder(null);     return TransactionSynchronizationManager.unbindResource(obtainDataSource());   }    @Override   protected void doResume(@Nullable Object transaction, Object suspendedResources) {     TransactionSynchronizationManager.bindResource(obtainDataSource(), suspendedResources);   }

然后对应的bind 和 unbind 操作,是在ThreadLocal 对象里,将资源对象绑定或移出当前线程对应的 resources 来实现的。

private static final ThreadLocal<Map<Object, Object>> resources =       new NamedThreadLocal<>("Transactional resources"); /**    * Bind the given resource for the given key to the current thread.    * @param key the key to bind the value to (usually the resource factory)    * @param value the value to bind (usually the active resource object)    * @throws IllegalStateException if there is already a value bound to the thread    * @see ResourceTransactionManager#getResourceFactory()    */   public static void bindResource(Object key, Object value) throws IllegalStateException {     Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);     Assert.notNull(value, "Value must not be null");     Map<Object, Object> map = resources.get();     // set ThreadLocal Map if none found     if (map == null) {       map = new HashMap<>();       resources.set(map);     }     Object oldValue = map.put(actualKey, value);     // Transparently suppress a ResourceHolder that was marked as void...     if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {       oldValue = null;     }   }

对比上面的说明和代码,这两个传播特性的区别也很明了了:

NESTED 的特性,本质上还是同一个事务的不同保存点,如果涉及到外层事务回滚,则内层的也将会被回滚;

REQUIRED_NEW 的实现对应的是一个新的事务,拿到的是新的资源,所以外层事务回滚时,不影响内层事务。

到此,关于“Spring是如何给事务实现传播特性的”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI