事务与分布式事务原理及应用
一、核心概念
1、概念
数据库事务:数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。
事务概念扩展:事务概念来源于数据库事务,扩展为事务是一个由有限操作集合组成的逻辑单元,包括文件系统,消息队列,一组不可分割的方法操作等。
事务操作的目的:
① 数据一致,指事务提交时保证事务内的所有操作都成功完成,并且更改永久生效;事务回滚时,保证能够恢复到事务执行之前的状态。
② 操作隔离,指多个同时执行的事务之间应该相互独立,互不影响。
2、事务的四个特性:ACID
- 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
- 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
- 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
- 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。
并发事务带来的问题
- 丢失更新(Lost Update): 当两个或多个事务选择同一行,最初的事务修改的值,会被后面的事务修改的值覆盖。
- 脏读(Dirty Reads): 当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
- 不可重复读(NonRepeatable Reads): 一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现和以前读出的数据不一致。
- 幻读(Phantom Reads): 一个事务按照相同的查询条件重新读取以前查询过的数据,却发现其他事务插入了满足其查询条件的新数据。
为了解决上述提到的事务并发问题,数据库提供一定的事务隔离机制来解决这个问题。MySQL的InnoDB引擎提供四种隔离级别(即ACID中的隔离性)
- 读未提交(READ UNCOMMITTED),能解决幻读问题问题;
- 读已提交(READ COMMITTED),能解决不可重复读取、幻读问题;
- 可重复读(REPEATABLE READ)(默认),能解决脏读、不可重复读、幻读问题;
- 串行化(SERIALIZABLE)
InnoDB默认的隔离级别是REPEATABLE READ
,其可避免脏读和不可重复读,但不能避免幻读。
引申:并发事务控制是通过MVCC(多版本并发控制)实现,MVCC是乐观锁的一种实现方式,目的是解决读写互斥问题,增大并发程度。mysql中的mvcc通过隐藏列DB_TRX_ID、DB_ROLL_PTR和ReadView实现。DB_TRX_ID是该行记录当前的事务id,DB_ROLL_PTR指向该行在undo log中的位置,ReadView是一个记录当前活跃事务id的链表。
当一行记录被修改时,undo文件中会存储它的相反操作,并且存有修改之前的版本号。每次修改会在undo文件中形成链式存储,链表头部是最近的版本,尾部是最老版本。当事务在执行select操作之前,会先构造ReadView(快照),并通过ReadView判断当前记录是否是可见的。ReadView中按序存储(事务id排序)着所有活跃事务id,如果当前记录的事务id小于ReadView中的最小值,意味着该记录已经被提交,则该记录可见。如果大于ReadView中最大值,那么该记录应当在之后的事务中提交,因此属于不可见。如果处于最大最小值之前,则需要通过二分查找判断,事务id是否存在于ReadView中,如果存在,说明该记录的事务活跃,但无法判定事务是否已经提交,因此需要读取DB_ROLL_PTR,获取上一个版本的事务id,看看上个版本是否是可见的,通过层层递归,直到某一个之前的版本满足可见的要求。
传送门:https://baijiahao.baidu.com/s?id=1709427910908845097&wfr=spider&for=pc
https://www.cnblogs.com/orangelsk/articles/15807591.html
3、传播行为(Spring针对方法嵌套调用时事务的创建行为定义了七种事务传播机制)
事务的第一个方面是传播行为(propagation behavior)。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。Spring定义了七种传播行为:
传播行为 | 含义 |
---|---|
PROPAGATION_REQUIRED |
表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务 |
PROPAGATION_SUPPORTS |
表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行 |
PROPAGATION_MANDATORY |
表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常 |
PROPAGATION_REQUIRED_NEW |
表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager |
PROPAGATION_NOT_SUPPORTED |
表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager |
PROPAGATION_NEVER |
表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常 |
PROPAGATION_NESTED |
表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务 |
4、事务执行原理(以mysql innodb引擎为例)
1). redo log
redo log 在InnoDB存储引擎中产生的,是用来实现事务的原子性和持久性。该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都会存到该日志中,也就是对磁盘上的数据进行的修改操作,用于在刷新脏页到磁盘发生错误时, 进行数据恢复使用(Redo Log往往用来恢复提交后的物理数据页,不过只能恢复到最后一次提交的位置)。
start transaction; select balance from bank where name="Tom"; -- 生成 重做日志 balance=8000 update bank set balance = balance - 2000; -- 生成 重做日志 account=2000 update finance set account = account + 2000; commit;
执行流程如图所示:
mysql 为了提升性能不会把每次的修改都实时同步到磁盘,而是会先存到Buffer Pool(缓冲池)里 头,把这个当作缓存来用。然后使用后台线程将缓存池刷新到磁盘。 当在执行刷新时,宕机或者断电,可能会丢失部分数据。所以引入了redo log来记录已成功提交事务的修改信息,并且在事务提交时会把redo log持久化到磁盘,系统重启之后在读取redo log恢复最新数据。 简单来说 , redo log是用来恢复数据的用于保障,已提交事务的持久化特性 ;
2). undo log
undo log 即回滚日志,记录数据被修改前的信息。用于回滚事务和多版本并发事务。正好跟前面所说的重做日志所记录的相反,重做日志记录数据被修改后的信息。undo log主要记录的是数据的逻辑变化,为了在发生错误时回滚到之前的操作,需要将之前的操作都记录下来,然后在发生错误时才可以回滚。
undo log 记录事务修改之前版本的数据信息,因此假如由于系统错误或者rollback操作而回滚的话,可以根据undo log的信息来进行回滚到没被修改前的状态。
在MySQL启动事务之前,会先将要修改的数据记录存储到Undo Log中。如果数据库的事务回滚或者MySQL数据库崩溃,可以利用Undo Log对数据库中未提交的事务进行回滚操作,从而保证数据库中数据的一致性。Undo Log会在事务开始前产生,当事务提交时,并不会立刻删除相应的Undo Log。此时,InnoDB存储引擎会将当前事务对应的Undo Log放入待删除的列表,接下来,通过一个后台线程purgethread进行删除处理。
3)redo log和undo log区别
Undo Log与Redo Log不同,Undo Log记录的是逻辑日志,可以这样理解:当数据库执行一条insert语句时,Undo Log会记录一条对应的delete语句;当数据库执行一条delete语句时,UndoLog会记录一条对应的insert语句;当数据库执行一条update语句时,Undo Log会记录一条相反的update语句。
需要注意的是,因为MySQL事务执行过程中产生的Undo Log也需要进行持久化操作,所以UndoLog也会产生Redo Log。由于Undo Log的完整性和可靠性需要Redo Log来保证,因此数据库崩溃时需要先做Redo Log数据恢复,然后做Undo Log回滚。
5、spring事务原理
纯JDBC操作数据库事务步骤:
- 获取连接 Connection con = DriverManager.getConnection()
- 开启事务con.setAutoCommit(true/false);
- 执行CRUD
- 提交事务/回滚事务 con.commit() / con.rollback();
- 关闭连接 conn.close();
使用Spring的事务管理功能后,步骤 2 和 4 的代码,而是由Spirng 自动完成。以注解方式为例,配置文件开启注解驱动,在相关的类和方法上通过注解@Transactional标识。Spring 在启动的时候会去解析生成相关的bean,这时候会查看拥有相关注解的类和方法,并且为这些类和方法生成代理,并根据@Transaction的相关参数进行相关配置注入,这样就在代理中增加了事务代码逻辑(开启正常提交事务,异常回滚事务)。而实质实现事务功能是通过数据库的事务处理(见上文,4、事务执行原理)。
二、应用场景
1、JDBC事务
在JDBC中处理事务,都是通过Connection完成的。同一事务中所有的操作,都在使用同一个Connection对象。
Connection的三个方法与事务有关:
- setAutoCommit(boolean):设置是否为自动提交事务,如果true(默认值为true)表示自动提交,也就是每条执行的SQL语句都是一个单独的事务,如果设置为false,那么相当于开启了事务了;
- commit():提交结束事务。
- rollback():回滚结束事务。
JDBC处理事务的代码格式:
try{ con.setAutoCommit(false);//开启事务 ...... con.commit();//try的最后提交事务 } catch() { con.rollback();//回滚事务 }
eg:
import cn.itcast.jdbc.JdbcUtils; import org.junit.Test; import java.sql.Connection; import java.sql.SQLException; public class Demo1 { /* * 演示转账方法 * 所有对Connect的操作都在Service层进行的处理 * 把所有connection的操作隐藏起来,这需要使用自定义的小工具(day19_1) * */ public void transferAccounts(String from,String to,double money) { //对事务的操作 Connection con = null; try{ con = JdbcUtils.getConnection(); con.setAutoCommit(false); AccountDao dao = new AccountDao(); dao.updateBalance(con,from,-money);//给from减去相应金额 if (true){ throw new RuntimeException("不好意思,转账失败"); } dao.updateBalance(con,to,+money);//给to加上相应金额 //提交事务 con.commit(); } catch (Exception e) { try { con.rollback(); } catch (SQLException e1) { e.printStackTrace(); } throw new RuntimeException(e); } } @Test public void fun1() { transferAccounts("zs","ls",100); } }
补充:
JDBC事务优缺点:JDBC为使用Java进行数据库的事务操作提供了最基本的支持。通过JDBC事务,我们可以将多个SQL语句放到同一个事务中,保证其ACID特性。但是,一个 JDBC 事务不能跨越多个数据库,不支持多数据库的操作或分布式场景。
2、JTA事务
JTA(Java Transaction API)是一种高层的,与实现无关的,与协议无关的API,应用程序和应用服务器可以使用JTA来访问事务。JTA提供了跨数据库连接的事务管理能力。JTA事务管理则由JTA容器实现。一个分布式事务包括一个事务管理器和一个或多个资源管理器。它是XA协议(采用两阶段提交方式来管理分布式事务)的Java版本规范,JavaEE的13个规范之一。
1)JTA的构成
a、高层应用事务界定接口,供事务客户界定事务边界的;
b、X/Open XA协议(资源之间的一种标准化的接口)的标准Java映射,它可以使事务性的资源管理器参与由外部事务管理器控制的事务中;
c、高层事务管理器接口,允许应用程序服务器为其管理的应用程序界定事务的边界;
2)JTA是基于Java语言开发分布式事务二阶段提交的标准。而要想真正的使用,必须使用它的实现。JavaEE容器技术(也有称为服务器或中间件的),如Tomcat,weblogic ,websphere, JBOSS,Jetty,Resin等等。这里面有轻量级,也有重量级。凡是重量级服务器都是完整实现了JavaEE规范的,也就是说可以直接使用JTA的实现。除了完整的实现了JavaEE规范的容器外,还有单独针对JTA进行实现的,像 atomikos,JOTM。
3、容器事务:主要指的是J2EE应用服务器提供的事务管理,如在Spring、Hibernate等框架中都有各自的事务管理功能,表现形式不同,但都是在JAVA事务管理的基础上实现的。
Spring事务
Spring并不直接管理事务,而是提供了多种事务管理器,从本质上讲,Spring事务是对数据库事务的进一步封装。也就是说,如果数据库不支持事务,Spring也无法实现事务操作。他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。
Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。此接口的内容如下:
Public interface PlatformTransactionManager()...{ // 由TransactionDefinition得到TransactionStatus对象 TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; // 提交 Void commit(TransactionStatus status) throws TransactionException; // 回滚 Void rollback(TransactionStatus status) throws TransactionException; }
所以Spring事务管理的一个优点就是为不同的事务API提供一致的编程模型,如JTA、JDBC、Hibernate、JPA。
Spring事务抽象的核心类图
部分Spring包含的对PlatformTransactionManager
的实现类如下图所示:
AbstractPlatformTransactionManager
抽象类实现了Spring事务的标准流程,其子类DataSourceTransactionManager
是我们使用较多的JDBC单数据源事务管理器,而JtaTransactionManager
是JTA(Java Transaction API)规范的实现类,另外两个则分别是JavaEE容器WebLogic和WebSphere的JTA事务管理器的具体实现。
spring事务核心逻辑
事务拦截器TransactionInterceptor
在invoke
方法中,通过调用父类TransactionAspectSupport
的invokeWithinTransaction
方法进行事务处理,该方法支持声明式事务和编程式事务。
// TransactionInterceptor.class @Override public Object invoke(final MethodInvocation invocation) throws Throwable { // 获取targetClass ... // Adapt to TransactionAspectSupport's invokeWithinTransaction... return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() { @Override public Object proceedWithInvocation() throws Throwable { // 实际执行目标方法 return invocation.proceed(); } }); } // TransactionInterceptor父类TransactionAspectSupport.class protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation) throws Throwable { // If the transaction attribute is null, the method is non-transactional. // 查询目标方法事务属性、确定事务管理器、构造连接点标识(用于确认事务名称) final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass); final PlatformTransactionManager tm = determineTransactionManager(txAttr); final String joinpointIdentification = methodIdentification(method, targetClass, txAttr); if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) { // 事务获取 TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); Object retVal = null; try { // 通过回调执行目标方法 retVal = invocation.proceedWithInvocation(); } catch (Throwable ex) { // 目标方法执行抛出异常,根据异常类型执行事务提交或者回滚操作 completeTransactionAfterThrowing(txInfo, ex); throw ex; } finally { // 清理当前线程事务信息 cleanupTransactionInfo(txInfo); } // 目标方法执行成功,提交事务 commitTransactionAfterReturning(txInfo); return retVal; } else { // 带回调的事务执行处理,一般用于编程式事务 ... } }
TransactionAspectSupport
//TransactionAspectSupport.class protected TransactionInfo createTransactionIfNecessary( PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) { ... TransactionStatus status = null; if (txAttr != null) { if (tm != null) { // 获取事务 status = tm.getTransaction(txAttr); ... } protected void commitTransactionAfterReturning(TransactionInfo txInfo) { if (txInfo != null && txInfo.hasTransaction()) { ... // 提交事务 txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); } } protected void completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable ex) { if (txInfo != null && txInfo.hasTransaction()) { ... if (txInfo.transactionAttribute.rollbackOn(ex)) { try { // 异常类型为回滚异常,执行事务回滚 txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus()); } ... } else { try { // 异常类型为非回滚异常,仍然执行事务提交 txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); } ... } protected final class TransactionInfo { private final PlatformTransactionManager transactionManager; ...
1)spring-jdbc
如果应用程序中直接使用JDBC来进行持久化,DataSourceTransactionManager会处理事务边界。为了使用DataSourceTransactionManager,需要使用如下的XML将其装配到应用程序的上下文定义中:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>
实际上,DataSourceTransactionManager是通过调用java.sql.Connection来管理事务,而后者是通过DataSource获取到的。通过调用连接的commit()方法来提交事务,同样,事务失败则通过调用rollback()方法进行回滚。
2)Hibernate事务
如果应用程序的持久化是通过Hibernate实现的,那么需要使用HibernateTransactionManager。对于Hibernate3,需要在Spring上下文定义中添加如下的<bean>
声明:
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean>
sessionFactory属性需要装配一个Hibernate的session工厂,HibernateTransactionManager的实现细节是它将事务管理的职责委托给org.hibernate.Transaction对象,而后者是从Hibernate Session中获取到的。当事务成功完成时,HibernateTransactionManager将会调用Transaction对象的commit()方法,反之,将会调用rollback()方法。
3)Java持久化API事务(JPA)
Hibernate多年来一直是事实上的Java持久化标准,但是现在Java持久化API作为真正的Java持久化标准进入大家的视野。如果使用JPA的话,需要使用Spring的JpaTransactionManager来处理事务。需要在Spring中这样配置JpaTransactionManager:
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean>
4)Java原生API事务
如果没有使用以上所述的事务管理,或者是跨越了多个事务管理源(比如两个或者是多个不同的数据源),就需要使用JtaTransactionManager:
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"> <property name="transactionManagerName" value="java:TransactionManager" /> </bean>
JpaTransactionManager只需要装配一个JPA实体管理工厂(javax.persistence.EntityManagerFactory接口的任意实现)。JpaTransactionManager将与由工厂所产生的JPA EntityManager合作来构建事务。
三、Spring事务应用---编程式事务和声明式事务
区别:Spring提供了对编程式事务和声明式事务的支持,编程式事务允许用户在代码中精确定义事务的边界,而声明式事务(基于AOP)有助于用户将操作与事务规则进行解耦(对原有代码无侵入)。
(1)编程式事务
Spring提供两种方式的编程式事务管理,分别是:使用TransactionTemplate和直接使用PlatformTransactionManager。
1.1)使用TransactionTemplate
采用TransactionTemplate和采用其他Spring模板,如JdbcTempalte和HibernateTemplate是一样的方法。它使用回调方法,把应用程序从处理取得和释放资源中解脱出来。如同其他模板,TransactionTemplate是线程安全的。代码片段:
TransactionTemplate tt = new TransactionTemplate(); // 新建一个TransactionTemplate Object result = tt.execute( new TransactionCallback(){ public Object doTransaction(TransactionStatus status){ updateOperation(); return resultOfUpdateOperation(); } }); // 执行execute方法进行事务管理
使用TransactionCallback()可以返回一个值。如果使用TransactionCallbackWithoutResult则没有返回值。
1.2)使用PlatformTransactionManager
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); //定义一个某个框架平台的TransactionManager,如JDBC、Hibernate dataSourceTransactionManager.setDataSource(this.getJdbcTemplate().getDataSource()); // 设置数据源 DefaultTransactionDefinition transDef = new DefaultTransactionDefinition(); // 定义事务属性 transDef.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED); // 设置传播行为属性 TransactionStatus status = dataSourceTransactionManager.getTransaction(transDef); // 获得事务状态 try { // 数据库操作 dataSourceTransactionManager.commit(status);// 提交 } catch (Exception e) { dataSourceTransactionManager.rollback(status);// 回滚 }
(2)声明式事务
①声明式事务原理
声明式事务的实现就是通过环绕增强的方式,在目标方法执行之前开启事务,在目标方法执行之后提交或者回滚事务,事务拦截器的继承关系图可以体现这一点:
②用法:根据代理机制的不同,总结了五种Spring事务的配置方式,配置文件如下:
1)每个Bean都有一个代理
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="configLocation" value="classpath:hibernate.cfg.xml" /> <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" /> </bean> <!-- 定义事务管理器(声明式的事务) --> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <!-- 配置DAO --> <bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <bean id="userDao" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <!-- 配置事务管理器 --> <property name="transactionManager" ref="transactionManager" /> <property name="target" ref="userDaoTarget" /> <property name="proxyInterfaces" value="com.bluesky.spring.dao.GeneratorDao" /> <!-- 配置事务属性 --> <property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> </beans>
2)所有Bean共享一个代理基类
3)使用拦截器
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="configLocation" value="classpath:hibernate.cfg.xml" /> <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" /> </bean> <!-- 定义事务管理器(声明式的事务) --> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor"> <property name="transactionManager" ref="transactionManager" /> <!-- 配置事务属性 --> <property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames"> <list> <value>*Dao</value> </list> </property> <property name="interceptorNames"> <list> <value>transactionInterceptor</value> </list> </property> </bean> <!-- 配置DAO --> <bean id="userDao" class="com.bluesky.spring.dao.UserDaoImpl"> <property name="sessionFactory" ref="sessionFactory" /> </bean> </beans>
4)使用tx标签配置的拦截器
5)全注解
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> <context:annotation-config /> <context:component-scan base-package="com.bluesky" /> <tx:annotation-driven transaction-manager="transactionManager"/> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="configLocation" value="classpath:hibernate.cfg.xml" /> <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" /> </bean> <!-- 定义事务管理器(声明式的事务) --> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> </beans>
此时在DAO上需加上@Transactional注解,如下:
import java.util.List; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; import org.springframework.stereotype.Component; @Transactional @Component("userDao") public class UserDaoImpl extends HibernateDaoSupport implements UserDao { public List<User> listUsers() { return this.getSession().createQuery("from User").list(); } }
eg:
数据库表:
book(isbn, book_name, price)
account(username, balance)
book_stock(isbn, stock)
xml配置:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> <import resource="applicationContext-db.xml" /> <context:component-scan base-package="com.springinaction.transaction"> </context:component-scan> <tx:annotation-driven transaction-manager="txManager"/> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> </beans>
BookShopServiceImpl
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @Service("bookShopService") public class BookShopServiceImpl implements BookShopService { @Autowired private BookShopDao bookShopDao; /** * 1.添加事务注解 * 使用propagation 指定事务的传播行为,即当前的事务方法被另外一个事务方法调用时如何使用事务。 * 默认取值为REQUIRED,即使用调用方法的事务 * REQUIRES_NEW:使用自己的事务,调用的事务方法的事务被挂起。 * * 2.使用isolation 指定事务的隔离级别,最常用的取值为READ_COMMITTED * 3.默认情况下 Spring 的声明式事务对所有的运行时异常进行回滚,也可以通过对应的属性进行设置。通常情况下,默认值即可。 * 4.使用readOnly 指定事务是否为只读。 表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务。若真的是一个只读取数据库值得方法,应设置readOnly=true * 5.使用timeOut 指定强制回滚之前事务可以占用的时间。 */ @Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.READ_COMMITTED, noRollbackFor={UserAccountException.class}, readOnly=true, timeout=3) @Override public void purchase(String username, String isbn) { //1.获取书的单价 int price = bookShopDao.findBookPriceByIsbn(isbn); //2.更新书的库存 bookShopDao.updateBookStock(isbn); //3.更新用户余额 bookShopDao.updateUserAccount(username, price); } }
备注:和spring boot集成使用方式参考 https://zhuanlan.zhihu.com/p/227922586
四、分布式事务
1、核心概念
(1)分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。简单来说就是组成事务的各个单元处于不同数据库服务器上。 在我们的实际开发中,分布式事务无处不在,比如,电商系统中的生成订单,账户扣款,减少库存,增加会员积分等等,他们就是组成事务的各个单元,它们要么全部发生,要么全部不发生,从而保证最终一致,数据准确。
(2)刚性事务和柔性事务:
刚性事务指的就是遵循本地事务四大特性(ACID)的强一致性事务。它的特点就是强一致性,要求组成事务的各个单元马上提交或者马上回滚,没有时间弹性,要求以同步的方式执行。通常在单体架构项目中应用较多,一般都是企业级应用(或者局域网应用)。例如:生成合同时记录日志,付款成功后生成凭据等等。
柔性事务是针对刚性事务而说的,刚性事务的两个特点,强一致性和近实时性 。而柔性事务的特点是不需要立刻马上执行(同步性),且不需要强一致性。它只要满足基本可用和最终一致就可以了。即根据每个业务根据自身的特点,采用适当的方式来使系统达到最终一致性。
2、5种分布式事务解决方案原理(推荐文章 https://developer.51cto.com/art/201907/600249.htm)
1)2pc(两阶段提交协议)
两阶段,即准备阶段(投票阶段) 和 提交/回滚阶段(执行阶段)。确定是存在节点同步阻塞问题,单点故障情况下参与者存在阻塞问题,可以通过补偿机制避免这些问题影响。
2)3pc(三阶段提交协议)
三阶段提交协议是对二阶段提交协议的补充,弥补了二阶段提交协议的不足,增加了canCommit阶段,减少了不必要的资源浪费,这个阶段只校验sql,不执行事务,不占用资源。同时,3pc协议在协调者和参与者中引入了超时机制。
3)TCC(补偿性事务)
即try(业务检查阶段)-confirm(业务确认阶段)-cancal(业务回滚阶段),一种常用的分布式事务解决方案;
4)本地消息表(可靠消息最终一致性方案)
事务发起方执行完本地事务,发动一条消息,事务参与方一定能够接收消息并可以处理自己的事务。
(1)本地消息表
这种实现方式应该是业界使用最多的,其核心思想是将分布式事务拆分成本地事务进行处理,这种思路是来源于ebay。它和MQ事务消息的实现思路都是一样的,都是利用MQ通知不同的服务实现事务的操作。不同的是,针对消息队列的信任情况,分成了两种不同的实现。本地消息表它是对消息队列的稳定性处于不信任的态度,认为消息可能会出现丢失,或者消息队列的运行网络会出现阻塞,于是在数据库中建立一张独立的表,用于存放事务执行的状态,配合消息队列实现事务的控制。
(2)MQ事务消息
有一些第三方的MQ是支持事务消息的,比如RocketMQ,ActiveMQ,他们支持事务消息的方式也是类似于采用的二阶段提交。 以RocketMQ中间件为例,其思路大致为:
第一阶段Prepared消息,会拿到消息的地址。
第二阶段执行本地事务。
第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。
也就是说在业务方法内要想消息队列提交两次请求,一次发送消息和一次确认消息。如果确认消息发送失败了 RocketMQ会定期扫描消息集群中的事务消息,这时候发现了Prepared消息,它会向消息发送者确认,所以生产方需要实现一个check接口,RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。
RocketMQ事务消息的执行流程图
RocketMQ应用示例:
5)Sagas事务模型(最终一致性)
Saga模式是一种分布式异步事务,一种最终一致性事务,是一种柔性事务。
4、分布式事务框架-Seata
(1)简介
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务4种模式,为用户打造一站式的分布式解决方案。
在图中的三个和seata相关的组件,他们分别是:
1)事务协调器(TC):维护全局和分支事务的状态,驱动全局提交或回滚。
2)事务管理器(TM):定义全局事务的范围:开始全局事务,提交或回滚全局事务。
3)资源管理器(RM):管理分支事务的资源,与TC通信以注册分支事务和报告分支事务的状态,并驱动分支事务 提交或回滚。
执行流程如下:
1.TM要求TC开始新的全局事务。 TC生成表示全局事务的XID。
2.XID通过微服务的调用链传播。
3.RM将本地事务注册为XID到TC的相应全局事务的分支。
4.TM要求TC提交或回滚XID的相应全局事务。
5.TC在XID的相应全局事务下驱动所有分支事务,以完成分支提交或回滚。
(2)Seata AT 模式
两阶段提交协议的演变:
-
一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
-
二阶段:
- 提交异步化,非常快速地完成。
- 回滚通过一阶段的回滚日志进行反向补偿。
(3)TCC模式
TCC模式又称为补偿型分布式事务解决方案,它是通过开发者自己编写补偿代码来实现事务各个组成部分的最终一致性的。其原理图大致如下:
(3)、ShardingSphere 集成了 SEATA作为分布式事务解决方案
Apache ShardingSphere ,是一套开源的分布式数据库解决方案组成的生态圈,包括由JDBC、Proxy 和 Sidecar(规划中)。TCC 和 Saga 是两种常见分布式事务实现方案, 主张开发者自行实现对数据库的反向操作,来达到数据在回滚时仍能够保证最终一致性。 SEATA 实现了 SQL 反向操作的自动生成,可以使柔性事务不再必须由开发者介入才能使用。ShardingSphere 集成了 SEATA 作为柔性事务的使用方案。
5、分布式事务框架-Atomikos(不推荐)
atomikos一套开源的,JTA规范的实现。它是基于二阶段提交思想实现分布式事务的控制。是分布式刚性事务的一种解决方案。通常情况下使用2pc提交方案的场景都是单服务多数据源(多数据库)的情况。执行原理如图所示:
6、分布式事务解决方案特点
(1)TCC方案
特点:严格一致性 执行时间短 实时性要求高
适用场景:抢红包 实时转账汇款 收付款
(2)异步确保方案
特点:实时性不高 执行周期较长
适用场景:非实时汇款 退货退款业务
(3)最大努力通知方案
特点:高并发低耦合 不支持回滚
适用场景:获取交易结果(例如:共享单车支付等)
感谢阅读,借鉴了不少大佬资料,如需转载,请注明出处,谢谢!https://www.cnblogs.com/huyangshu-fs/p/15600093.html