Spring 事务详解
一般地,用户的每次请求都对应一个业务逻辑方法,而一个业务逻辑方法往往包括一系列数据库原子访问操作,并且这些数据库原子访问操作应该绑定成一个事务来执行。然而,在使用传统的事务编程策略时,程序代码必然和具体的事务操作代码耦合,而使用Spring事务管理策略恰好可以避免这种尴尬。Spring的事务管理提供了两种方式:编程式事务管理
和声明式事务管理
。本文通过在对Spring事务管理API分析的基础上,详细地阐述了Spring编程式事务管理和声明式事务管理的原理、本质和使用。
一. Spring 事务概述
一般而言,用户的每次请求都对应一个业务逻辑方法,并且每个业务逻辑方法往往具有逻辑上的原子性。此外,一个业务逻辑方法往往包括一系列数据库原子访问操作,并且这些数据库原子访问操作应该绑定成一个整体,即要么全部执行,要么全部不执行,通过这种方式我们可以保证数据库的完整性,这就是事务。总的来说,事务是一个不可分割操作序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。
但是,在使用传统的事务编程策略时,程序代码必然和具体的事务操作代码耦合,如下所示:
// JDBC事务
Connection conn = getConnection();
conn.setAutoCommit(false);
...
// 业务实现
...
if 正常
conn.commit();
if 失败
conn.rollback();
// Hibernate事务
Session s = getSession();
Transaction tx = s.beginTransaction();
...
// 业务实现
...
if 正常
tx.commit();
if 失败
tx.rollback();
因此,当应用需要在不同的事务策略之间切换时,开发者必须手动修改程序代码。使用Spring事务管理策略,就可以避免这种尴尬。因为Spring的事务管理不需与任何特定的事务API耦合,并且其提供了两种事务管理方式:编程式事务管理和声明式事务管理。对不同的持久层访问技术,编程式事务提供一致的事务编程风格,通过模板化的操作一致性地管理事务;而声明式事务基于Spring AOP实现,却并不需要程序开发者成为AOP专家,亦可轻易使用Spring的声明式事务管理。
我们讲一个以前没讲过的小知识点,从JDBC3.0以后,引入了一个保存点特性,Savepoint接口允许将事务分割成多个阶段
,用户可以指定回滚到事务特定的保存点。而以前的版本中,只要回滚就只能回滚到开始事务的点,这个保存点特性可以解决很多实际问题,在发生特定问题时,回滚到指定的保存点,我们来演示一下。
Connection conn;
Savepoint s;
try{
conn=DriverManager.getConnection();
//开启事务
conn.setAutoCommit(false);
执行A操作;
执行B操作;
//设置保存点
s=conn.setSavepoint("savapoint");
执行C操作
执行D操作
//提交事务
conn.commit();
}catch(Exception e){
//判断是在AB操作时发生异常还是CD操作时发生异常
if(s!=null){
//回滚到保存点,并提交保存点之前的事务
conn.rollback(s);
conn.commit();
}else{
conn.rollback();
}
}finally{
....
}
二、Spring事务管理介绍
Spring为事务管理提供了一致的编程模板,不管选择Spring JDBC
、Hibernate
、Mybatis
,Spring都让我们可以用统一的编程模板进行事务管理。
Spring为我们提供了事务模板类,通过事务模板类就可以通过编程方式实现事务管理,而无需关注资源获取、释放、异常处理等操作。
Spring事务管理的亮点在于声明式事务管理,Spring允许通过声明方式,在IoC配置中指定事务的边界和事务属性,Spring自动在指定的事务边界上应用事务属性。
Spring事务管理主要包括三个接口:
-
PlatformTransactionManager: 事务管理器。Spring使用事务管理器来管理事务的提交回滚等。
-
TransactionDefinition:事务属性。用来描述事务的隔离级别、超时时间、是否为只读事务等事务属性。我们配置事务属性,Spring会将事务封装到该对象实例。
-
TransactionStatus:事务状态。用来记录事务的具体运行状态。例如事务是否有保存点,事务是否结束等。
PlatformTransactionManager
PlatformTransactionManager定义了三个接口方法:
-
TransactionStatus getTransaction(TransactionDefinition definition):该方法通过事务详情属性获得一个事务,并用TransactionStatus描述这个事务的状态
-
commit(TransactionStatus status):根据事务的状态提交事务,如果事务状态已经被标识为rollback-only,该方法执行回滚事务的操作
-
rollback(TransactionStatus status):根据状态回滚事务,commit()方法抛出异常时,该方法会被隐式调用
Spring为不同的持久化框架提供了PlatformTransactionManager接口的实现类
:
可以看出,PlatformTransactionManager是一个与任何事务策略分离的接口。PlatformTransactionManager接口有许多不同的实现类,应用程序面向与平台无关的接口编程,而对不同平台的底层支持由PlatformTransactionManager接口的实现类完成,故而应用程序无须与具体的事务API耦合。因此使用PlatformTransactionManager接口,可将代码从具体的事务API中解耦出来。
在PlatformTransactionManager接口内,包含一个getTransaction(TransactionDefinition definition)
方法,该方法根据一个TransactionDefinition
参数,返回一个TransactionStatus
对象。TransactionStatus对象表示一个事务,该事务可能是一个新的事务,也可能是一个已经存在的事务对象,这由TransactionDefinition所定义的事务规则所决定。
TransactionStatus详解
TransactionStatus代表一个事务的具体运行状态。事务管理器通过该接口获取事务的运行期的状态信息。
该接口继承SavepointManager接口。
SavepointManager接口
拥有以下的方法:
Object createSavepoint():创建一个保存点对象。
void rollbackToSavepoint(Object savepoint):回滚到指定的保存点
void releaseSavepoint(Object savepoint):释放一个保存点。如果事务提交,所有保存点会自动释放。
TransactionStatus
扩展了SavepointManager并提供了以下的方法
boolean hasSavepoint():判断当前的事务是否有保存点。
boolean isNewTransaction():判断当前的事务是否是一个新的事务。
boolean isCompleted():判断当前事务是否已经结束:已经提交或者回滚
boolean isRollbackOnly():判断当前事务是否已经被标识为rollback-only
void setRollbackOnly():将当前事务设置为rollback-only,通过该标识通知事务管理器只能进行事务回滚。
TransactionDefinition 接口
TransactionDefinition 接口用于定义一个事务的规则,它包含了事务的一些静态属性,比如:事务传播行为、超时时间等。同时,Spring 还为我们提供了一个默认的实现类:DefaultTransactionDefinition,该类适用于大多数情况。如果该类不能满足需求,可以通过实现 TransactionDefinition 接口来实现自己的事务定义。
TransactionDefinition接口包含与事务属性相关的方法,如下所示:
public interface TransactionDefinition{
int getIsolationLevel();
int getPropagationBehavior();
int getTimeout();
boolean isReadOnly();
}
TransactionDefinition 接口只提供了获取属性的方法,而没有提供相关设置属性的方法。因为,事务属性的设置完全是程序员控制
的,因此程序员可以自定义任何设置属性的方法,而且保存属性的字段也没有任何要求。唯一的要求的是,Spring 进行事务操作的时候,通过调用以上接口提供的方法必须能够返回事务相关的属性取值。例如,TransactionDefinition 接口的默认的实现类 —— DefaultTransactionDefinition
就同时定义了一系列属性设置和获取方法。
TransactionDefinition 接口定义的事务规则包括:事务隔离级别
、事务传播行为
、事务超时
、事务的只读属性
和事务的回滚规则
,下面我们一一详细介绍。
(1)事务隔离级别
所谓事务的隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量:
-
TransactionDefinition.ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,该级别就是 TransactionDefinition.ISOLATION_READ_COMMITTED;
-
TransactionDefinition.ISOLATION_READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据,该级别不能防止脏读和不可重复读,因此很少使用该隔离级别;
-
TransactionDefinition.ISOLATION_READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
-
TransactionDefinition.ISOLATION_REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。
-
TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是,这将严重影响程序的性能,通常情况下也不会用到该级别。
(2)事务传播行为
所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。TransactionDefinition接口定义了如下几个表示传播行为的常量:
- TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
- TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
- TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
这里需要指出的是,以 PROPAGATION_NESTED
启动的事务内嵌于外部事务中(如果存在外部事务的话),此时,内嵌事务并不是一个独立的事务,它依赖于外部事务的存在,只有通过外部的事务提交,才能引起内部事务的提交,嵌套的子事务不能单独提交。另外,外部事务的回滚也会导致嵌套子事务的回滚。
(3)事务超时
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。
(4)事务的只读属性
事务的只读属性是指,对事务性资源进行只读操作或者是读写操作。所谓事务性资源就是指那些被事务管理的资源,比如数据源、 JMS 资源,以及自定义的事务性资源等等。如果确定只对事务性资源进行只读操作,那么我们可以将事务标志为只读的,以提高事务处理的性能。在 TransactionDefinition接口中,以 boolean 类型来表示该事务是否只读。
(5)事务的回滚规则
通常情况下,如果在事务中抛出了未检查异常(继承自 RuntimeException 的异常),则默认将回滚事务。如果没有抛出任何异常,或者抛出了已检查异常,则仍然提交事务。这通常也是大多数开发者希望的处理方式,也是 EJB 中的默认处理方式。但是,我们可以根据需要人为控制事务在抛出某些未检查异常时任然提交事务,或者在抛出某些已检查异常时回滚事务。
三、Spring 编程式事务管理
在 Spring 出现以前,编程式事务管理对基于 POJO 的应用来说是唯一选择。用过 Hibernate 的人都知道,我们需要在代码中显式调用beginTransaction()
、commit()
、rollback()
等事务管理相关的方法,这就是编程式事务管理。通过 Spring 提供的事务管理 API,我们可以在代码中灵活控制事务的执行。在底层,Spring 仍然将事务操作委托给底层的持久化框架来执行。
1、基于底层 API 的编程式事务管理
基于PlatformTransactionManager
、TransactionDefinition
和 TransactionStatus
三个核心接口,我们完全可以通过编程的方式来进行事务管理。下面给出一个基于底层 API 的编程式事务管理的示例,
public class BankServiceImpl implements BankService {
private BankDao bankDao;
private TransactionDefinition txDefinition;
private PlatformTransactionManager txManager;
......
public boolean transfer(Long fromId, Long toId, double amount) {
// 获取一个事务
TransactionStatus txStatus = txManager.getTransaction(txDefinition);
boolean result = false;
try {
result = bankDao.transfer(fromId, toId, amount);
txManager.commit(txStatus); // 事务提交
} catch (Exception e) {
result = false;
txManager.rollback(txStatus); // 事务回滚
System.out.println("Transfer Error!");
}
return result;
}
}
相应的配置文件如下所示:
<bean id="bankService" class="footmark.spring.core.tx.programmatic.origin.BankServiceImpl">
<property name="bankDao" ref="bankDao"/>
<property name="txManager" ref="transactionManager"/>
<property name="txDefinition">
<bean class="org.springframework.transaction.support.DefaultTransactionDefinition">
<property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/>
</bean>
</property>
</bean>
如上所示,我们在BankServiceImpl类中增加了两个属性:一个是 TransactionDefinition 类型的属性,它用于定义事务的规则;另一个是 PlatformTransactionManager 类型的属性,用于执行事务管理操作。如果一个业务方法需要添加事务,我们首先需要在方法开始执行前调用PlatformTransactionManager.getTransaction(…) 方法启动一个事务;创建并启动了事务之后,便可以开始编写业务逻辑代码,然后在适当的地方执行事务的提交或者回滚。
2、基于 TransactionTemplate 的编程式事务管理
当然,除了可以使用基于底层 API 的编程式事务外,还可以使用基于 TransactionTemplate 的编程式事务管理。通过上面的示例可以发现,上述事务管理的代码散落在业务逻辑代码中,破坏了原有代码的条理性,并且每一个业务方法都包含了类似的启动事务、提交/回滚事务的样板代码。Spring 也意识到了这些,并提供了简化的方法,这就是 Spring 在数据访问层非常常见的 模板回调模式。
TracsactionTemplate有两个主要的方法
-
void setTransactionManager(PlatformTransactionManager transactionManager):设置事务管理器
-
Object execute(TransactionCallback action):在TransactionCallback回调接口中定义需要以事务方式组织的数据访问逻辑。简单的说需要以事务方式处理的代码放在这里面。
TransactionCallback接口中只有一个方法:
- Object doInTransaction(TransactionStatus status),如果操作不会返回任何结果,可以使用TransactionCallback的子接口TransactionCallbackWithoutResult.
我们来演示一个银行转账的例子。
//先创建数据库及要操作的数据表
CREATE DATABASE bank;
USE bank;
CREATE TABLE account(
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50),
money INT
);
INSERT INTO account(username,money) VALUES('张三',1000);
INSERT INTO account(username,money) VALUES('李四',1000);
//创建Dao层,Dao层应该有两个方法,一个是给收款人加钱,一个给付款人减钱
public class BankDao extends JdbcDaoSupport{
//给收款人加钱的方法
public void addMoney(String payee,Integer money){
String sql="update account set money=money+? where username=?";
this.getJdbcTemplate().update(sql, money,payee);
}
//给付款人减钱的方法
public void reduceMoney(String payer,Integer money){
String sql="update account set money=money-? where username=?";
this.getJdbcTemplate().update(sql, money,payer);
}
}
//在Service层使用编程式事务进行转账操作
public class BankService {
private BankDao dao;
private TransactionTemplate template;
public void setDao(BankDao dao) {
this.dao = dao;
}
public void setTemplate(TransactionTemplate template) {
this.template = template;
}
//转账方法
public void transfer(String payee,String payer,Integer money){
//因为没有返回结果,将转账的具体逻辑放在TransactionCallbackWithoutResult接口中
template.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
dao.addMoney(payee, money);
dao.reduceMoney(payer, money);
}
});
}
}
<!--编写配置文件-->
<?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.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--加载数据库配置文件-->
<context:property-placeholder location="jdbc.properties"/>
<!--设置连接池配置-->
<bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driverClass}"></property>
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
<!--实例TransactionTemplate模板类,需要一个事务管理器-->
<bean id="template" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"></property>
</bean>
<!--实例事务管理器,需要一个数据源-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource"></property>
</bean>
<!--实例bankDao,需要数据源-->
<bean id="bankDao" class="com.spring.transa.BankDao">
<property name="dataSource" ref="datasource"></property>
</bean>
<!--实例bankService,需要注入两个参数-->
<bean id="bankService" class="com.spring.transa.BankService">
<property name="template" ref="template"></property>
<property name="dao" ref="bankDao"></property>
</bean>
</beans>
//测试一下
public class TestDemo {
@Test
public void test(){
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
BankService service=(BankService) ac.getBean("bankService");
service.transfer("张三", "李四", 200);
}
}
TransactionTemplate 的 execute() 方法有一个 TransactionCallback 类型的参数,该接口中定义了一个 doInTransaction() 方法,通常我们以匿名内部类的方式实现 TransactionCallback 接口,并在其 doInTransaction() 方法中书写业务逻辑代码
。这里可以使用默认的事务提交和回滚规则,这样在业务代码中就不需要显式调用任何事务管理的 API。doInTransaction() 方法有一个TransactionStatus 类型的参数,我们可以在方法的任何位置调用该参数的 setRollbackOnly() 方法将事务标识为回滚的,以执行事务回滚。
此外,TransactionCallback 接口有一个子接口 TransactionCallbackWithoutResult
,该接口中定义了一个 doInTransactionWithoutResult() 方法
,TransactionCallbackWithoutResult 接口主要用于事务过程中不需要返回值的情况。当然,对于不需要返回值的情况,我们仍然可以使用 TransactionCallback 接口,并在方法中返回任意值即可。
四、Spring 声明式事务管理
Spring 的声明式事务管理是建立在Spring AOP
机制之上的,其本质是对目标方法前后进行拦截,并在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中作相关的事务规则声明(或通过等价的基于标注的方式),便可以将事务规则应用到业务逻辑中。总的来说,声明式事务得益于 Spring IoC容器
和 Spring AOP 机制
的支持:IoC容器为声明式事务管理提供了基础设施,使得 Bean 对于 Spring 框架而言是可管理的;而由于事务管理本身就是一个典型的横切逻辑(正是 AOP 的用武之地),因此 Spring AOP 机制是声明式事务管理的直接实现者。
显然,声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要在XML文件中配置或者添加注解就可以获得完全的事务支持。因此,通常情况下,笔者强烈建议在开发中使用声明式事务,不仅因为其简单,更主要是因为这样使得纯业务代码不被污染,极大方便后期的代码维护。
1、使用XML配置声明式事务
使用原始的TransactionProxyFactoryBean
,Spring早期版本中,用户必须通过TransactionProxyFactoryBean代理类对需要进行事务管理的业务类进行代理,在Spring3.0以后,这种方式已经不被推荐,但是我们可以了解一下,有助于我们理解Spring声明式事务的内部工作原理。
//我们还使用上面的转账例子
public class BankService {
private BankDao dao;
public void setDao(BankDao dao) {
this.dao = dao;
}
public void transfer(String payee,String payer,Integer money){
dao.addMoney(payee, money);
//我们故意让抛出一个异常
int i=1/0;
dao.reduceMoney(payer, money);
}
}
<!--使用TransactionProxyFactoryBean配置-->
<?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.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--加载数据库配置文件-->
<context:property-placeholder location="jdbc.properties"/>
<bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driverClass}"></property>
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource"></property>
</bean>
<!--配置Dao-->
<bean id="bankDao" class="com.spring.transa.BankDao">
<property name="dataSource" ref="datasource"></property>
</bean>
<!--配置Service-->
<bean id="bankService" class="com.spring.transa.BankService">
<property name="dao" ref="bankDao"></property>
</bean>
<!--配置代理类-->
<bean id="proxyBankService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!--目标类-->
<property name="target" ref="bankService"></property>
<!--事务管理器-->
<property name="transactionManager" ref="transactionManager"></property>
<!--事务属性,通过键值对的方式配置事务属性信息-->
<property name="transactionAttributes">
<props>
<!--key为要进行事务管理的方法名,可以用*匹配所有方法-->
<!--Value为属性值,格式为PROPAGATION(传播行为),ISOLATION(隔离级别 可选),readOnly(是否只读 可选),-Exceptions(发生这些异常回滚事务 可选),+Exceptions(发生这些异常照样提交事务 可选)-->
<prop key="transfer">PROPAGATION_REQUIRED,+Exception</prop>
</props>
</property>
</bean>
</beans>
//进行测试
public class TestDemo {
@Test
public void test(){
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
BankService service=(BankService) ac.getBean("proxyBankService");
service.transfer("张三", "李四", 200);
}
}
我们可以看到,虽然出现了异常,但还是提交了事务。这就是我们设置的+Exception的作用
2、基于tx/aop命名空间的配置
使用TransactionProxyFactoryBean代理工厂类
为业务类添加事务支持,有一些很明显的缺点,例如需要对每一个需要事务支持的业务类进行单独配置等。这一切的缺点是因为低版本Spring中没有引入强大的AOP切面表达式造成的,Spring引入AspectJ后,这一切自然就迎刃而解。
Spring在XML配置中,提供了tx命名空间,以明确的结构方式定义事务相关信息,配置aop提供的切面定义,事务配置得到了大大的简化。这也是开发中最常用的事务管理的方法
//我们还使用前面的银行转账的例子,都不需要改变
//注意的是配置文件需要引入tx和aop命名空间
<?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" //引入tx命名空间
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
//指定tx xsd文件位置
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:property-placeholder location="jdbc.properties"/>
<bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driverClass}"></property>
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
<!--定义事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource"></property>
</bean>
<!--定义Dao-->
<bean id="bankDao" class="com.spring.transa.BankDao">
<property name="dataSource" ref="datasource"></property>
</bean>
<!--定义Service-->
<bean id="bankService" class="com.spring.transa.BankService">
<property name="dao" ref="bankDao"></property>
</bean>
<!--定义事务增强,需要指定id和事务管理器-->
<tx:advice id="txadvice" transaction-manager="transactionManager">
<!--配置事务属性-->
<tx:attributes>
<tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT"/>
</tx:attributes>
</tx:advice>
<!--定义aop切面,使用AspectJ表达式定义切点-->
<aop:config>
<aop:advisor pointcut="execution(* com.spring.transa.BankService.*(..))" advice-ref="txadvice"/>
</aop:config>
</beans>
//我们测试一下
public class TestDemo {
@Test
public void test(){
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
BankService service=(BankService) ac.getBean("bankService");
service.transfer("张三", "李四", 200);
}
}
事实上,Spring配置文件中关于事务的配置总是由三个部分组成,即:DataSource
、TransactionManager
和代理机制
三部分,无论哪种配置方式,一般变化的只是代理机制这部分。其中,DataSource、TransactionManager这两部分只是会根据数据访问方式有所变化,比如使用hibernate进行数据访问时,DataSource实际为SessionFactory,TransactionManager的实现为 HibernateTransactionManager。如下图所示:
3、使用注解配置声明式事务
除了基于XML的事务配置之外,Spring还提供了基于注解的事务配置,使用@Transactional
对需要使用注解的类或方法进行标注,在容器中配置基于注解的事务增强驱动,即可启用基于注解的声明式事务。
@Transactional 可以作用于接口、接口方法、类以及类方法上:当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性;当作用于方法上时,该标注来覆盖类级别的定义。
//对整个类使用注解,也可以对单独的方法使用注解
//一般来说,我们只需要使用默认的事务属性即可,如果需要,直接使用以下格式
//@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT)
@Transactional
public class BankService {
private BankDao dao;
public void setDao(BankDao dao) {
this.dao = dao;
}
public void transfer(String payee,String payer,Integer money){
dao.addMoney(payee, money);
int i=1/0;
dao.reduceMoney(payer, money);
}
}
<?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.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:property-placeholder location="jdbc.properties"/>
<bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driverClass}"></property>
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource"></property>
</bean>
<bean id="bankDao" class="com.spring.transa.BankDao">
<property name="dataSource" ref="datasource"></property>
</bean>
<bean id="bankService" class="com.spring.transa.BankService">
<property name="dao" ref="bankDao"></property>
</bean>
<!--注解驱动,对添加@Transactional注解的Bean织入事务管理,peoxy-target-class属性如果为true,使用CGLib-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
4、Spring 声明式事务的本质
就Spring 声明式事务而言,无论其基于 XML配置文件、 命名空间还是基于 @Transactional 的实现,其本质都是 Spring AOP 机制的应用:即通过以@Transactional的方式或者XML配置文件的方式向业务组件中的目标业务方法插入事务增强处理并生成相应的代理对象供应用程序(客户端)使用从而达到无污染地添加事务的目的。如下图所示: