欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  php教程

Spring事务抽象

程序员文章站 2022-03-28 20:43:44
...

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实现驱动事务环绕方法调用。

Spring事务抽象

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/>标签的各种属性:

Spring事务抽象

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设置:

Spring事务抽象

@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

Spring事务抽象

当设置事务传播为PROPAGATION_REQUIRED时,为每个方法创建一个逻辑事务范围。每个逻辑事务范围能单独决定只回滚状态,外部事务范围逻辑独立于内部事务范围。当然,在标准的PROPAGATION_REQUIRED情况下,所有这些范围将映射相同的物理事务。因此,只回滚标记设置在内部事务范围不影响外部事务发生的实际提交。

然而,在这种情况下,内部事务范围设置为只回滚标记,外部事务没有决定回滚,因此回滚是意想不到的。相应的UnexpectedRollbackException抛出。

PROPAGATION_REQUIRES_NEW

Spring事务抽象

对比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。

相关标签: Spring