您的位置:首页 > 编程语言 > Java开发

Spring事务抽象

2016-09-17 17:07 134 查看
摘要: 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/>标签的各种属性:

属性必须?默认值描述
nameY事务属性关联的方法名。通配符(*)匹配任意数量的字符。
propagationNREQUIRED事务传播行为。
isolationNDEFAULT事务隔离级别。
timeoutN-1事务超时值(秒)。
N
read-onlyNfalse是否是只读事务。
rollback-forN触发回滚的异常;逗号分隔。
no-rollback-forN不触发回滚的异常;逗号分隔。

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);

}

注解驱动事务设置选项:

XML属性注解属性默认值描述
transaction-managerTransactionManagementConfigurertransactionManager事务管理器使用的名字。
modemodeproxy“proxy”:Spring AOP代理 “aspectj”:Spring的AspectJ织入
proxy-target-classproxyTargetClassfalse只用于代理模式。如果为true,使用基于类的代理,如果为false,使用基于标准JDK接口创建代理。
orderorderOrdered.LOWEST_PRECEDENCE定义事务通知顺序,没有定义,AOP系统自行决定。
@Transactional设置:

属性类型默认值描述
value字符串指定事务管理器的可选限定符
propagation枚举:PropagationPROPAGATION_REQUIRED可选的事务传播设置
isolation枚举:IsolationISOLATION_DEFAULT可选的事务隔离级别
readOnlyboolean读/写读/写 vs 只读
timeout-1事务超时时间
rollbackFor任意派生自Throwable的Class对象数组导致回滚的可选异常类数组
rollbackForClassName任意派生自Throwable的类名数组导致回滚的可选异常类名数组
noRollbackFor任意派生自Throwable的Class对象数组不导致回滚的可选异常类数组
noRollbackForClassName任意派生自Throwable的类名数组不导致回滚的可选异常类名数组
@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) {

[b]status.setRollbackOnly();[/b]

}

}

});

指定事务设置:

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。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息