1 事务特性
-
原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
-
一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
-
隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
-
持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中
2 Spring 事务管理
Spring支持编程式事务管理以及声明式事务管理两种方式。
2.1 编程式事务管理
编程式事务管理是侵入性事务管理,使用TransactionTemplate
或者直接使用PlatformTransactionManager
,对于编程式事务管理,Spring推荐使用TransactionTemplate
。
使用TransactionTemplate
不需要显式地开始事务,甚至不需要显式地提交事务。这些步骤都由模板完成。但出现异常时,应通过TransactionStatus
的setRollbackOnly
显式回滚事务。
TransactionTemplate
的execute
方法接收一个TransactionCallback
实例
@Override
@Nullable
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
}
else {
TransactionStatus status = this.transactionManager.getTransaction(this);
T result;
try {
// 调用 callback
result = action.doInTransaction(status);
}
catch (RuntimeException | Error ex) {
// Transactional code threw application exception -> rollback
// 事务代码抛出应用程序异常 -> 回滚
rollbackOnException(status, ex);
throw ex;
}
catch (Throwable ex) {
// Transactional code threw unexpected exception -> rollback
// 事务代码引发意外异常->回滚
rollbackOnException(status, ex);
throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
}
this.transactionManager.commit(status);
return result;
}
}
TransactionCallback是一个函数接口:
/**
* 事务代码的回调接口。与 {@link TransactionTemplate} 的 {@code execute} 方法一起使用,通常作为方法实现中的匿名类。
*/
@FunctionalInterface
public interface TransactionCallback<T> {
/**
* 在事务上下文中由 {@link TransactionTemplateexecute} 调用。
* 不需要关心事务本身,尽管它可以通过给定的状态对象检索和影响当前事务的状态,例如设置仅回滚。
* 允许返回在事务中创建的结果对象,即域对象或域对象的集合。
* 回调抛出的 RuntimeException 被视为强制回滚的应用程序异常。
* 任何此类异常都将传播给模板的调用者,除非回滚出现问题,在这种情况下将抛出 TransactionException。
*/
@Nullable
T doInTransaction(TransactionStatus status);
}
使用示例:
transactionTemplate.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
try{
// 进行你的数据库操作
}catch (Exception e) {
// 仅设置事务回滚。 这指示事务管理器事务的唯一可能结果可能是回滚
// 作为抛出异常的替代方法,该异常反过来会触发回滚
status.setRollbackOnly();
}
});
2.2 声明式事务管理
声明式事务管理注解@Transactional
基于AOP实现,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。
显然声明式事务管理要优于编程式事务管理,这正是Spring倡导的非侵入式的编程方式。唯一不足的地方就是声明式事务管理的粒度是方法级别,而编程式事务管理是可以到代码块的,但是可以通过提取方法的方式完成声明式事务管理的配置。
public @interface Transactional {
/**
* @author Sam Brannen
*/
@AliasFor("transactionManager")
String value() default "";
/**
* 指定事务的限定符值。
* 可用于确定目标事务管理器
* 匹配特定PlatformTransactionManager bean 定义的限定符值(或 bean 名称)
*/
@AliasFor("value")
String transactionManager() default "";
/**
* 事务传播类型。
* 默认为Propagation.REQUIRED
*/
Propagation propagation() default Propagation.REQUIRED;
/**
* 事务隔离级别。
* 默认为Isolation.DEFAULT 。
* 专为与Propagation.REQUIRED或Propagation.REQUIRES_NEW一起使用而设计,因为它仅适用于新启动的事务。
* 如果您希望隔离级别声明在参与具有不同隔离级别的现有事务时被拒绝,
* 请考虑将事务管理器上的“validateExistingTransactions”标志切换为“true”
*/
Isolation isolation() default Isolation.DEFAULT;
/**
* 此事务的超时时间(以秒为单位)
* 默认为底层事务系统的默认超时。
* 专为与Propagation.REQUIRED或Propagation.REQUIRES_NEW一起使用而设计,因为它仅适用于新启动的事务
*/
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
/**
* 如果事务实际上是只读的,则可以将其设置为true布尔标志,
* 从而允许在运行时进行相应的优化。
* 默认为false
* 这只是对实际事务子系统的提示;
* 它不一定会导致写访问尝试失败。
* 无法解释只读提示的事务管理器在请求只读事务时不会抛出异常
* 而是默默地忽略该提示
*/
boolean readOnly() default false;
/**
* 定义零 (0) 个或多个异常classes ,它们必须是Throwable子类,
* 指示哪些异常类型必须导致事务回滚。
* 默认情况下,事务将在RuntimeException和Error上回滚,
* 但不会在已检查的异常(业务异常)上回滚
*/
Class<? extends Throwable>[] rollbackFor() default {};
/**
* 定义零 (0) 个或多个异常名称(对于必须是Throwable子类的异常)
* 指示哪些异常类型必须导致事务回滚。
* 这可以是完全限定类名的子字符串,目前不支持通配符。
* 例如, "ServletException"的值将匹配javax.servlet.ServletException及其子类。
* 注意:仔细考虑模式的具体程度以及是否包含包信息(这不是强制性的)。
* 例如, "Exception"几乎可以匹配任何内容,并且可能会隐藏其他规则。
* 如果"Exception"旨在为所有已检查的异常定义规则,则"java.lang.Exception"将是正确的。
* 对于更不寻常的Exception名称,例如"BaseBusinessException" ,则无需使用 FQN
*/
String[] rollbackForClassName() default {};
/**
* 定义零 (0) 个或多个异常Classes
* 它们必须是Throwable子类,指示哪些异常类型不得导致事务回滚。
* 这是构建回滚规则的首选方法(与noRollbackForClassName )
* 匹配异常类及其子类
*/
Class<? extends Throwable>[] noRollbackFor() default {};
/**
* 定义零 (0) 个或多个异常名称(对于必须是Throwable子类的异常)
* 指示哪些异常类型不得导致事务回滚。
*/
String[] noRollbackForClassName() default {};
}
需要注意的是,
@Transactional
基于动态代理,调用者和目标方法不在spring的同一个bean中,否则不生效。这个和动态代理有关
3 事务传播机制
枚举:org.springframework.transaction.annotation.Propagation
public enum Propagation {
/**
* Spring默认的传播机制,能满足绝大部分业务需求,
* 如果外层有事务,则当前事务加入到外层事务,一块提交,一块回滚。
* 如果外层没有事务,新建一个事务执行
*/
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
/**
* 如果外层有事务,则加入外层事务,
* 如果外层没有事务,则直接使用非事务方式执行。
* 完全依赖外层的事务
*/
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
/**
* 支持当前事务,如果不存在则抛出异常
*/
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
/**
* 该事务传播机制是每次都会新开启一个事务,
* 时把外层事务挂起,当当前事务执行完毕,恢复上层事务的执行。
* 如果外层没有事务,执行当前新开启的事务即可
*/
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
/**
* 该传播机制不支持事务,
* 如果外层存在事务则挂起,执行完当前代码,则恢复外层事务,
* 无论是否异常都不会回滚当前的代码
*/
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
/**
* 该传播机制不支持外层事务,即如果外层有事务就抛出异常
*/
NEVER(TransactionDefinition.PROPAGATION_NEVER),
/**
* 与NEVER相反,如果外层没有事务,则抛出异常
*/
NESTED(TransactionDefinition.PROPAGATION_NESTED);
}
4 事务隔离级别
多事务同时运行时,就会出现并发导致事务问题
-
脏读(Dirty read):脏读发生在一个事务读取了被另一个事务改写但尚未提交的数据时。如果这些改变在稍后被回滚了,那么第一个事务读取的数据就会是无效的。
另一个事务回滚了
-
不可重复读(Nonrepeatable read):不可重复读发生在一个事务执行相同的查询两次或两次以上,但每次查询结果都不相同时。这通常是由于另一个并发事务在两次查询之间更新了数据
另一个事务修改了数据。
-
幻读(Phantom reads):幻读和不可重复读相似。当一个事务(T1)读取几行记录后,另一个并发事务(T2)插入了一些记录时,幻读就发生了。在后来的查询中,第一个事务(T1)就会发现一些原来没有的额外记录。
另一个事务在新增或删除了数据
在理想状态下,事务之间将完全隔离,从而可以防止这些问题发生。然而,完全隔离会影响性能,因为隔离经常涉及到锁定在数据库中的记录(甚至有时是锁表)。完全隔离要求事务相互等待来完成工作,会阻碍并发。因此,可以根据业务场景选择不同的隔离级别。
隔离级别 | value | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
读未提交 | READ-UNCOMMITTED | 未解决 | 未解决 | 未解决 |
读提交 | READ-COMMITTED | 已解决 | 未解决 | 未解决 |
可重复读 | REPEATABLE-READ (mysql 默认级别) | 已解决 | 已解决 | 未解决 |
可串行化 | SERIALIZABLE | 已解决 | 已解决 | 已解决 |
5 只读
如果一个事务只对数据库执行读操作,那么该数据库就可能利用那个事务的只读特性,采取某些优化措施。通过把一个事务声明为只读,可以给后端数据库一个机会来应用那些它认为合适的优化措施。由于只读的优化措施是在一个事务启动时由后端数据库实施的, 因此,只有对于那些具有可能启动一个新事务的传播行为(Propagation.REQUIRES_NEW
、Propagation.REQUIRED
、Propagation.NESTED
)的方法来说,将事务声明为只读才有意义。
6 事务超时
为了使一个应用程序很好地执行,它的事务不能运行太长时间。因此,声明式事务的下一个特性就是它的超时。
假设事务的运行时间变得格外的长,由于事务可能涉及对数据库的锁定,所以长时间运行的事务会不必要地占用数据库资源。这时就可以声明一个事务在特定秒数后自动回滚,不必等它自己结束。
由于超时时钟在一个事务启动的时候开始的,因此,只有对于那些具有可能启动一个新事务的传播行为(Propagation.REQUIRES_NEW
、Propagation.REQUIRED
、Propagation.NESTED
)的方法来说,声明事务超时才有意义。
7 事务回滚
Spring在默认情况下,事务将在RuntimeException
和Error
上回滚,但不会在已检查的异常(业务异常)上回滚
不过,可以声明在出现特定受检查异常时像运行时异常一样回滚。同样,也可以声明一个事务在出现特定的异常时不回滚,即使特定的异常是运行时异常。
推广
公众号会推送更多更新,关注支持一下 _
错误的地方已更正