访问原文java
在本教程中,咱们将学习@Transactional注解的事务隔离级别和传播行为。git
咱们可使用@Transactional
注解为一个方法添加事务支持。github
咱们能够经过它指定设置事务的传播行为、超时时间与回滚条件。此外,咱们还能够经过它指定一个事务管理器。spring
Spring经过建立代理对象或者操纵字节码来实现事务的建立、提交与回滚。sql
在经过代理模式实现事务管理的状况下,对于来自类内部的调用@Transactional
会失效。数据库
简单地说,假如咱们有一个叫callMethod
的方法并且该方法被@Transactional
注解标记过。并发
Spring将使用一些用于事务管理的代码将这个方法进行包装,@Transactional
注解的实现可使用下面的伪代码表示:ide
// 若有必要则建立事务
createTransactionIfNecessary();
try {
// 调用callMethod方法
callMethod();
// 调用成功则提交事务
commitTransactionAfterReturning();
} catch (exception) {
// 调用失败则回滚事务
completeTransactionAfterThrowing();
throw exception;
}
复制代码
咱们能够在接口、类、方法上面使用这个注解,位于接口、类、方法上的@Transactional
注解会按照不一样的优先级发生覆盖,优先级底的将被高的覆盖掉,覆盖的优先级从低到高分别是:性能
Spring会把一个类上标注的@Transactional
注解应用到该类全部public
的方法上,因此咱们不须要再单独给方法上标注@Transactional
。学习
可是,若是咱们在private
或protected
方法上标注@Transcational注解,那么Spring将会忽略这些注解并且不给出任何错误提示。
让咱们从在接口上标注@Transcational注解开始:
@Transactional
public interface TransferService {
void transfer(String user1, String user2, double val);
}
复制代码
一般状况下,不推荐在接口上标注@Transactional
注解,然而在某些接口上使用也是能够接受的,好比:Spring Data的@Repository
接口。
咱们能够将这个注解标注在类的定义上,这样能够覆盖接口或超类中标注的@Transactional
注解:
@Service
@Transactional
public class TransferServiceImpl implements TransferService {
@Override
public void transfer(String user1, String user2, double val) {
// ...
}
}
复制代码
如今,让咱们将这个注解直接标注在方法的定义上:
@Transactional
public void transfer(String user1, String user2, double val) {
// ...
}
复制代码
事务传播行为定义了咱们业务逻辑的事务边界,Spring根据咱们配置的事务传播行为开始或者中断事务。
Spring经过调用TransactionManager::getTransaction
方法而后根据事务的传播行为来获取或者建立一个事务,它支持部分在TransactionManager
中定义的事务传播行为,可是还有一些特殊的事务传播行为须要经过一些特殊的 TransactionManager
实现去支持。
REQUIRED
是默认的事务传播行为。对于该传播行为而言,Spring会检查当前线程上下文中是否存在一个活跃的事务。
若是不存在活跃的事务则会建立一个新的事务。
若是存在一个活跃的事务则将当前的业务逻辑算入这个活跃的事务范围中:
@Transactional(propagation = Propagation.REQUIRED)
public void requiredExample(String user) {
// ...
}
复制代码
因为REQUIRED
是默认的事务传播行为,因此上面的代码能够简写成:
@Transactional
public void requiredExample(String user) {
// ...
}
复制代码
来咱们看一下建立具备REQUIRED
传播行为事务的伪代码:
// 检测是否已经位于事务中
if (isExistingTransaction()) {
if (isValidateExistingTransaction()) {
validateExisitingAndThrowExceptionIfNotValid();
}
// 返回已有的事务
return existing;
}
// 不然不使用事务
return createNewTransaction();
复制代码
对于SUPPORTS
而言,Spring会先检查线程上下文中是否存在一个活跃的事务,若是存在一个活跃的事务则使用它。若是没有,则以不使用事务的方式去执行supportsExample()
方法::
@Transactional(propagation = Propagation.SUPPORTS)
public void supportsExample(String user) {
// ...
}
复制代码
让咱们看看使用SUPPORTS
传播行为建立事务的伪代码:
// 检测是否已经位于事务中
if (isExistingTransaction()) {
if (isValidateExistingTransaction()) {
validateExisitingAndThrowExceptionIfNotValid();
}
// 返回已有的事务
return existing;
}
// 不然不使用事务
return emptyTransaction;
复制代码
当传播行为被设为MANDATORY
时,若是Spring没有在线程上下文中找到一个活跃的事务则会抛出一个异常:
@Transactional(propagation = Propagation.MANDATORY)
public void mandatoryExample(String user) {
// ...
}
复制代码
让咱们看一下该选项对应的伪代码表示:
// 检测是否已经位于事务中
if (isExistingTransaction())
{
if (isValidateExistingTransaction()) {
validateExisitingAndThrowExceptionIfNotValid();
}
// 返回已有的事务
return existing;
}
// 不然抛出异常
throw IllegalTransactionStateException;
复制代码
当传播行为被设为NEVER
时,Spring在线程上下文中发现一个活跃事务时会抛出一个异常:
@Transactional(propagation = Propagation.NEVER)
public void neverExample(String user) {
// ...
}
复制代码
让咱们看看使用NEVER
传播行为建立事务的伪代码:
// 检测是否已经位于事务中
if (isExistingTransaction()) {
// 若是位于事务中就抛出异常
throw IllegalTransactionStateException;
}
// 不然不使用事务
return emptyTransaction;
复制代码
当传播行为被设为NOT_SUPPORTED
时,Spring会将线程上下文中现有的事务进行挂起,而后再以不使用事务的方式去执行notSupportedExample()
方法:
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void notSupportedExample(String user) {
// ...
}
复制代码
JTATransactionManager
事务管理器以开箱即用的方式支持真正的事务挂起。其它 的事务管理器则是经过保存现有事务的引用而后将其从线程上下文中清除的方法来模拟事务挂起。
当传播行为设为REQUIRES_NEW
时,Spring发现线程上下文中存在一个活跃的事务时,则先将其挂起,而后建立一个新的事务去执行requiresNewExample()
方法:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void requiresNewExample(String user) {
// ...
}
复制代码
与NOT_SUPPORTED
选项相似,对于真正的事务挂起,咱们须要使用JTATransactionManager
事务管理器
该选项对应的伪代码实现:
// 检测是否已经位于事务中
if (isExistingTransaction()) {
// 先挂起已存在的事务
suspend(existing);
try {
// 而后建立一个新事务
return createNewTransaction();
} catch (exception) {
// 若是新事务发生异常则恢复旧事务
resumeAfterBeginException();
throw exception;
}
}
// 没有找到旧事务,直接建立新事务
return createNewTransaction();
复制代码
对于NESTED
而言,Spring会检测线程上下文中是否存在活跃的事务。
若是已经存在则在该事务上创建一个保存点,这意味着若是咱们的nestedExample()
方法发生异常,该事务将会回滚到咱们创建的保存点处。
若是没有检测到活跃事务的存在,那么该选项的行为与REQUIRED
选项同样。
DataSourceTransactionManager
支持以开箱即用的方式使用该选项。此外,JTATransactionManager
的某些实现也支持这个选项。
JpaTransactionManager
仅对JDBC链接支持NESTED
选项。可是,若是咱们将nestedTransactionAllowed
标志设置为true
,并且咱们的JDBC驱动程序也支持保存点功能,则它也适用于在JPA事务中的JDBC
访问代码。
最后,让咱们把传播行为设为NESTED
:
@Transactional(propagation = Propagation.NESTED)
public void nestedExample(String user) {
// ...
}
复制代码
隔离性是ACID属性中的一个:
隔离性表示的是数据在并发事务间的可见性。
每种级别的隔离能够防止在并发事务中零或多个并发反作用的发生:
咱们能够经过@Transactional::isolation
设置事务的隔离级别。在Spring中,它可使用下面五个枚举值:
在Spring中事务的默认隔离级别是DEFAULT
。因此,当Spring建立新事务时,隔离级别是咱们数据库中所设置的隔离级别。所以,若是咱们在对数据库中的数据进行修改时须要注意这一点。
咱们还要思考在调用链中的,当各个方法对应不一样的事务隔离级别时会出现什么状况。
一般状况下,在建立事务时也会设置事务的隔离级别,若是咱们不想让咱们的调用链中的方法存在不一样的事务隔离级别,咱们能够设置TransactionManager::setValidateExistingTransaction
为true
,它的功能使用伪代码表示为:
// 若是隔离级别不是DEFAULT
if (isolationLevel != ISOLATION_DEFAULT) {
// 当前事务设置的隔离级别与已存在事务的隔离级别不一致则抛出异常
if (currentTransactionIsolationLevel() != isolationLevel) {
throw IllegalTransactionStateException
}
}
复制代码
如今让咱们深刻了解不一样的隔离级别与其做用。
READ_UNCOMMITTED
是隔离性最低的级别,具备最高的并发访问性能,该级别下会发生脏读、不可重复读与幻读的状况。
咱们能够为方法或类设置这个隔离级别:
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void log(String message) {
// ...
}
复制代码
Postgres不支持READ_UNCOMMITTED
隔离级别,它使用READ_COMMITED
隔离级别做为替代。Oracle不支持READ_UNCOMMITTED
级别。
隔离性的第二个级别是READ_COMMITTED
。它能够防止脏读的发生。
这个隔离级别下仍是存在不可重复读与幻读的状况,因此在并发事务中未提交的更改对咱们没有影响,可是,在事务提交后,对一样数据的从新查询可能会发生不一样的结果。
下面的代码设置事务的隔离级别为READ_COMMITTED
:
@Transactional(isolation = Isolation.READ_COMMITTED)
public void log(String message){
// ...
}
复制代码
隔离性的第三个级别是REPEATABLE_READ
。它能够防止脏读、不可重复读的发生。所以,咱们不受并发事务中未提交更改的影响。
一样,当咱们从新查询一行数据时,咱们不会获得不一样的结果。可是在从新执行范围查询时,咱们可能会得到新添加或删除的行。
此外,它是知足防止更新丢失的要求最低级别,当两个或多个并发事务读取并更新同一行时,就会发生更新丢失。REPEATABLE_READ
不容许并发事务同时访问同一行数据。所以,不会出现更新丢失的状况。
下面的代码设置事务的隔离级别为READ_COMMITTED
:
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void log(String message){
// ...
}
复制代码
REPEATABLE_READ
是Mysql中的默认隔离级别。Oracle不支持REPEATABLE_READ
隔离级别。
SERIALIZABLE
是隔离等级最高的级别。它能够防止脏读、不可重复读与幻读。
由于它把并发的事务变成了串行的,因此它可能会致使最低的并发访问性能,
换句话说,并行执行一组事务的结果与串行执行它们的结果相同。
如今看一下如何把隔离级别设为SERIALIZABLE
:
@Transactional(isolation = Isolation.SERIALIZABLE)
public void log(String message){
// ...
}
复制代码
在本教程中,咱们详细探讨了@Transaction的传播行为。而后,咱们学习了并发事务的反作用与隔离级别。
与往常同样,你能够在GitHub上找到完整的代码。