事务学习笔记

本文深入解析事务管理,涵盖事务分类(本地事务、编程事务、声明式事务)、事务属性(如Required、Mandatory等)、隔离级别及其对并发问题的影响,以及事务的ACID特性。同时,探讨了事务传播行为及其实现细节。
摘要由CSDN通过智能技术生成

本文从事务的分类和特性两个方面来介绍

事务的分类

本地事务

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);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值