本文从事务的分类和特性两个方面来介绍
事务的分类
本地事务
1、管理连接
2、getConnection
3、conn.setAutoCommit(false)默认值是true
4、conn.commit()
5、conn.rollback()
注
操作的是数据库连接
编程事务
1、管理事务Transaction不再是管理连接
2、getTransaction
3、txn.begin();
4、txn.commit();
5、txn.rollback();
使用场景
编程式事务通常的用武之地是客户端发起事务(cient-initiated transactions)的情形。如果客户端为一个业务请求做多次远程方法调用,从道理上讲事务必须由客户端开启。使用 JTA 时,就需要使用UserTransaction 接口和编程式事务。对这样的需求,您必须在客户端 bean 使用编程式事务,而在远程的 EJB 使用声明式事务——因为事务上下文不能被传递给使用编程式事务的 EJB。
声明式事务
和编程事务一样也是管理事务
事务属性
- 1、Required(需要)
Required 属性(对应 Spring 中的 PROPAGATION_REQUIRED 属性)告诉容器,目标对象方法需要一个事务。如果事务上下文已经存在,容器将会使用它;否则,容器将为此方法开启一个新事务。这是最常用到的事务属性,大多数开发人员都会使用它进行事务处理。然而,正如我们将会在后续章节看到的,有时候我们有更好的理由使用 Mandatory 属性。
- 2、Mandatory(强制必须)
Mandatory 属性(对应 Spring 中的 PROPAGATION_MANDATORY 属性)告知容器,特定的对象方法需要一个事务。然而,和 Required 属性不同的是,此属性标注后,容器并不为目标对象开启新的事务。当使用这个事务属性时,当方法被调用时,一个事先存在的事务上下文必须存在。如果此事务上下文不存在,容器将会抛出一个 TransactionRequiredException,宣告它需要一个已存在的事务,但此事务没有找到
- 3、RequiresNew(需要新的)
RequiresNew 属性(对应 Spring 中的 PROPAGATION_REQUIRES_NEW 属性)告知容器,当对象方法被调用时, “总是”需要开启一个新的事务。如果先于方法调用前一个事务已经开启了,此事务将被暂时挂起,容器启动一个新的事务。当这个新事务随着方法调用完成终止后,老的事务将会继续。在调用前已有事务上下文建立的情况下,使用 RequiresNew 属性违背了事务的 ACID 特性。这是因为直到新的事务结束时,原有事务一直处于被挂起的状态。一个事务如果需要与其外围包裹事务(surrounding transaction)相独立,不受其执行结果的影响,自行完成(也就是提交)时,这个属性相当有用。一个典型的例子便是记录审计日志。例如,在大多数交易系统中,无论任务成功与否,每个操作都必须要求要记录日志。看看商家进行股票交易的例子,假设一个事务从 placement()方法调用开始。该方法调用了另一个EJB 提供的,通用的 audit()方法去记录每一次尝试动作到数据库中。如果 audit()方法和外面的 placement()方法是在同一个事务中,当 placement()因其中业务操作发生问题回滚时, audit()也回滚了。这就与我们“无论任务成功与否,每个操作都必须要求要记录日志”的初衷相悖了。因此,通过设定 audit()方法的事务属性为 RequiresNew,便能够保证 audit()方法总能够提交,无论其外围 placement()方法操作后果如何
- 4、Supports(支持)
Supports 属性(对应 Spring 中的 PROPAGATION_SUPPORTS 属性)告知容器,对象方法并不需要一个事务上下文,但当调用到这个方法而事务上下文碰巧存在时,该方法会使用它。Supports 属性在事务管理中很强大很有用。考虑这样的例子,一个获取某商家的所有交易的简单数据库查询,执行这个操作并不一定需要事务存在。于是,使用 Supports 事务属性,告知容器在调用此方法时不必开启一个事务。然而,如果此查询是在一个正在进行的事务中完成的,对目标方法应用 Supports 属性会让容器使用当前事务上下文,参考数据库操作记录,从而将事务中作出的各种修改也包括到查询结果中。
- 5、NotSupported(不支持)
NotSupported 属性(对应 Spring 中的 PROPAGATION_NOT_SUPPORTED 属性)告知容器,被调用的对象方法不使用事务。如果一个事务业已启动,容器会将此事务暂停直至方法调用结束。如果调用方法时没有事务存在,容器也不会为此方法开启任何事务。在方法逻辑中有排斥事务上下文的代码存在时,这个属性就非常有用了。例如,在 XA 事务的上下文中调用包含 DDL 的存储过程便会导致异常。如果没有办法去修改这个存储过程,使用 NotSupported事务属性便是一个绕开问题的方法了。 NotSupported 属性会让在执行包含该存储过程的代码的方法前暂停事务,待其完成后再继续
- 6、Never(不用)
Never 属性(对应 Spring 中的 PROPAGATION_NEVER 属性)告知容器,在调用对象方法时,不允许有事务上下文存在。要注意它和 NOTSUPPORTED 属性的区别。当使用 NotSupported属性时,如果方法调用之前事务存在,容器会暂停此事务以执行方法,方法在没有事务上下文的环境下执行;而使用 NEVER 属性时,如果方法调用前事务存在,容器直接会抛出异常,告知您这个方法是决不能和事务有任何关系的。使用 Never 属性会造成一些不可预期和不希望发生的运行时异常,因此,在没有确定绝对必要时,最好不使用它。事实上,也没有太多的需求需要使用这个属性去构造事务解决方案。当我们为此事纠结时,选择 NOTSUPPORTED属性也许就够了。
7、NESTED
Spring 附加的属性 PROPAGATION_NESTED,该属性告知 Spring 进行事务嵌
套,并采用 Required 属性。当然,如果要使用这个设置,底层事务服务实现必须要能支持嵌套事务。虽然以上列出的事务属性能被应用于 bean 对象级别,通常我们会将它们应用在bean 的方法级别。当事务属性被应用在 bean 级别,bean 中所有的方法都被赋予相同的事务属性。如果有方法特别的应用了其他事务属性,方法级别的属性会被使用,而对象级别的属性指派在此方法就不起作用了
事务的特性
事务的隔离级别
- 1、TransactionReadUncommitted 读取未提交
这一隔离级别允许事务读取其他事务在提交到
数据库之前产生的未提交更改
- 2、TransactionReadCommitted 读取已提交
该隔离级别设置允许多个事务访问同一份数据,但将未提交的数据对其他事务隐藏,直至数据提交
- 3、TransactionRepeatableRead 可重复读(mysql默认的事物级别)
这一隔离级别保证,一旦在某一事务中读取了数据库的一个值集,在后续的每次查询操作中都读到同样的值(除非此事务拿到这些数据的读写锁,并自行更改了数据)
在 Repeatable Read 隔离级别下,一个事务如果要更改数据,而这一数据被其他事务读取时,此事务需要等待占用数据事务提交的操作(或直接返回失败)
- 4、TransactionSerializable 可序列化
这是 Java 所能支持的最低的事务隔离级别。在这一隔离级别设置下,交错发生的事务被“堆迭”起来,以致同一时间点仅仅有一个事务具备访问目标数据的权力
并发出现的三种情况
- Dirty reads–读脏数据
也就是说,比如事务A的未提交(还依然缓存)的数据被事务B读走,如果事务A失败回滚,会导致事务B所读取的的数据是错误的。
- non-repeatable reads–数据不可重复读
比如事务A中两处读取数据-total-的值。在第一读的时候,total是100,然后事务B就把total的数据改成200,事务A再读一次,结果就发现,total竟然就变成200了,造成事务A数据混乱。
- phantom reads–幻象读数据
这个和non-repeatable reads相似,也是同一个事务中多次读不一致的问题。但是non-repeatable reads的不一致是因为他所要取的数据集被改变了(比如total的数据),但是phantom reads所要读的数据的不一致却不是他所要读的数据集改变,而是他的条件数据集改变。比如Select account.id where account.name=”ppgogo*”,第一次读去了6个符合条件的id,第二次读取的时候,由于事务b把一个帐号的名字由”dd”改成”ppgogo1”,结果取出来了7个数据。
ACID特性
- 原子性(atomicity)
一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
- 一致性(consistency)
事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
- 隔离性(isolation)
一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
- 持久性(durability)
持续性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
事务传播
事务的传播特性描述的就是事务怎么样传播,如何传播,什么情况下回滚
理解事务如何传播可以参考上面的事务属性部分
事务的传播性适用于单一项目而非分布式事务。
例如:
1、当一个service A中引用了项目中的另一份service B, A在方法中调用了B的某个方法,这个时候的事务是怎么生效的,又是怎么回滚的。
解答: 这种情况下的事务情况根据具体的配置情况而定
2、当一个service A中有两个方法play 和 say ,play中存在事务,say中也有事务,这个时候play内部引用say方法。这个时候的事务又是怎么处理的。
解答: 这种情况下say方法的事务永远不会生效,因为事务是基于aop实现的,它代理的是对象而不是方法,内部方法的相互调用的时候事务是不会生效的。
关于如何根据事务属性来判断并实现的请参考:
代码实现
另外事务的回滚支持的是RuntimeException和Error的异常的自动回滚
参考代码
自己追踪的类
org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(Method, Class<?>, InvocationCallback)
捕获事务异常
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// target invocation exception
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
判断下一步是回滚还是提交
protected void completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable ex) {
if (txInfo != null && txInfo.hasTransaction()) {
if (logger.isTraceEnabled()) {
logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
"] after exception: " + ex);
}
if (txInfo.transactionAttribute.rollbackOn(ex)) {
try {
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by rollback exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException ex2) {
logger.error("Application exception overridden by rollback exception", ex);
throw ex2;
}
catch (Error err) {
logger.error("Application exception overridden by rollback error", ex);
throw err;
}
}
else {
// We don't roll back on this exception.
// Will still roll back if TransactionStatus.isRollbackOnly() is true.
try {
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by commit exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException ex2) {
logger.error("Application exception overridden by commit exception", ex);
throw ex2;
}
catch (Error err) {
logger.error("Application exception overridden by commit error", ex);
throw err;
}
}
}
}
只支持以下类型的自动回滚
public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error);
}