@caos
2014-09-04T21:53:55.000000Z
字数 15089
阅读 1993
编程
Transaction在此指的是数据访问的事务,也就是Database transaction,可以理解为它是一组具有原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)的数据操作。对于J2EE来说,事务是一个不可或缺的组建模型,它保证了用户对数据操作的ACID属性。
事务使用ACID特性来衡量事务的质量。介绍如下:
1. 原子性
事务必须是原子的,在事务结束的时候,事务中的所有任务必须全部成功完成,否则全部失败,事务回滚到事务开始之间的状态。
2. 一致性
事务必须保证和数据库的一致性,即数据库中的所有数据和现实保持一致。如果事务失败数据必须返回到事务执行之前的状态,反之修改数据和现实的同步。
3. 隔离性
隔离性是事务与事务之间的屏障,每个事务必须与其他事务的执行结果隔离开,直到该事务执行完毕,它保证了事务的访问的任何数据不会受其他事务执行结果的影响。
4. 持久性
如果事务成功执行,无论系统发生任何情况,事务的持久性都必须保证事务的执行结果是永久的。
提到事务我们都会联想到小明取钱的例子,再次就不赘述了,请自行脑补。
public void transferAccount() {
Connection conn = null;
Statement stmt = null;
try{
conn = getDataSource().getConnection();
// 将自动提交设置为 false,
//若设置为 true 则数据库将会把每一次数据更新认定为一个事务并自动提交
conn.setAutoCommit(false);
stmt = conn.createStatement();
// 将 A 账户中的金额减少 500
stmt.execute("\
update t_account set amount = amount - 500 where account_id = 'A'");
// 将 B 账户中的金额增加 500
stmt.execute("\
update t_account set amount = amount + 500 where account_id = 'B'");
// 提交事务
conn.commit();
// 事务提交:转账的两步操作同时成功
} catch(SQLException sqle){
try{
// 发生异常,回滚在本事务中的操做
conn.rollback();
// 事务回滚:转账的两步操作完全撤销
stmt.close();
conn.close();
}catch(Exception ignore){
}
sqle.printStackTrace();
}
}
public void transferAccount() {
UserTransaction userTx = null;
Connection connA = null;
Statement stmtA = null;
Connection connB = null;
Statement stmtB = null;
try{
// 获得 Transaction 管理对象
userTx = (UserTransaction)getContext().lookup("\
java:comp/UserTransaction");
// 从数据库 A 中取得数据库连接
connA = getDataSourceA().getConnection();
// 从数据库 B 中取得数据库连接
connB = getDataSourceB().getConnection();
// 启动事务
userTx.begin();
// 将 A 账户中的金额减少 500
stmtA = connA.createStatement();
stmtA.execute("
update t_account set amount = amount - 500 where account_id = 'A'");
// 将 B 账户中的金额增加 500
stmtB = connB.createStatement();
stmtB.execute("\
update t_account set amount = amount + 500 where account_id = 'B'");
// 提交事务
userTx.commit();
// 事务提交:转账的两步操作同时成功(数据库 A 和数据库 B 中的数据被同时更新)
} catch(SQLException sqle){
try{
// 发生异常,回滚在本事务中的操纵
userTx.rollback();
// 事务回滚:转账的两步操作完全撤销
//( 数据库 A 和数据库 B 中的数据更新被同时撤销)
stmt.close();
conn.close();
...
}catch(Exception ignore){
}
sqle.printStackTrace();
} catch(Exception ne){
e.printStackTrace();
}
}
架构:它包括事务管理器(Transaction Manager)和一个或多个支持 XA 协议的资源管理器 ( Resource Manager ) 两部分, 我们可以将资源管理器看做任意类型的持久化数据存储;事务管理器则承担着所有事务参与单元的协调与控制。 根据所面向对象的不同,我们可以将 JTA 的事务管理器和资源管理器理解为两个方面:面向开发人员的使用接口(事务管理器)和面向服务提供商的实现接口(资源管理器)。其中开发接口的主要部分即为上述示例中引用的 UserTransaction 对象,开发人员通过此接口在信息系统中实现分布式事务;而实现接口则用来规范提供商(如数据库连接提供商)所提供的事务服务,它约定了事务的资源管理功能,使得 JTA 可以在异构事务资源之间执行协同沟通。以数据库为例,IBM 公司提供了实现分布式事务的数据库驱动程序,Oracle 也提供了实现分布式事务的数据库驱动程序, 在同时使用 DB2 和 Oracle 两种数据库连接时, JTA 即可以根据约定的接口协调者两种事务资源从而实现分布式事务。正是基于统一规范的不同实现使得 JTA 可以协调与控制不同数据库或者 JMS 厂商的事务资源。
开发人员使用开发人员接口,实现应用程序对全局事务的支持;各提供商(数据库,JMS 等)依据提供商接口的规范提供事务资源管理功能;事务管理器( TransactionManager )将应用对分布式事务的使用映射到实际的事务资源并在事务资源间进行协调与控制。
UserTransaction开发人员通常只使用此接口实现 JTA 事务管理,
Transaction代表了一个物理意义上的事务,在开发人员调用 UserTransaction.begin() 方法时 TransactionManager 会创建一个 Transaction 事务对象(标志着事务的开始)并把此对象通过 ThreadLocale 关联到当前线程。UserTransaction 接口中的 commit()、rollback(),getStatus() 等方法都将最终委托给 Transaction 类的对应方法执行。
TransactionManager本身并不承担实际的事务处理功能,它更多的是充当用户接口和实现接口之间的桥梁。
在开发人员调用 UserTransaction.begin() 方法时 TransactionManager 会创建一个 Transaction 事务对象(标志着事务的开始)并把此对象通过 ThreadLocale 关联到当前线程上;同样 UserTransaction.commit() 会调用 TransactionManager.commit(), 方法将从当前线程下取出事务对象 Transaction 并把此对象所代表的事务提交, 即调用 Transaction.commit()
UserTransactionImpl implenments UserTransaction
public void begin() throws NotSupportedException, SystemException {
// 将开始事务的操作委托给 TransactionManagerImpl
TransactionManagerImpl.singleton().begin();
}
TransactionManagerImpl implements TransactionManager
// 此处 transactionHolder 用于将 Transaction 所代表的事务对象关联到线程上
private static ThreadLocal<TransactionImpl> transactionHolder
= new ThreadLocal<TransactionImpl>();
//TransacationMananger 必须维护一个全局对象,因此使用单实例模式实现
private static TransactionManagerImpl singleton = new TransactionManagerImpl();
private TransactionManagerImpl(){
}
public static TransactionManagerImpl singleton(){
return singleton;
}
public void begin() throws NotSupportedException, SystemException {
//XidImpl 实现了 Xid 接口,其作用是唯一标识一个事务
XidImpl xid = new XidImpl();
// 创建事务对象,并将对象关联到线程
TransactionImpl tx = new TransactionImpl(xid);
transactionHolder.set(tx);
}
由于Transaction 对象本身就代表了一个事务,在它被创建的时候就表明事务已经开始,因此也就不需要额外定义 begin() 方法了。
UserTransactionImpl implenments UserTransaction
public void commit() throws RollbackException, HeuristicMixedException,
HeuristicRollbackException, SecurityException,
IllegalStateException, SystemException {
// 检查是否是 Roll back only 事务,如果是回滚事务
if(rollBackOnly){
rollback();
return;
} else {
// 将提交事务的操作委托给 TransactionManagerImpl
TransactionManagerImpl.singleton().commit();
}
}
TransactionManagerImpl implenments TransactionManager
public void commit() throws RollbackException, HeuristicMixedException,
HeuristicRollbackException, SecurityException,
IllegalStateException, SystemException {
// 取得当前事务所关联的事务并通过其 commit 方法提交
TransactionImpl tx = transactionHolder.get();
tx.commit();
}
同理, rollback、getStatus、setRollbackOnly 等方法也采用了与 commit() 相同的方式实现。 UserTransaction 对象不会对事务进行任何控制, 所有的事务方法都是通过 TransactionManager 传递到实际的事务资源即 Transaction 对象上。
上述示例演示了 JTA 事务的处理过程,下面将为您展示事务资源(数据库连接,JMS)是如何以透明的方式加入到 JTA 事务中的。首先需要明确的一点是,在 JTA 事务 代码中获得的数据库源 ( DataSource ) 必须是支持分布式事务的。在如下的代码示例中,尽管所有的数据库操作都被包含在了 JTA 事务中,但是因为 MySql 的数据库连接是通过本地方式获得的,对 MySql 的任何更新将不会被自动包含在全局事务中。
public void transferAccount() {
UserTransaction userTx = null;
Connection mySqlConnection = null;
Statement mySqlStat = null;
Connection connB = null;
Statement stmtB = null;
try{
// 获得 Transaction 管理对象
userTx =
(UserTransaction)getContext().lookup("java:comp/UserTransaction");
// 以本地方式获得 mySql 数据库连接
mySqlConnection = DriverManager.getConnection("localhost:1111");
// 从数据库 B 中取得数据库连接, getDataSourceB 返回应用服务器的数据源
connB = getDataSourceB().getConnection();
// 启动事务
userTx.begin();
// 将 A 账户中的金额减少 500
//mySqlConnection 是从本地获得的数据库连接,不会被包含在全局事务中
mySqlStat = mySqlConnection.createStatement();
mySqlStat.execute("
update t_account set amount = amount - 500 where account_id = 'A'");
//connB 是从应用服务器得的数据库连接,会被包含在全局事务中
stmtB = connB.createStatement();
stmtB.execute("
update t_account set amount = amount + 500 where account_id = 'B'");
// 事务提交:connB 的操作被提交,mySqlConnection 的操作不会被提交
userTx.commit();
} catch(SQLException sqle){
// 处理异常代码
} catch(Exception ne){
e.printStackTrace();
}
}
关于JTA事务本文不再赘述,详细资料请看JTA 深度历险 - 原理与实现
支持两种事务声明方式分别是编程式事务与声明式事务,当然无论你选择上述何种事务方式去实现事务控制,spring都提供基于门面设计模式的事务管理器供选择,如下表
事务管理器实现(org.springframework.*) | 使用时机 |
---|---|
jdbc.datasource.DataSourceTransactionManager | 使用jdbc的抽象以及ibatis支持 |
orm.hibernate.HibernateTransactionManager | 使用hibernate支持(默认3.0以下版本) |
orm.hibernate3.HibernateTransactionManager | 使用hibernate3支持 |
transaction.jta.JtaTransactionManager | 使用分布式事务(分布式数据库支持) |
orm.jpa.JpaTransactionManager | 使用jpa做为持久化工具 |
orm.toplink.TopLinkTransactionManager | 使用TopLink持久化工具 |
orm.jdo.JdoTransactionManager | 使用Jdo持久化工具 |
jms.connection.JmsTransactionManager | 使用JMS 1.1+ |
jms.connection.JmsTransactionManager102 | 使用JMS 1.0.2 |
transaction.jta.OC4JJtaTransactionManager | 使用oracle的OC4J JEE容器 |
transaction.jta.WebLogicJtaTransactionManager | 在weblogic中使用分布式数据库 |
jca.cci.connection.CciLocalTransactionManager | 使用jrping对J2EE Connector Architecture (JCA)和Common Client Interface (CCI)的支持 |
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
hibernate的事务管理器会注入session会话工厂,然后将事务处理委托给当前的transaction对象,事务提交时,调用commit()方法,回滚时调用rollback()方法
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
spring特有的事务传播行为,spring支持7种事务传播行为,确定客户端和被调用端的事务边界(说得通俗一点就是多个具有事务控制的service的相互调用时所形成的复杂的事务边界控制)下图所示为7钟事务传播机制
传播行为 | 含义 |
---|---|
PROPAGATION_REQUIRED(XML文件中为REQUIRED) | 表示当前方法必须在一个具有事务的上下文中运行,如有客户端有事务在进行,那么被调用端将在该事务中运行,否则的话重新开启一个事务。(如果被调用端发生异常,那么调用端和被调用端事务都将回滚) |
PROPAGATION_SUPPORTS(XML文件中为SUPPORTS) | 表示当前方法不必需要具有一个事务上下文,但是如果有一个事务的话,它也可以在这个事务中运行 |
PROPAGATION_MANDATORY(XML文件中为MANDATORY) | 表示当前方法必须在一个事务中运行,如果没有事务,将抛出异常 |
PROPAGATION_NESTED(XML文件中为NESTED) | 表示如果当前方法正有一个事务在运行中,则该方法应该运行在一个嵌套事务中,被嵌套的事务可以独立于被封装的事务中进行提交或者回滚。如果封装事务存在,并且外层事务抛出异常回滚,那么内层事务必须回滚,反之,内层事务并不影响外层事务。如果封装事务不存在,则同PROPAGATION_REQUIRED的一样 |
PROPAGATION_NEVER(XML文件中为NEVER) | 表示当方法务不应该在一个事务中运行,如果存在一个事务,则抛出异常 |
PROPAGATION_REQUIRES_NEW(XML文件中为REQUIRES_NEW) | 表示当前方法必须运行在它自己的事务中。一个新的事务将启动,而且如果有一个现有的事务在运行的话,则这个方法将在运行期被挂起,直到新的事务提交或者回滚才恢复执行。 |
PROPAGATION_NOT_SUPPORTED(XML文件中为NOT_SUPPORTED) | 表示该方法不应该在一个事务中运行。如果有一个事务正在运行,他将在运行期被挂起,直到这个事务提交或者回滚才恢复执行 |
spring的事务隔离级别其实本质上是对SQL92标准的4种事务隔离级别的一种封装.
隔离级别 | 含义 |
---|---|
ISOLATION_DEFAULT | 使用数据库默认的事务隔离级别 |
ISOLATION_READ_UNCOMMITTED | 允许读取尚未提交的修改,可能导致脏读、幻读和不可重复读 |
ISOLATION_READ_COMMITTED | 允许从已经提交的事务读取,可防止脏读、但幻读,不可重复读仍然有可能发生 |
ISOLATION_REPEATABLE_READ | 对相同字段的多次读取的结果是一致的,除非数据被当前事务自生修改。可防止脏读和不可重复读,但幻读仍有可能发生 |
ISOLATION_SERIALIZABLE | 完全服从ACID隔离原则,确保不发生脏读、不可重复读、和幻读,但执行效率最低。 |
Spring配置文件中关于事务配置总是由三个组成部分,分别是DataSource、TransactionManager和代理机制这三部分,无论哪种配置方式,一般变化的只是代理机制这部分。
DataSource、TransactionManager这两部分只是会根据数据访问方式有所变化,比如使用Hibernate进行数据访问时,DataSource实际为SessionFactory,TransactionManager的实现为HibernateTransactionManager。
具体如下图:
<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>
<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="transactionBase"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
lazy-init="true" abstract="true">
<!-- 配置事务管理器 -->
<property name="transactionManager" ref="transactionManager" />
<!-- 配置事务属性 -->
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<!-- 配置DAO -->
<bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="userDao" parent="transactionBase" >
<property name="target" ref="userDaoTarget" />
</bean>
<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>
<context:annotation-config />
<context:component-scan base-package="com.bluesky" />
<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>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="interceptorPointCuts"
expression="execution(* com.bluesky.spring.dao.*.*(..))" />
<aop:advisor advice-ref="txAdvice"
pointcut-ref="interceptorPointCuts" />
</aop:config>
<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>