Spring 事务管理详情介绍

一、事务管理介绍

事务是现代数据库理论中的核心概念之一,是逻辑上的一组操作,这组操作要么全都成功,要么全都失败。如果一组处理步骤或者全部发生或者一步也不执行,我们称该组处理步骤为一个事务。当所有的步骤像一个操作一样被完整地执行,我们称该事务被提交。由于其中的一部分或多步执行失败,导致没有步骤被提交,则事务必须回滚到最初的系统状态。分别是:提交事务(调用commit()方法)、回滚事务(失败提交-调用rollBack()方法)。有如下优点:

  1.为不同的事务API提供一致的编程模型,如JTA,JDBC,JPA,Hibernate,MyBatis数据库层和JDO

  2.支持 声明式事务管理

  3.提供比大多数复杂的事务API(诸如JTA)更简单的,更易于使用的 编程式 事务管理API

  4.非常好地整合Spring的各种数据访问抽象

事务管理究竟是否需要应用服务器?
Spring框架对事务管理的支持极大地改变了传统上认为J2EE应用需要应用服务器的认识。
这种改变尤其在于你不需要仅仅为了通过EJB来使用声明式事务而使用应用服务器。事实上,即使你的应用服务器拥有强大的JTA功能,你也有充分的理由可以发现,Spring框架的声明式事务提供了比EJB CMT更加强大、高效的编程模型。
一般来说,只有当你需要支持多个事务性资源时,你才需要应用服务器的JTA功能。而大多数应用并不需要能够处理跨越多种资源。许多高端应用使用单一的、高伸缩性的数据库,比如Oracle 9i RAC。
(当然,也许你需要应用服务器的其他功能,比如JMS或JCA。)
最重要的一点是,使用Spring,你可以选择何时把你的应用迁移到全功能的应用服务器。用硬编码去实现本地事务来替代EJB CMT或JTA,处理JDBC连接,或者还需要使用硬编码来处理那些全局的、受到容器管理的事务,这样的日子将一去不复返了。
使用Spring,你仅需要改动配置文件,而不必改动你的代码。

二、事务的属性(ACID)

事务必须服从ISO/IEC所制定的ACID原则。ACID是原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)的缩写。事务的原子性表示事务执行过程中的任何失败都将导致事务所做的任何修改失效。一致性表示当事务执行失败时,所有被该事务影响的数据都应该恢复到事务执行前的状态。隔离性表示在事务执行过程中对数据的修改,在事务提交之前对其他事务不可见。持久性表示当系统或介质发生故障时,确保已提交事务的更新不能丢失。持久性通过数据库备份和恢复来保证。

原子性(Atomicity): 事务作为一个整体被执行,包含在其中的对数据的操作要么全部被执行,要么都不执行.
一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态. 一致状态的含义是数据库中的数据应满足为完整性约束.
隔离性(Isolation):多个事务并发执行时, 一个事务的执行不应该影响其他事务的执行.
持久性(Durability):一个事务一旦提交, 他对数据库的修改应该永久保存在数据库中.

三、事务处理的API(事务管理器)

一般J2EE服务器支持三种类型的事务管理。即:JDBC事务,JTA事务,容器管理事务。

1. JDBC事务

public String delete(String id) {
String ID = id;
 db = new getConnection();
 Connection con = db.getConnection();
 try {
con.setAutoCommit(false);
db.executeUpdate("delete from helloworld where ID=" + ID); //更新操作1
  db.executeUpdate("delete from helloworld _book where ID=" + ID); //更新操作2
  db.executeUpdate("delete from helloworld_user where ID=" + ID); //更新操作3
  con.commit();//提交JDBC事务
  con.setAutoCommit(true); 
db.close();
return “success”;
 }
 catch (Exception e) {
  con.rollBack();//回滚JDBC事务
  e.printStackTrace();
db.close();
return “fail”;
  }
}

