一、编程式事务
- 直接使用PlatformTransactionManager来进行编程式事务管理
- 使用框架底层API(JJDBC,Session)来进行事务控制
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>
事务管理的代码散落在业务逻辑代码中,破坏了原有代码的条理性,并且每一个业务方法都包含了类似的启动事务、提交/回滚事务的样板代码。
- 直接使用TransactionTemplate来进行编程式事务管理
public class BankServiceImpl implements BankService {
private BankDao bankDao;
private TransactionTemplate transactionTemplate;
......
public boolean transfer(final Long fromId, final Long toId, final double amount) {
return (Boolean) transactionTemplate.execute(new TransactionCallback(){
public Object doInTransaction(TransactionStatus status) {
Object result;
try {
result = bankDao.transfer(fromId, toId, amount);
} catch (Exception e) {
status.setRollbackOnly();
result = false;
System.out.println("Transfer Error!");
}
return result;
}
});
}
}
相应的XML配置如下:
清单 7. 基于 TransactionTemplate 的事务管理示例配置文件
<bean id="bankService"
class="footmark.spring.core.tx.programmatic.template.BankServiceImpl">
<property name="bankDao" ref="bankDao"/>
<property name="transactionTemplate" ref="transactionTemplate"/>
</bean>
- 创建基于SavePoint的嵌套事务
二、声明式事务
1.配置方式
- 注解元数据驱动的声明式事务(@Transactional)
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 启用事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
在需要使用事务的类上添加注解即可 @Transactional
@Transactional(propagation = Propagation.REQUIRED)
public boolean transfer(Long fromId, Long toId, double amount) {
return bankDao.transfer(fromId, toId, amount);
}
propagation:事务传播行为,
isolation:事务隔离级别定义;默认为“DEFAULT”
timeout:事务超时时间设置,单位为秒,默认-1,表示事务超时将依赖于底层事务系统;
readonly:事务只读设置,默认为false,表示不是只读;
rollbackfor:需要触发回滚的异常定义,可定义多个,以“,”分割,默认任何RuntimeException都将导致事务回滚,而任何Checked Exception将不导致事务回滚;
norollbackfor:不被触发进行回滚的 Exception(s);可定义多个,以“,”分割;
@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该注解来覆盖类级别的定义。
虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。
- XML元数据驱动的声明式事务
基于 TransactionInterceptor和ProxyFactoryBean 的事务管理
<beans>
......
<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="transfer">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<bean id="bankServiceTarget"
class="footmark.spring.core.tx.declare.origin.BankServiceImpl">
<property name="bankDao" ref="bankDao"/>
</bean>
<bean id="bankService"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="bankServiceTarget"/>
<property name="interceptorNames">
<list>
<idref bean="transactionInterceptor"/>
</list>
</property>
</bean>
......
</beans>
TransactionInterceptor的两个主要属性
1)transactionManager(指定一个事务管理器,并将具体事务相关的操作委托给它)
2)Properties 类型的 transactionAttributes 属性来定义事务规则,该属性的每一个键值对中,键指定的是方法名,方法名可以使用通配符,而值就表示相应方法的所应用的事务属性。事务的规则(传播行为 [,隔离级别] [,只读属性] [,超时属性] [不影响提交的异常] [,导致回滚的异常])。传播行为是唯一必须设置的属性,其他都可以忽略,Spring为我们提供了合理的默认值。传播行为的取值必须以“PROPAGATION_”开头,具体包括:PROPAGATION_MANDATORY、PROPAGATION_NESTED、PROPAGATION_NEVER、PROPAGATION_NOT_SUPPORTED、PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_SUPPORTS,共七种取值。隔离级别的取值必须以“ISOLATION_”开头,具体包括:ISOLATION_DEFAULT、ISOLATION_READ_COMMITTED、ISOLATION_READ_UNCOMMITTED、ISOLATION_REPEATABLE_READ、ISOLATION_SERIALIZABLE,共五种取值。
如果事务是只读的,那么我们可以指定只读属性,使用“readOnly”指定。否则我们不需要设置该属性。超时属性的取值必须以“TIMEOUT_”开头,后面跟一个int类型的值,表示超时时间,单位是秒。不影响提交的异常是指,即使事务中抛出了这些类型的异常,事务仍然正常提交。必须在每一个异常的名字前面加上“+”。异常的名字可以是类名的一部分。比如“+RuntimeException”、“+tion”等等。导致回滚的异常是指,当事务中抛出这些类型的异常时,事务将回滚。必须在每一个异常的名字前面加上“-”。异常的名字可以是类名的全部或者部分,比如“-RuntimeException”、“-tion”等等。
示例:
<property name="*Service">
PROPAGATION_REQUIRED,ISOLATION_READ_COMMITTED,TIMEOUT_20,
+AbcException,+DefException,-HijException
</property>
针对所有方法名以 Service 结尾的方法,使用 PROPAGATION_REQUIRED 事务传播行为,事务的隔离级别是ISOLATION_READ_COMMITTED,超时时间为20秒,当事务抛出 AbcException 或者 DefException类型的异常,则仍然提交,当抛出 HijException 类型的异常时必须回滚事务。这里没有指定"readOnly",表示事务不是只读的。
<property name="test">PROPAGATION_REQUIRED,readOnly</property>
针对所有方法名为 test 的方法,使用 PROPAGATION_REQUIRED事务传播行为,并且该事务是只读的。除此之外,其他的属性均使用默认值。比如,隔离级别和超时时间使用底层事务性资源的默认值,并且当发生未检查异常,则回滚事务,发生已检查异常则仍提交事务。
配置好了 TransactionInterceptor,我们还需要配置一个 ProxyFactoryBean 来组装 target和advice。这也是典型的 Spring AOP 的做法。通过 ProxyFactoryBean生成的代理类就是织入了事务管理逻辑后的目标类。
配置文件太多。我们必须针对每一个目标对象配置一个 ProxyFactoryBean;另外,虽然可以通过父子 Bean 的方式来复用TransactionInterceptor 的配置,但是实际的复用几率也不高;这样,加上目标对象本身,每一个业务类可能需要对应三个<bean/> 配置,随着业务类的增多,配置文件将会变得越来越庞大,管理配置文件又成了问题。
基于TransactionProxyFactoryBean的配置 (Spring 经典的声明式事务管理)(TransactionInterceptor 和 ProxyFactoryBean 的配置合二为一)
<beans>
......
<bean id="bankServiceTarget"
class="footmark.spring.core.tx.declare.classic.BankServiceImpl">
<property name="bankDao" ref="bankDao"/>
</bean>
<bean id="bankService"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="target" ref="bankServiceTarget"/>
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="transfer">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
......
</beans>
基于<tx/>命名空间的配置
<beans>
......
<bean id="bankService"
class="footmark.spring.core.tx.declare.namespace.BankServiceImpl">
<property name="bankDao" ref="bankDao"/>
</bean>
<tx:advice id="bankAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="transfer" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="bankPointcut" expression="execution(* *.transfer(..))"/>
<aop:advisor advice-ref="bankAdvice" pointcut-ref="bankPointcut"/>
</aop:config>
......
</beans>
<tx:advice>定义事务通知,用于指定事务属性,其中“transaction-manager”属性指定事务管理器,并通过<tx:attributes>指定具体需要拦截的方法<tx:method>拦截方法,其中参数有:
name:方法名称,将匹配的方法注入事务管理,可用通配符
propagation:事务传播行为,
isolation:事务隔离级别定义;默认为“DEFAULT”
timeout:事务超时时间设置,单位为秒,默认-1,表示事务超时将依赖于底层事务系统;
read-only:事务只读设置,默认为false,表示不是只读;
rollback-for:需要触发回滚的异常定义,可定义多个,以“,”分割,默认任何RuntimeException都将导致事务回滚,而任何Checked Exception将不导致事务回滚;
no-rollback-for:不被触发进行回滚的 Exception(s);可定义多个,以“,”分割;
<!-- 定义切入点,expression为切入点表达式,如下是指定impl包下的所有方法,具体以自身实际要求自定义 -->
<aop:config>
<aop:pointcut expression="execution(* com.kaizhi.*.service.impl.*.*(..))" id="pointcut"/>
<!--<aop:advisor>定义切入点,与通知,把tx与aop的配置关联,才是完整的声明事务配置 -->
<aop:advisor advice-ref="advice" pointcut-ref="pointcut"/>
</aop:config>
如果配置的事务管理器 Bean 的名字取值为“transactionManager”,则我们可以省略 <tx:advice> 的 transaction-manager 属性,因为该属性的默认值即为“transactionManager”。利用切点表达式,一个配置可以匹配多个方法。
基于BeanNameAutoProxyCreator的配置
<!-- 配置事务拦截器-->
<bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
<!-- 事务拦截器bean需要依赖注入一个事务管理器 -->
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<!-- 下面定义事务传播属性-->
<props>
<prop key="insert*">PROPAGATION_REQUIRED</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<!-- 定义BeanNameAutoProxyCreator,该bean是个bean后处理器,无需被引用,因此没有id属性 这个bean后处理器,根据事务拦截器为目标bean自动创建事务代理
指定对满足哪些bean name的bean自动生成业务代理 -->
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<!-- 下面是所有需要自动创建事务代理的bean-->
<list>
<value>personDao</value>
</list>
<!-- 此处可增加其他需要自动创建事务代理的bean-->
</property>
<!-- 下面定义BeanNameAutoProxyCreator所需的事务拦截器-->
<property name="interceptorNames">
<list>
<value>transactionInterceptor</value>
<!-- 此处可增加其他新的Interceptor -->
</list>
</property>
</bean>
它是个bean后处理器,BeanNameAutoProxyCreator是个根据bean名生成自动代理的代理创建器(可以跟自己创建的拦截器一起用)。MethodInterceptor 接口,在调用目标对象的方法时,就可以实现在调用方法之前、调用方法过程中、调用方法之后对其进行控制。
public static class adviseMethodInterceptor implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
Object result=null;
try{
System.out.println("方法执行之前:"+methodInvocation.getMethod().toString());
result= methodInvocation.proceed();
System.out.println("方法执行之后:"+methodInvocation.getMethod().toString());
System.out.println("方法正常运行结果:"+result);
return result;
}catch (Exception e){
System.out.println("方法出现异常:"+e.toString());
System.out.println("方法运行Exception结果:"+result);
return result;
}
}
}
ProxyFactory proxyFactory=new ProxyFactory();//创建一个代理对象
proxyFactory.setTarget(new TestMethodInterceptor());//设置代理对象的目标对象
proxyFactory.addAdvice(new adviseMethodInterceptor());//设置代理对象的方法拦截器
Object proxy = proxyFactory.getProxy();//得到生成的代理对象
TestMethodInterceptor methodInterceptor = (TestMethodInterceptor) proxy;
methodInterceptor.doSomeThing("通过代理工厂设置代理对象,拦截代理方法");//调用方法
<bean id="adviseMethodInterceptor" class="com.i2p.admin.interceptor.adviseMethodInterceptor" />
<!-- 根据 Bean 的名字自动实现代理拦截 -->
<bean
class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="interceptorNames">
<list>
<value>methodTimeAdvice</value>
</list>
</property>
<property name="beanNames">
<list>
<!-- 添加到其中的 Bean 自动就被代理拦截了(下面的是对应的bean的名称,那么当请求InnovatePartBorrowController任何一个方法都会执行MethodTimeAdvice里面的Invoke方法) -->
<value>innovatePartBorrowController</value>
</list>
</property>
</bean>
参数:1)beanNames:设置哪些bean需要自动生成代理。2)interceptorNames:指定事务拦截器,自动创建事务代理时,系统会根据这些事务拦截器的属性来生成对应的事务代理。
定义了两个org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator,按照顺序分别计作A,B,A会为目标对象T生成代理对象P1,B会为P1生成代理对象P2,代理逻辑就变成:P2代理P1,P1代理T,而不是P2、P1都代理T。
所以,调用T的方法时,应该先执行P1,但P2代理了P1,最终先执行P2,再执行P1,在执行T。这种情况下,如果T的方法上有注解,只有P1能够拿到注解信息,P2是拿不到的。
所以,不要定义个多个org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator的bean。
事务的实现原理:建立在 AOP 的基础之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。它将事务管理代码从业务方法中分离出来, 以声明的方式来实现事务管理.事务管理作为一种横切关注点,可以通过AOP方法模块化.Spring 通过 SpringAOP 框架支持声明式事务管理。
三、编程式事物与声明式事物的不同点
Spring的编程式事务即在代码中使用编程的方式进行事务处理,可以做到比声明式事务更细粒度。有两种方式一是使用TransactionManager,另外就是TransactionTemplate。
Spring的声明式事务管理在底层是建立在AOP的基础之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过等价的基于标注的方式),便可以将事务规则应用到业务逻辑中。因为事务管理本身就是一个典型的横切逻辑,正是AOP的用武之地。依赖注入容器为声明式事务管理提供了基础设施,使得Bean对于Spring框架而言是可管理的;而Spring AOP则是声明式事务管理的直接实现者。和编程式事务相比,声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。