Spring 做为风靡世界的 Java 开源框架,发挥着举足轻重的作用。那你有没有想过, Spring 内部又是怎么样实现的事务呢?
而且 在 Spring 之中除了设置事务的「隔离级别」之外,还可以额外配置事务的「传播特性」。你要知道,传播特性里,有两个家伙比较特别,一个PROPAGATION_REQUIRES_NEW ,还有一个是PROPAGATION_NESTED。你要知道,所谓的 REQUIRES_NEW,是会在方法级联调用的时候,会开启一个新的事务,同时挂起(suspend)当前事务;NESTED 代表的嵌套事务,则是在方法级联调用的时候,在嵌套事务内执行。
我们应该都知道,这些传播行为,是 Spring 独有的,和数据库没有一毛钱关系。那在底层 Spring 用了什么黑魔法吗?是怎么样做到挂起事务的,又是怎么样嵌套事务的呢?
咱们一起来揭秘。
毋庸置疑,数据的操作中,我们离不开事务。
银行的转帐操作中,我们相信如果一边扣款,那对方一定会收到,而不竹篮打水一场空
事务在很多场景中都发挥着关键作用。
咱们先以 MySQL 为例,来捋一捋数据库的事务,隔离级别。
然后再来看这些数据库的配置,特性,在 Spring 里是怎样做的事务对应的。
以及 Spring 所谓的传播特性,又是如何作用到数据库的事务的,怎样做到挂起事务,嵌套事务。
事务
事务是什么?
它是一级原子性的 SQL 操作,一个独立的工作单元。如果工作单元中的SQL语句执行成功,那会全部成功,一个失败就会全部回滚。
与数据库的事务同时为大众熟知的是ACID。即数据库的原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。就像应用内为了线程之间的互斥,线程安全等,需要做大量的工作,数据库为了 ACID,也做了许多的工作。
隔离级别
SQL 的标准中定义了四种隔离级别,规定了哪些是事务之间可见的,哪些是事务内可见的。
READ UNCOMMITTED (未提交读) --> 两个事务间,一个的事务还没提交,但被另一个看到了。
READ COMMITTED (提交读) --> 两个事务间,只有一个事务提交之后,另一个事务才能看到。
REPEATABLE READ (可重复读)--> 一个事务执行中,多次读到的结果是一样的,即使其他事务做了修改,也先「看不见」
SERIALIZABLE (可串行化)--> 最高隔离级别,强制事务串行执行。
由于这些隔离级别,会造成所谓的「脏读」、「幻读」、「不可重复读」等问题,所以需要根据具体的场景选择适用的。
如何设置隔离级别
对于 MySQL 的隔离级别,可以全局的,也可以设置 Session 级别的。
官方文档设置语法如下:
我们通过客户端连接到 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 里,隔离级别的定义有五种,四个和数据库的一致,另外包含一个 DEFAULT,代表不具体设置,直接使用数据库定义的。
咱们看到数据库的隔离级别可以设置 GLOBAL 级别的,也可以设置 SESSION 级别的,每个 SESSION 则代表的是一个连接。而在Spring 内,我们又是通过一个个独立的「连接」来操作数据库,完成CRUD。到这儿咱们应该就能知道,在 Spring 层面的设置,是通过 session 级别的 隔离来设置的,从而和数据库里事务隔离级别做了对应来实现。
那传播特性又是怎么回事呢?既然数据库并不直接支持这样的特性,Spring 又根据不同的传播特性要求,来迂回实现了。
总结起来,对于级联操作中,如果是REQUIRED_NEW这种的情况,所谓的挂起当前事务,开启新的事务,是 Spring 又去申请了一个新的 Connection,这样就会对应到一个新的事务上,然后将这个连接绑定到当前线程,再继续执行;
对于嵌套事务,底层则是通过数据库的 SAVEPOINT 来实现所谓的「子事务」
如果你熟悉代码 DEBUG 过程中每个方法对应的 Frame,可以类比一下,如果执行失败回滚的时候,可以指定回滚到当前事务的某个 SAVEPOINT,不需要全部回滚。
在 Spring 层面,是通过 JDBC 3.0来实现的,看下面这段代码注释。
*
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 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> 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 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 的实现对应的是一个新的事务,拿到的是新的资源,所以外层事务回滚时,不影响内层事务。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Responsive Web Design
Ethan Marcotte / Happy Cog / 2011-6 / USD 18.00
From mobile browsers to netbooks and tablets, users are visiting your sites from an increasing array of devices and browsers. Are your designs ready? Learn how to think beyond the desktop and craft be......一起来看看 《Responsive Web Design》 这本书的介绍吧!