如上例:更新操作1,2,3只有当三步操作都成功完成才进行提交,否则回滚已经进行的操作。这样,保证了数据的完整性,不会因为突然断电等特殊情况导致的数据错误。

 
public class ProxyDataSource implements DataSource {
/** 数据源池配置 */
private Map<String, DataSource> dataSourcePoolConfig;

public Connection getConnection() throws SQLException {
        return createDataSource().getConnection();
}
private synchronized DataSource createDataSource() {
        String dbName = DataSourceContextHolder.getDbName();
        return dataSourcePoolConfig.get(dbName);
}

每次调用spring事务管理器之前,设置DataSourceContextHolder.set(“dbName”) 

事务提交之后在调用 DataSourceContextHolder.clear() 方法即可

 但是这样设计实际使用过程中也会遇到一些典型的问题,这就是在仔细了解spring中持久化层的设计之后,才能明白所产生的问题的原因。下面主要总结一下spring 持久化的设计。

 

Jdbc基本的编程模型

由于任何持久化层的封装实际上都是对java.sql.Connection等相关对象的操作,一个典型的数据操作的流程如下:

但在我们实际使用spring和ibatis的时候,都没有感觉到上面的流程,其实spring已经对外已经屏蔽了上述的操作,让我们更关注业务逻辑功能,但是我们有必要了解其实现,以便能够更好运用和定位问题。

 

开启事务:

在开启事务的时候,我们需要初始化事务上下文信息,以便在业务完成之后,需要知道事务的状态,以便进行后续的处理,这个上下文信息可以保存在 ThreadLocal里面,包括是否已经开启事务,事务的超时时间,隔离级别,传播级别,是否设置为回滚。这个信息对应用来说是透明的,但是提供给使用者编程接口,以便告知业务结束的时候是提交事务还是回滚事务。

 

获取连接

首先来看看spring如何获取数据库连接的,对于正常情况来看,获取连接直接调用DataSource.getConnection()就可以了,我们在自己实现的时候也肯定会这么做,但是需要考虑两种情况(这里面先不引入事务的传播属性):

1 还没有获取过连接,这是第一次获取连接

2 已经获取过连接,不是第一次获取连接,可以复用连接

解决获取数据库连接的关键问题就是如何判断是否已经可用的连接,而不需要开启新的数据库连接,同时由于数据库连接需要给后续的业务操作复用,如何保持这个连接,并且透明的传递给后续流程。对于一个简单的实现就是使用线程上下文变量ThrealLocal来解决以上两个问题。

具体的实现是:在获取数据库连接的时候,判断当前线程线程变量里面是否已经存在相关连接,如果不存在,就创新一个新的连接,如果存在,就直接获取其对应的连接。在第一次获取到数据库连接的时候,我们还需要做一些特殊处理,就是设置自动提交为false。在业务活动结束的时候在进行提交或者回滚。这个时候就是要调用connection.setAutoCommit(false)方法。

 

执行sql

这一部分和业务逻辑相关,通过对外提供一些编程接口,可以让业务决定业务完成之后如何处理事务,比较简单的就是设置事务状态。

 

提交事务:

在开启事务的时候,事务上下文信息已经保存在线程变量里面了,可以根据事务上下文的信息,来决定是否是提交还是回滚。其实就是调用数据库连接Connection.commit 和 Connection.rollback 方法。然后需要清空线程变量中的事务上下文信息。相当于结束了当前的事务。

  

关闭连接:

关闭连接相对比较简单,由于当前线程变量保存了连接信息,只需要获取连接之后,调用connection.close方法即可,接着清空线程变量的数据库连接信息。

