Spring事务抽象
1 介绍Spring框架事务管理
全面的事务支持是使用Spring框架令人信服的原因。Spring框架为事务管理提供一致性抽象,拥有以下好处:
一致性编程模型跨不同事务APIs,例如,Java事务API(JTA)、JDBC、Hibernate、Java持久化API(JPA)和Java数据对象(JDO)。
支持声明式事务管理。
比复杂事务APIs(例如,JTA)更简单。
完美的与Spring的数据访问抽象集成。
2 Spring框架的事务支持模型的优点
传统上,Java EE开发人员有两种可选的事务管理方式:全局或本地事务,都有各自的局限性。
2.1 全局事务
全局事务让你能使用多个事务资源,通常是关系型数据库和消息队列。应用程序通过JTA管理全局事务,这是一个笨重的API。而且,JTA UserTransaction通常需要来自JNDI,意味着你需要使用JNDI。显然,使用全局事务会限制应用程序代码的重用,因为JTA通常只在应用程序服务器环境有效。
以往,使用全局事务的首选方式是通过EJB CMT(容器管理事务):CMT是声明式事务管理。EJB CMT清除了相关事务的JNDI查找,不过EJB自己需要使用JNDI。它清除大多数(但不是所有)需要编写Java代码控制事务。重要的缺点是,CMT捆绑JTA和应用程序服务器环境。同时,它只有使用EJBs实现业务逻辑,或至少在事务EJB门面中时才有效。
2.2 本地事务
本地事务时特定资源,例如,事务关联JDBC连接。本地事务易于使用,但有明显缺点:它们不能跨多个事务资源。例如,使用JDBC连接管理事务不能运行在全局JTA事务中。因为应用程序服务器没有负责事务管理,它不保证跨资源的正确性。另一个缺点是本地事务时入侵式编程模型。
2.3 Spring框架的一致性编程模型
Spring解决了全局和本地事务的缺点。它让开发人员在任意环境使用一致性编程模型。开发人员只需编写自己的代码,它能抽离不同环境中的不同的事务管理。Spring框架提供声明式和编程式事务管理。大多数用户喜欢声明式事务管理,这也是推荐的。
使用编程式事务管理,开发人员使用Spring框架事务抽象,能运行在任意底层事务之上。使用首选的声明式模型,开发人员通常写很少或不写事务管理代码,因此,不依赖于Spring框架事务API,或其它事务API。
3 理解Spring框架事务抽象
Spring事务抽象的关键是事务策略的概念。事务策略通过org.springframework.transaction.PlatformTransactionManager接口定义:
public interface PlatformTransactionManager {
TransactionStatus getTransaction(
TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
这主要是服务提供接口(SPI),尽管它能在你的代码中使用编程模型。因为PlatformTransactionManager是一个接口,它易于模拟(mocked)和存根(stubbed)。它不需要像JNDI一样查找策略。PlatformTransactionManager实现像Spring IoC容器中定义的其它对象一样。这个好处让Spring框架事务值得抽象,甚至是使用JTA时。事务代码比直接使用JTA易于测试。
PlatformTransactionManager接口方法抛出的TransactionException是未检测异常(即,继承java.lang.RuntimeException)。事务失败是致命性的。很少情况,应用程序代码能实际从事务失败中恢复,应用程序开发人员能选择捕获并处理TransactionException。优点在于开发人员不必强制这样做。
getTransaction(..)方法依赖于TransactionDefinition参数返回TransactionStatus对象。返回的TransactionStatus可以代表一个新事务,或如果匹配事务在当前调用栈中存在则代表已存在的事务。其实就是后面这种情况,正如Java EE事务上下文,TransactionStatus关联可执行的事务。
TransactionDefinition接口说明:
Isolation(事务隔离级别):事务隔离级别。例如,该事务是否能读取其它的未提交事务?
Propagation(事务传播):通常,所有代码执行在事务范围将运行在事务中。然而,你有一个选项可以指定当事务上下文已存在时事务方法执行的行为。例如,代码能继续运行在已存在的事务中(通常情况);或已存在的事务暂停,创建一个新事务。
事务超时:该事务运行多长时间后失效,并通过底层事务自动回滚。
只读状态:当你的代码读取但不修改数据时,可以使用只读事务。在某些情况下,只读事务有利于优化,例如,当你使用Hibernate时。
这些设置反应标准事务概念。底层这些概念是使用Spring框架或任意事务管理解决方案不可或缺的。
TransactionStatus接口为事务代码提供简单的方式控制可执行事务和查询事务状态:
public interface TransactionStatus extends SavepointManager {
boolean isNewTransaction();
boolean hasSavepoint();
void setRollbackOnly();
boolean isRollbackOnly();
void flush();
boolean isCompleted();
}
无论你是否选择Spring中的声明式或编程式事务管理,定义正确的PlatformTransactionManager实现是必不可少的。你通常通过依赖注入定义该实现。
PlatformTransactionManager通常需要知道工作的环境:JDBC、JTA、Hibernate等等。下面是定义本地PlatformTransactionManager的例子。
定义JDBC DataSource:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
PlatformTransactionManager定义引用DataSource定义:
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
如果你在Java EE容器中使用JTA,那么你可以通过使用JNDI和Spring的JtaTransactionManager获取容器的DataSource:
<?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:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee.xsd">
<jee:jndi-lookup id="dataSource" jndi-name="jdbc/jpetstore"/>
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" />
<!-- other <bean/> definitions here -->
</beans>
JtaTransactionManager不需要知道DataSource,或任意特定资源,因为它使用容器的全局事务管理。
你也能使用Hibernate的本地事务。在这种情况下,你需要定义Hibernate的LocalSessionFactoryBean,你的应用程序代码将使用它获取Hibernate Session实例。
这种情况下,txManager bean是HibernateTransactionManager。正如DataSourceTransactionManager需要引用DataSource一样,HibernateTransactionManager需要引用SessionFactory:
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mappingResources">
<list>
<value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<value>
hibernate.dialect=${hibernate.dialect}
</value>
</property>
</bean>
<bean id="txManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
如果你使用Hibernate和Java EE容器管理JTA事务,那么你只用使用JtaTransactionManager:
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
在所有这些情况下,应用程序代码不需要改变。你仅仅需要改变配置来改变如何管理事务。
4 使用事务同步资源
现在应该清楚如何创建不同的事务管理器,和它们如何链接需要同步事务的相关资源(例如,DataSourceTransactionManager链接DataSource,HibernateTransactionManager链接SessionFactory等等)。
4.1 高级同步方式
首选方式是使用Spring的高级模板基于持久化集成APIs或使用带有事务的本地ORM APIs——感知工厂bean或代理管理本地资源工厂。这些事务感知解决方案内部处理资源创建、重用、清理、资源事务同步选项和异常映射。因此,数据访问代码没有处理这些任务,但可以关注非样板式持久化逻辑。通常,你使用本地ORM API或通过使用JdbcTemplate获取模板。
4.2 低级同步方式
例如,DataSourceUtils(JDBC)、EntityManagerFactoryUtils(JPA)、SessionFactoryUtils(Hibernate)、PersistenceManagerFactoryUtils(JDO)存在底层同步方式。当你想应用程序代码直接处理本地持久化APIs,你使用这些类确保获取Spring框架管理的实例,事务是(可选)同步的,发生在进程中的异常正确映射一致性API。
例如,在JDBC的情况下,传统的JDBC方式在DataSource上调用getConnection()方法,你可以使用Spring的org.springframework.jdbc.datasource.DataSourceUtils:
Connection conn = DataSourceUtils.getConnection(dataSource);
如果已存在的事务已经有一个连接同步(链接)它,实例被返回。否则,方法调用触发器创建新的连接,(可选)同步任意已存在的事务,后续在相同事务中重用。
这种方式不需要Spring事务管理(事务同步是可选的),因此,无论你是否使用Spring管理事务你都能使用它。
当然,一旦你使用Spring的JDBC支持、JPA支持或Hibernate支持,你通常不喜欢使用DataSourceUtils或其它的帮助类。
4.3 TransactionAwareDataSourceProxy
在底层,已经存在TransactionAwareDataSourceProxy类。这是目标DataSource代理,包装目标DataSource到感知Spring管理事务。为此,它类似于Java EE服务器提供的传统JNDI DataSource。
5 声明式事务管理
Spring框架的声明式事务管理让Spring面向切面编程(AOP)成为可能,尽管,Spring框架发布包以模板形式自带事务切面代码,一般需要理解AOP。
5.1 理解Spring框架的声明式事务实现
通过元数据驱动事务通知(当前基于XML或注解)。AOP联合事务元数据产生AOP代理,使用TransactionInterceptor联合适当的PlatformTransactionManager实现驱动事务环绕方法调用。
5.2 声明式事务实现的例子
<?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: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/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 事务通知-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!-- 所有只读方法以'get'开头 -->
<tx:method name="get*" read-only="true"/>
<!-- 其它方法使用默认设置-->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
</aop:config>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
<property name="username" value="scott"/>
<property name="password" value="tiger"/>
</bean>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
5.3 回滚声明式事务
默认情况下,只有抛出运行时异常或Error时导致Spring事务回滚。检查异常不导致Spring事务回滚。
你能明确配置事务回滚的异常类型。
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true" rollback-for="NoProductInStockException"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
你也能指定事务不会滚的异常类型:
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
5.4 为不同的bean配置不同的事务语义
<?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: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/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:config>
<aop:pointcut id="defaultServiceOperation" expression="execution(* x.y.service.*Service.*(..))"/>
<aop:pointcut id="noTxServiceOperation" expression="execution(* x.y.service.ddl.DefaultDdlManager.*(..))"/>
<aop:advisor pointcut-ref="defaultServiceOperation" advice-ref="defaultTxAdvice"/>
<aop:advisor pointcut-ref="noTxServiceOperation" advice-ref="noTxAdvice"/>
</aop:config>
<tx:advice id="defaultTxAdvice">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<tx:advice id="noTxAdvice">
<tx:attributes>
<tx:method name="*" propagation="NEVER"/>
</tx:attributes>
</tx:advice>
</beans>
5.5 <tx:advice/>设置
默认设置:
事务传播设置是REQUIRED
事务隔离级别是DEFAULT
事务是读/写
事务超时时间默认为底层事务系统的超时时间,或如果不支持超时就没有
任意RuntimeException异常触发回滚,任意检查Exception不触发
你能改变这些默认设置;嵌套在<tx:advice/>和<tx:attributes/>标签中的<tx:method/>标签的各种属性:
5.6 使用@Transactional
开启注解管理事务:
<?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: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/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 开启注解配置事务 -->
<tx:annotation-driven transaction-manager="txManager"/>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- other <bean/> definitions here -->
</beans>
Service声明注解:
@Transactional
public class DefaultFooService implements FooService {
Foo getFoo(String fooName);
Foo getFoo(String fooName, String barName);
void insertFoo(Foo foo);
void updateFoo(Foo foo);
}
@Transactional设置:
@Transactional的多事务管理器:
public class TransactionalService {
@Transactional("order")
public void setSomething(String name) { ... }
@Transactional("account")
public void doSomething() { ... }
}
<tx:annotation-driven/>
<bean id="transactionManager1"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
...
<qualifier value="order"/>
</bean>
<bean id="transactionManager2"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
...
<qualifier value="account"/>
</bean>
自定义简称注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("order")
public @interface OrderTx {
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("account")
public @interface AccountTx {
}
5.7 事务传播
PROPAGATION_REQUIRED
当设置事务传播为PROPAGATION_REQUIRED时,为每个方法创建一个逻辑事务范围。每个逻辑事务范围能单独决定只回滚状态,外部事务范围逻辑独立于内部事务范围。当然,在标准的PROPAGATION_REQUIRED情况下,所有这些范围将映射相同的物理事务。因此,只回滚标记设置在内部事务范围不影响外部事务发生的实际提交。
然而,在这种情况下,内部事务范围设置为只回滚标记,外部事务没有决定回滚,因此回滚是意想不到的。相应的UnexpectedRollbackException抛出。
PROPAGATION_REQUIRES_NEW
对比PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW使用完全独立的事务。在这种情况下,底层物理事务是不同的,因此,能独自提交或回滚,外部事务的回滚状态不受内部事务的影响。
PROPAGATION_NESTED
使用一个能回滚到多个保存点的物理事务。这种回滚允许内部事务范围触发它的范围回滚,外部事务能够继续物理事务,尽管一些操作已经回滚。该设置通常映射到JDBC保存点,因此,只能用于JDBC资源事务。
6 编程式事务管理
Spring框架提供两种编程式事务管理:
使用TransactionTemplate(推荐)
使用PlatformTransactionManager直接实现
6.1 使用TransactionTemplate
TransactionTemplate采用与其它Spring模板(例如,JdbcTemplate)相同的方式。
public class SimpleService implements Service {
private final TransactionTemplate transactionTemplate;
public SimpleService(PlatformTransactionManager transactionManager) {
Assert.notNull(transactionManager, "The 'transactionManager' argument must not be null.");
this.transactionTemplate = new TransactionTemplate(transactionManager);
}
public Object someServiceMethod() {
return transactionTemplate.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
updateOperation1();
return resultOfUpdateOperation2();
}
});
}
}
如果没有返回值,则使用TransactionCallbackWithoutResult类:
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
updateOperation1();
updateOperation2();
}
});
在业务代码中回滚:
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
updateOperation1();
updateOperation2();
} catch (SomeBusinessExeption ex) {
status.setRollbackOnly();
}
}
});
指定事务设置:
public class SimpleService implements Service {
private final TransactionTemplate transactionTemplate;
public SimpleService(PlatformTransactionManager transactionManager) {
Assert.notNull(transactionManager, "The 'transactionManager' argument must not be null.");
this.transactionTemplate = new TransactionTemplate(transactionManager);
this.transactionTemplate.setIsolationLevel(
TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
this.transactionTemplate.setTimeout(30); // 30秒
}
}
使用Spring XML配置TransactionTemplate:
<bean id="sharedTransactionTemplate"
class="org.springframework.transaction.support.TransactionTemplate">
<property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/>
<property name="timeout" value="30"/>
</bean>
6.2 使用PlatformTransactionManager
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try {
// 执行业务逻辑
} catch (MyException ex) {
txManager.rollback(status);
throw ex;
}
txManager.commit(status);
7 选择编程式还是声明式管理事务?
如果你只有少量事务操作选择编程式事务管理。
8 事务绑定事件
从Spring 4.2开始,监听器事件可以绑定到事务阶段。
@Componentpublic class MyComponent {
@TransactionalEventListener
public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) {
...
}
}
TransactionalEventListener注解暴露phase属性允许定制监听事务阶段。有效的阶段是:BEFORE_COMMIT、AFTER_COMMIT(默认)、AFTER_ROLLBACK和AFTER_COMPLETION。