终于到关键的spring声明式事务管理了,大多数Spring用户选择声明式事务管理。因为它是对业务代码侵入性最小的选择,也最符合spring所倡导的非侵入式 轻量级容器的理念。这里分别讲解spring基于注解的和基于aop的事务处理。这篇文章暂时只讲解传统的基于Aop的声明式事务管理,即使用代理工厂类去实现业务类的事务管理支持,相比tx/aop命名空间的方式,虽然有很多缺点,但是它更能直观让我们看到Spring实施事务管理的内在工作原理,下面的代码分别讲解了基于注解的声明式事务管理,和基于Aop的事务管理
public class TicketBookingServiceThroughAnnotation extends JdbcDaoSupport implements BookingService{ //这里对将要进行事务增强的类进行标注 @Transactional public void bookTicket(int userId, int ticketId, int noOfTickets){ int accountId = TicketUtils.getAccountId(getJdbcTemplate(), userId); float ticketCost = TicketUtils.findTicketCost(getJdbcTemplate(), ticketId); float totalCost = (noOfTickets * ticketCost); TicketUtils.deductMoneyFromAccount(getJdbcTemplate(), accountId, totalCost); TicketUtils.reduceTicketCount(getJdbcTemplate(), ticketId, noOfTickets); } }
上边的注解@Transactional可以被应用于方法或者类级别,如果应用到类级别,所有的在这个类中的public方法将继承他,值得注意的是也可以配置事务的隔离级别和传播特性,如果没有显示的去定义默认的隔离级别为REQUIRED,它将使用数据库的隔离级别,请注意上边的类继承自 JdbcDaoSupport,所以当我们创建一个实例的时候,JdbcTemplate对象必须已经被设定过,如果我们在某一个类的某一个方法上不想使用类提供的事务管理的规则,那我们可以在具体的方法定义新的规则,他将覆盖类的事务属性
public class Main { public static void main(String[] args) { ApplicationContext context = Utils.getContext("main-annotation.xml"); BookingService service = (BookingService)context.getBean("ticketBookingServiceThroughAnnotation"); service.bookTicket(1, 1, 1); }}
注意客户端程序引用如下的配置文件main-annotation.xml
<?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:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx.xsd">
<!--对标注Ttransactional注解的Bean进行加工处理,以植入事务管理的切面--> <tx:annotation-driven transaction-manager="transactionManager" /> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="myOralceDataSource" /> </bean> <bean id="myOralceDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" destroy-method="close"> <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" /> <property name="url" value="jdbc:oracle:thin:@localhost:1521:orcl" /> <property name="username" value="sniper" /> <property name="password" value="123" /> </bean>
<!--因为该Bean实现类标注了@Transactional,所以会被上面的注解驱动自动织入事务--> <bean id="ticketBookingServiceThroughAnnotation" class="com.sniper.springtm.ticbook.annotation.TicketBookingServiceThroughAnnotation"> <property name="dataSource" ref="myOralceDataSource" /> </bean></beans>
注请注意只是使用 @Transactional 注解并不会启用事务行为, 它仅是一种元数据,能够被可以识别 @Transactional 注解和上述的配置适当的具有事务行为的beans所使用。因此我们在Spring的配置文件通过一个小小的配置通过Spring容器对标识@transactional的Bean进行加工和处理,上面的例子中实质上是<tx:annotation-driven/>元素的出现 开启了事务行为。
使用基于注解的方式进行事务管理的配置时,有几点是我们注意的,虽然@Transactional可以被应用于接口定义,接口方法、类定义和类的方法上,但是注解的方式不能被继承,也就是说我们在接口上标注的@Transactional注解不会被实现的业务类所继承,如果用下面的方式去启用子类代理
<tx:anotaion-driver transaction = "txMnager" prox-target-class = "true" />
这样的情况下子类依然不会进行事务的处理。因此Spring建议在具体的业务类上进行@Tranactional的注解操作。
测试输出
Key值是 - ID, Value值是 - 1Key值是 - NAME, Value值是 - 华为Key值是 - TICKETID, Value值是 - 32Key值是 - ACCOUNTID, Value值是 - 22*******************************************-1Key - ID, Value - 1Key - MOVIENAME, Value - 精武风云Key - TOTALTICKETSCOUNT, Value - 190
下面的例子我们讲解spring基于aop方式的事务管理,AOP编程使应用可以通过横切点对事务管理进行分离和封装。在事务开始之前或者结束,操作成功提交数据,或者失败的时候回滚时都是我们关注的横切点。下面的这个场景是通过基本的aop去实现的,在Spring早期的版本中,用户必须通过TransactionProxyFactoryBean代理类,对需要进行事务管理的业务类进行代理,以便实施事务管理功能的增强,在Spring3.0以后这种方式已经不在被推荐了,但是原始的通过代理的方式更能让我们直观的认识Spring是怎样去实现声明式事务管理的,让我们看一下下边的例子
public class TicketBookingServiceThroughAop extends JdbcDaoSupport{ public void bookTicket(int userId, int ticketId, int noOfTickets){ int accountId = TicketUtils.getAccountId(getJdbcTemplate(), userId); float ticketCost = TicketUtils.findTicketCost(getJdbcTemplate(), ticketId); float totalCost = (noOfTickets * ticketCost); TicketUtils.deductMoneyFromAccount(getJdbcTemplate(), accountId, totalCost); TicketUtils.reduceTicketCount(getJdbcTemplate(), ticketId, noOfTickets); }}
这里指的注意的是订票服务的类没有任何事务相关的配置,aop的相关配置被配置在外部的配置文件中
public class Main { public static void main(String[] args) { ApplicationContext context = Utils.getContext(); TicketBookingServiceThroughAop service = (TicketBookingServiceThroughAop)context.getBean("ticketBookingServiceThroughAop"); service.bookTicket(1, 1, 1); }}
在程序运行中客户端实际上是去加载建立在真实接口TicketBookingService的代理类ticketBookingServiceThroughAop,配置文件如下:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd"><beans> <bean id="myOralceDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" destroy-method="close"> <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" /> <property name="url" value="jdbc:oracle:thin:@localhost:1521:orcl" /> <property name="username" value="sniper" /> <property name="password" value="123" /> </bean>
<!--这里我们声明了Spring事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="myOralceDataSource" /> </bean> <bean id="bookingTransactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor"> <property name="transactionManager" ref="transactionManager" />
<!--通过拦截器来配置事务属性--> <property name="transactionAttributes"> <props>
<!--Spring允许我们通过键值的方法配置业务方法的事务属性信息,他支持我们常用的通配符原则,例如get*将代表业务类中以get开始的全部方法,*代表所有方法--> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean>
<--在这里我们看到了ticketBookingProxyService为目标也就是标签target类,我们上面写的业务类ticketBookingServiceThroughAop提供了事务增强> <bean id="ticketBookingProxyService" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target" ref="ticketBookingServiceThroughAop" /> <property name="interceptorNames"> <list>
<!--这里我们采用了Spring提供的拦截器的功能,这里主要是植入事务管理器,配置事务管理属性--> <value>bookingTransactionInterceptor</value> </list> </property> </bean> <bean id="ticketBookingServiceThroughAop" class="com.sniper.springtm.ticbook.aop.TicketBookingServiceThroughAop"> <property name="dataSource" ref="myOralceDataSource" /> </bean></beans>
注意在上面的声明中,我们声明了作为org.springframework.aop.framework.ProxyFactoryBean实例的代理类ticketBookingProxyService,目标代理购票服务对象通过目标属性进行了定义,并且我们通过元素interceptorNames定义了bookingTransactionInterceptor的拦截器,在拦截器中我们引入了我们需要的一些配置操作
测试输出
Key值是 - ID, Value值是 - 1Key值是 - NAME, Value值是 - 华为Key值是 - TICKETID, Value值是 - 32Key值是 - ACCOUNTID, Value值是 - 22*******************************************-1Key - ID, Value - 1Key - MOVIENAME, Value - 精武风云Key - TOTALTICKETSCOUNT, Value - 189
总结:
在这个系列的文章中我们看到了如何在spring中开发相关的事务管理,我们也讨论了事务管理的重要性,看到了spring给我们提供的编程式事务管理和声明式事务管理,并且提供了一些例子,希望读者通过这些基本的例子将事务管理应用在以后实际的应用例子中。