 上面几个流程是一个简单的事务处理流程,在spring中都有对应的实现,见TransactionTemplate.execute方法。Spring定义了一个TransactionSynchronizationManager对象,里面保存了各种线程变量信息,

 

//保存了数据源和其对应连接的映射,value是一个Map结构,其中key为datasource,value为其打开的连接

private static final ThreadLocal resources

//这个暂时用不到,不解释

private static final ThreadLocal synchronizations

//当前事务的名字

private static final ThreadLocal currentTransactionName

//是否是只读事务以及事务的隔离级别(这个一般我们都用不到,都是默认界别)

private static final ThreadLocal currentTransactionReadOnly

private static final ThreadLocal currentTransactionIsolationLevel

//代表是否是一个实际的事务活动,这个后面将)

private static final ThreadLocal actualTransactionActive

 

在获取连接的时候,可见DataSourceUtils.doGetConnection()方法,就是从调用TransactionSynchronizationManager.getResource(dataSource)获取连接信息,如果为空,就直接从调用dataSource.getConnection()创建新的连接,后面在调用

TransactionSynchronizationManager.bindResource(dataSource,conn)绑定数据源到线程变量,以便后续的线程在使用。

ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
           if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
                   conHolder.requested();
               if (!conHolder.hasConnection()) {
                logger.debug("Fetching resumed JDBC Connection from DataSource");
                conHolder.setConnection(dataSource.getConnection());
                     }
                    return conHolder.getConnection();
                }
                  logger.debug("Fetching JDBC Connection from DataSource");
                  Connection con = dataSource.getConnection();

在提交事务的时候,见 DataSourceTransactionManager.doCommit方法,其实就是获取事务状态信息以及连接信息,调用conn.commmit方法,比较简单。

 

但是实际上,spring事务管理远远比上述复杂,我们没有考虑以下几种情况:

1 如果当前操作不需要事务支持,也就是每次执行一次,就自动进行提交。如何在同一个架构里面兼容这两种情况。比如就是简单的query操作。

2 一个业务活动跨越多个事务,每个事务的传播级别配置不一样。后面会拿一个例子来说明

 

对于第一个问题,比较好解决,首先就是根据线程变量里面获取数据源对应的连接,如果有连接,就复用。如果没有,就创建连接。在判断当前是否存在活动的事务上下文,如果存在事务信息,设置conn.setAutoCommit(false),然后设置线程上下文,绑定对应的数据源。如果不存在事务信息,就直接返回连接给应用。

这样就会带来一个新的问题,就是连接如何进行关闭。根据最开始的分析,在存在事务上下文的情况下,直接从获取线程获取对应的数据库连接,然后关闭。在关闭的也需要也进行判断一下即可。在spring里面,在事务中获取连接和关闭连接有一些特殊的处理,主要还是和其jdbc以及orm框架设计兼容。在jdbcTemplate,IbatiTemplate每执行一次sql操作,就需要获取conn,执行sql,关闭conn。如果不存在事务上下文,这样做没有任何问题,获取一次连接,使用完成,然后就是比。但是如果存在事务上下文,每次获取的conn并不一定是真实的物理连接,所以关闭的时候,也不能直接关闭这数据库连接。Spring的中定义一个ConnectionHandle对象,这个对象持有一个数据库连接对象,以及该连接上的引用次数(retain属性)。每次复用一次就retain++ 操作,没关闭一次,就执行retain-- 操作,在retain 为0的时候,说明没有任何连接,就可以进行真实的关闭了。

2.JTA事务
JTA是J2EE事务服务的解决方案、描述了J2EE模型事务接口。JTA具有三个主要的接口:UserTransaction、TransactionManager、Transaction接口。这些接口共享公共的事务操作,如:commit()、rollback()。同时各自也有自己的操作。举例说明:
 1 public String delete(String id) {
 2  String ID = id;
 3  db = new getConnection();
 4  db.getConnection();
 5  UserTransaction transaction = sessionContext.getUserTransaction();//获得JTA事务
 6  try {
 7   transaction.begin(); //开始JTA事务
 8   db.executeUpdate("delete from helloworld where ID=" + ID);
 9   db.executeUpdate("delete from helloworld _book where ID=" + ID);
10   db.executeUpdate("delete from helloworld _user where ID=" + ID);
11   transaction.commit(); //提交JTA事务
12   db.close();
13   return”success”;
14  }
15  catch (Exception e) {
16   try {
17    transaction.rollback();//事务回滚
18   }
19   catch (Exception e) {
20    e.printStackTrace();
21   }
22   exc.printStackTrace();
23   db.close();
24   return “fail”;
25  }
26 } 
View Code

参考:http://www.cnblogs.com/aigongsi/p/3152419.html

         《Spring in action》

原文地址:https://www.cnblogs.com/jackhman520/p/5659017.html