Spring同时支持编程式事务策略和声明式事务策略,大部分时候,咱们都推荐采用声明式事务策略。使用声明式事务策略的优点十分明显:
java
声明式事务能大大下降开发者的代码书写量,并且声明式事务几乎不影响应用的代码。所以,不论底层事务策略如何变化,应用程序都无需任何改变mysql
应用程序代码无需任何事务处理代码,能够更专一于业务逻辑的实现spring
Spring可对任何POJO的方法提供事务管理,并且Spring的声明式事务管理无需容器的支持,可在任何环境下使用sql
EJB的CMT没法提供声明式回滚规则;而经过配置文件,Spring可指定事务在遇到特定异常时自动回滚。Spring不只可在代码中使用setRollbackOnly回滚事务,也可在配置文件中配置回滚规则数据库
因为Spring采用AOP的方式管理事务,所以,能够在事务回滚动做中插入用户本身的动做,而不只仅是执行系统默认的回滚编程
本文主要介绍Spring中声明式事务管理的使用。测试
在Spring1.X中,声明式事务使用TransactionProxyFactoryBean来配置事务代理Bean。正如它的类名所暗示的,它是一个专门为目标Bean生成事务代理的工厂Bean。既然TransactionProxyFactoryBean产生的是事务代理Bean,可见Spring的声明式事务策略是基于Spring AOP的。this
每一个TransactionProxyFactoryBean为一个目标Bean生成一个事务代理Bean,事务代理的方法改写了目标Bean的方法,就是在目标Bean的方法执行以前加入开始事务,在目标Bean的方法正常结束以前提交事务,若是遇到特定异常则回滚。spa
TransactionProxyFactoryBean建立事务代理时,须要了解当前事务所处的环境,该环境属性经过PlatformTransactionManager实例(其实现类的实例)传入,而相关事务规则则在该Bean定义中给出。下面是一个简单的持久化测试程序,该程序插入两条数据,这两条数据彻底相同,将违反惟一键约束:线程
package com.abc.dao.impl; public class NewsDaoImpl implements NewsDao { private DataSource dataSource; public void setDataSource(DataSrouce dataSource) { this.dataSource = dataSource; } public void insert(String title, String content) { JdbcTemplate template = new JdbcTemplate(dataSource); template.update("insert into news_table values (....)"); //两次相同的操做,将违反主键约束 template.update("insert into news_table values (....)"); } }
上面的程序中,两次update语句将会违反主键约束——该行代码将会引起异常,若是在没有事务的环境下,前一条代码会向数据库中插入一条记录;但若是在增长了事务控制的环境下,则这两条语句是一个总体,由于第二条语句插入失败将致使第一条插入的记录也被回滚。下面是在Spring配置文件中配置该测试程序,并使用TransactionProxyFactoryBean为它们配置事务代理:
<!-- 使用C3P0数据库链接池做为数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPolledDataSource" destroy-method="close"> <property name="driverClass" value="com.mysql.jdbc.Driver" /> <property name="jdbcUrl" value="jdbc:mysql://localhost/test" /> <property name="user" value="root" /> <property name="password" value="root" /> <property name="maxPoolSize" value="40" /> <property name="minPoolSize" value="4" /> <property name="initialPoolSize" value="10" /> <!-- 指定数据库链接池的链接的最大空闲时间 --> <property name="maxIdleTime" value="20" /> </bean> <!-- 配置JDBC数据源的局部事务管理器,使用DataSourceTransactionManager类,该类实现了 PlatformTransactionManager接口,是针对采用数据源链接的特定实现 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 配置TransactionManager时须要注入数据源引用 --> <property name="dataSource" ref="dataSource" /> </bean> <!-- 下面这个是前面定义的业务Bean --> <bean id="newsDao" class="com.abc.dao.impl.NewsDaoImpl"> <!-- 为业务Bean注入属性 --> <property name="dataSource" ref="dataSource" /> </bean> <bean id="newsDaoTransProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <!-- 为事务代理工厂Bean注入事务管理器 --> <property name="transactionManager" ref="transactionManager" /> <!-- 要在哪一个Bean上面建立事务代理对象 --> <property name="target" ref="newsDao" /> <!-- 指定事务属性 --> <property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean>
上面的配置文件中定义了一个事务管理器transactionManager,该事务管理器是针对JDBC局部事务的特定实现类。配置事务代理(如上面的newsDaoTransProxy)时须要传入一个事务管理器,一个目标Bean,并指定该事务代理的事务属性。事务属性由transactionAttributes属性指定。上面事务属性只有一条事务传播规则,该规则制定对于全部方法都使用PROPAGATION_REQUIRED的传播规则。Spring支持的事务传播规则以下:
PROPAGATION_MANDATORY:要求调用该方法的线程必须处于事务环境中,不然抛出异常
PROPAGATION_NESTED:若是执行该方法的线程已处于事务环境下,依然启动新的事务,方法在嵌套的事务里执行。若是执行方法的线程为处于事务中,也启动新的事务,而后执行该方法,此时与PROPAGATION_REQUIRED相同
PROPAGATION_NEVER:不容许调用该方法的线程处于事务环境下,若是调用该方法的线程处于事务环境下,则抛出异常
PROPAGATION_NOT_SUPPORTED:若是调用该方法的线程处在事务中,则暂停当前事务,而后执行该方法
PROPAGATION_REQUIRED:要求在事务环境中执行该方法,若是当前执行的线程已处于事务中,则直接调用;若是当前执行线程不处于事务中,则启动新的事务后执行该方法
PROPAGATION_REQUIRES_NEW:要求在事务环境中执行该方法,若是当前执行的线程已处于事务中,则暂停当前事务,启动新事务后执行该方法;若是当前执行线程不处于事务中,则启动新的事务后执行该方法
PROPAGATION_SUPPORTS:若是当前执行线程处于事务中,则使用当前事务;不过不在事务中,则不使用事务
主程序中主要获取了定义的NewsDao类型的Bean,并调用其insert方法,下面是主程序:
public class SpringTest { public static void main(String[] arg) { ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); NewsDao dao = (NewsDao)context.getBean("newsDaoTransProxy",NewsDao.class); dao.insert("新闻标题","新闻内容"); } }
上面的第4行中获取了newsDaoTransProxy Bean,该Bean已经不在是NewsDaoImpl类的实例了,它只是Spring容器建立的事务代理,该事务代理以NewsDaoImpl实例为目标对象,且该目标对象也实现了NewsDao接口(与NewsDaoImpl实现了相同的接口),故代理对象也能够当成NewsDao实例来使用。运行上面的程序,将出现一个异常,并且insert方法所执行的两条SQL语句所有回滚——由于事务控制的缘故。
当咱们使用TransactionProxyFactoryBean为目标Bean配置了事务代理之后,SpringAOP将会把负责事务操做的加强处理织入目标Bean的业务方法当中。事实上,Spring不只支持对接口的代理,整合CGLIB后,Spring甚至能够对具体类生成代理,只要设置proxyTargetClass属性为true便可。若是目标Bean没有实现任何接口,proxyTargetClass属性默认被设为true,此时Spring会对具体类生成代理。固然一般建议面向接口编程,而不要面向具体的实现类编程。