Spring的 编程式事务和声明式事务

本文深入探讨了Spring框架中的事务管理机制,包括编程式事务和声明式事务的区别与实现细节。介绍了事务的基本属性如隔离级别、传播行为等,并通过实例展示了如何在Spring中配置和使用这些事务属性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Spring的 编程式事务声明式事务

入口(了解一些基本概念)

  • Spring事务属性(事务的属性有哪些?)
    我们都知道事务有开始,保存点,提交,回滚,隔离级别等属性。那么Spring对于事务属性定义有哪些呢?通过TransactionDefinition接口我们可以了解到:

    public interface TransactionDefinition{
    int getIsolationLevel();
    int getPropagationBehavior();
    int getTimeout();
    boolean isReadOnly();
    }

    • 获取隔离级别
    • 获取传播特性
    • 获取超时
    • 获取是否只读
  • 事务隔离级别
    隔离离别也是通过TransactionDefinition接口定义的,代表并发事务的隔离程度。
隔离级别描述
TransactionDefinition.ISOLATION_DEFAULT这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED
TransactionDefinition.ISOLATION_READ_UNCOMMITTED该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读和不可重复读,因此很少使用该隔离级别
TransactionDefinition.ISOLATION_READ_COMMITTED该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值
TransactionDefinition.ISOLATION_REPEATABLE_READ该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。
TransactionDefinition.ISOLATION_SERIALIZABLE所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

* 事务传播行为
所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了如下几个表示传播行为的常量.

传播行为描述
TransactionDefinition.PROPAGATION_REQUIRED如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务
TransactionDefinition.PROPAGATION_REQUIRES_NEW创建一个新的事务,如果当前存在事务,则把当前事务挂起
TransactionDefinition.PROPAGATION_SUPPORTS如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行
TransactionDefinition.PROPAGATION_NOT_SUPPORTED以非事务方式运行,如果当前存在事务,则把当前事务挂起
TransactionDefinition.PROPAGATION_NEVER以非事务方式运行,如果当前存在事务,则抛出异常
TransactionDefinition.PROPAGATION_MANDATORY如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常
TransactionDefinition.PROPAGATION_NESTED如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED

* 事务超时
指定事务的最大运行时间。使用int指定,单位是秒。
* 事务的只读属性
事务的只读属性是指,对事务性资源进行只读操作或者是读写操作。所谓事务性资源就是指那些被事务管理的资源,比如数据源、 JMS 资源,以及自定义的事务性资源等等。如果确定只对事务性资源进行只读操作,那么我们可以将事务标志为只读的,以提高事务处理的性能。在 TransactionDefinition 中以 boolean 类型来表示该事务是否只读。
* 事务的回滚规则
默认出现RuntimeException就会回滚。如果没有抛出任何异常,或者抛出了已检查异常,则仍然提交事务。这通常也是大多数开发者希望的处理方式,也是 EJB 中的默认处理方式。(这里个人理解的是已检查异常,是我们定义的checkedException,包括我们自定义的异常和调用方法捕获的异常)
*

Spring事务的三个基本类

Spring 框架中,涉及到事务管理的 API 大约有100个左右,其中最重要的有三个:TransactionDefinition、PlatformTransactionManager、TransactionStatus。所谓事务管理,其实就是“按照给定的事务规则来执行提交或者回滚操作”。“给定的事务规则”就是用 TransactionDefinition 表示的,“按照……来执行提交或者回滚操作”便是用 PlatformTransactionManager 来表示,而 TransactionStatus 用于表示一个运行着的事务的状态。打一个不恰当的比喻,TransactionDefinition 与 TransactionStatus 的关系就像程序和进程的关系。

  • TransactionDefinition 定义事务的属性
  • TransactionStatus 定义事务的状态

    public interface TransactionStatus{
    boolean isNewTransaction();
    void setRollbackOnly();
    boolean isRollbackOnly();
    }
  • PlatformTransactionManager 就是各种事务平台的实现接口

    Public interface PlatformTransactionManager{
    TransactionStatus getTransaction(TransactionDefinition definition)
    throws TransactionException;
    void commit(TransactionStatus status)throws TransactionException;
    void rollback(TransactionStatus status)throws TransactionException;
    }

所以我们现在可以向下,spring的事务真正实现是PlatformTransactionManager的实现类,通过各种参数来符合TransactionDefinitionTransactionStatus的要求,最后根据我们编程的或者声明的进行事务管理。

根据底层框架的不同(稍后我们看一下DataSourceTransactoinManager和HibernateTransactionManager的代码,主要做了什么),Spring(或者其他框架)提供主要的实现如下:

  • DataSourceTransactionManager: 适合JDBC和ibatis
  • HibernateTransactionManager: 适合hibernate
  • JpaTransactionManager: 适用于使用JPA进行数据持久化操作的情况(更底层的一些)
  • 适用于使用JPA进行数据持久化操作的情况

到这里基本概念终于结束了,我们可以介绍两种类型的事务管理了

编程式事务管理

首先我们回想一下不适用spring事务管理时,hibernate事务的管理是怎么样的? 大概是我们手动获取session,获取transaction,开始transaction,提交或者回滚,关闭session

那么我们使用spring的管理之后,事务本身控制还是交给持久框架自己管理。知识spring像一个代理人一样,你告诉它,它之后转化后告诉底层框架。

看个实际例子吧:

基于底层API的编程式事务管理
public class BankServiceImpl implements BankService {
    private BankDao bankDao;
    private TransactionDefinition txDefinition; // transaction定义是哪个 
    private PlatformTransactionManager txManager; //具体使用的txmanager
    ......
    public boolean transfer(Long fromId, Long toId, double amount) {
    //这里获取事务状态
    TransactionStatus txStatus = txManager.getTransaction(txDefinition);
    boolean result = false;
    try {
    result = bankDao.transfer(fromId, toId, amount);
    //提交事务
    txManager.commit(txStatus);
    } catch (Exception e) {
    result = false;
    //回滚
    txManager.rollback(txStatus);
    System.out.println("Transfer Error!");
    }
    return result;
    }
}

对应的xml文件:

<bean id="bankService" class="footmark.spring.core.tx.programmatic.origin.BankServiceImpl">
    <property name="bankDao" ref="bankDao"/>
    <property name="txManager" ref="transactionManager"/>
    <property name="txDefinition">
        <bean class="org.springframework.transaction.support.DefaultTransactionDefinition">
    <property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/>
    </bean>
    </property>
</bean>

但是这种写法和我们不适用spring的有何不同呢,最多是将事务层次提高了service层,不限于dao层,所以Spring做了改进:

基于TransactionTemplate的编程式事务管理

TransactionTemplate的execute方法提供一个内部匿名类,用来我们写transaction代码,然后提供一个transactionStatus的参数,这样你可以控制回滚。这样一来,我们就不用写任何关于事务API的代码了。格式大概是 Boolean b = transactionTempate.execute(new TransactionCallBack() { 执行方法(TransactionStatus transactionStatus){} },当执行完成后返回一个boolean的值. 还有一个方法,就是不提供返回结果的。

public class BankServiceImpl implements BankService {
    private BankDao bankDao;
    private TransactionTemplate transactionTemplate;
    ......
    public boolean transfer(final Long fromId, final Long toId, final double amount) {
        //调用一个回调函数
        return (Boolean) transactionTemplate.execute(new TransactionCallback(){
    public Object doInTransaction(TransactionStatus status) {
        Object result;
        try {
            result = bankDao.transfer(fromId, toId, amount);
        } catch (Exception e) {
        status.setRollbackOnly();
        result = false;
        System.out.println("Transfer Error!");
        }
        return result;
        }
        });
    }
}

对应的XML:

<bean id="bankService"
class="footmark.spring.core.tx.programmatic.template.BankServiceImpl">
    <property name="bankDao" ref="bankDao"/>
    <property name="transactionTemplate" ref="transactionTemplate"/>
</bean>

从结果来看,好像还是不够简单和清晰。下面我们来看声明式事务管理,也是比较推荐的方式

声明式事务管理

Spring的声明式事务管理是基于AOP的,在方法前和后加上切点,用来打开事务和提交/回滚事务。

基于TransactionInterceptor的管理

最初,Spring 提供了 TransactionInterceptor 类来实施声明式事务管理功能

<beans...>
    ......
    <bean id="transactionInterceptor"
    class="org.springframework.transaction.interceptor.TransactionInterceptor">
        <property name="transactionManager" ref="transactionManager"/>
        <property name="transactionAttributes">
            <props>
                //指定了方法,可以使用通配符
                <prop key="transfer">PROPAGATION_REQUIRED</prop>
            </props>
        </property>
    </bean>
    <bean id="bankServiceTarget"
    class="footmark.spring.core.tx.declare.origin.BankServiceImpl">
    <property name="bankDao" ref="bankDao"/>
    </bean>
    <bean id="bankService"
    class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="bankServiceTarget"/>
        <property name="interceptorNames">
            <list>
                <idref bean="transactionInterceptor"/>
            </list>
        </property>
    </bean>
    ......
</beans>

首先,我们配置了一个 TransactionInterceptor 来定义相关的事务规则,他有两个主要的属性:一个是 transactionManager,用来指定一个事务管理器,并将具体事务相关的操作委托给它;另一个是 Properties 类型的 transactionAttributes 属性,它主要用来定义事务规则,该属性的每一个键值对中,键指定的是方法名,方法名可以使用通配符,而值就表示相应方法的所应用的事务属性。
指定事务属性的取值有较复杂的规则,这在 Spring 中算得上是一件让人头疼的事。具体的书写规则如下:
传播行为 [,隔离级别] [,只读属性] [,超时属性] [不影响提交的异常] [,导致回滚的异常]

基于 TransactionProxy… 的声明式事务管理

前面的声明式事务虽然好,但是却存在一个非常恼人的问题:配置文件太多。
为了缓解这个问题,Spring 为我们提供了 TransactionProxyFactoryBean,用于将TransactionInterceptor 和 ProxyFactoryBean 的配置合二为一

<beans......>
    ......
    <bean id="bankServiceTarget"
    class="footmark.spring.core.tx.declare.classic.BankServiceImpl">
        <property name="bankDao" ref="bankDao"/>
    </bean>
    <bean id="bankService"
    class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="target" ref="bankServiceTarget"/>
        <property name="transactionManager" ref="transactionManager"/>
        <property name="transactionAttributes">
            <props>
                <prop key="transfer">PROPAGATION_REQUIRED</prop>
            </props>
        </property>
    </bean>
    ......
</beans>

这样子是减少了proxy的代码,但是每个service还是需要一个配置。所以我们可以使用自动代理的配置,这样子就减少了大量的配置。也应该是最常用的。

!-- Spring事务管理 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
</bean>

<!-- 配置事务的传播特性 -->
<bean id="baseTransactionProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" abstract="true" >
    <property name="transactionManager" ref="transactionManager" />
    <property name="transactionAttributes">
        <props>
            <prop key="add*">PROPAGATION_REQUIRED</prop>
            <prop key="edit*">PROPAGATION_REQUIRED</prop>
            <prop key="remove*">PROPAGATION_REQUIRED</prop>
            <prop key="insert*">PROPAGATION_REQUIRED</prop>
            <prop key="update*">PROPAGATION_REQUIRED</prop>
            <prop key="del*">PROPAGATION_REQUIRED</prop>
            <prop key="*">readOnly</prop>
        </props>
    </property>
</bean>
基于 命名空间的声明式事务管理

前面两种声明式事务配置方式奠定了 Spring 声明式事务管理的基石。在此基础上,Spring 2.x 引入了 命名空间,结合使用 命名空间,带给开发人员配置声明式事务的全新体验,配置变得更加简单和灵活。另外,得益于 命名空间的切点表达式支持,声明式事务也变得更加强大。

具体例子:

<beans......>
    ......
    <bean id="bankService" 
    class="footmark.spring.core.tx.declare.namespace.BankServiceImpl">
        <property name="bankDao" ref="bankDao"/>
    </bean>
    <tx:advice id="bankAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="transfer" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:pointcut id="bankPointcut" expression="execution(* *.transfer(..))"/>
        <aop:advisor advice-ref="bankAdvice" pointcut-ref="bankPointcut"/>
    </aop:config>
    ......
</beans>
@Transactional 的声明式事务管理

除了基于命名空间的事务配置方式,Spring 2.x 还引入了基于 Annotation 的方式,具体主要涉及@Transactional 标注。@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。

具体例子:

@Transactional(propagation = Propagation.REQUIRED)
public boolean transfer(Long fromId, Long toId, double amount) {
    return bankDao.transfer(fromId, toId, amount);
}

但是使用这种我们就必须启用tx的annotation:

<tx:annotation-driven transaction-manager="transactionManager"/>

附录

  • DataSourceTransactionManager(类主要的方法,就不看原代码了)
    • 设置和获取DataSource
    • 获取transaction
    • transaction是否存在
    • 开始transaction
    • 暂停,释放连接connection
    • 恢复暂停的连接
    • 提交
    • 回滚
    • 仅回滚
    • 清理
  • HibernateTransactionManager
    • 转换异常
    • 开始事务
    • 清理
    • 提交
    • 获取事务
    • 恢复
    • 回滚
    • 仅回滚
    • 暂停
    • 获取数据源
    • 获取Entity的Interceptor
    • 获取SessionFactory
    • 是否存在事务
    • 是否预先提交

很好说明的IBM文章:全面分析 Spring 的编程式事务管理及声明式事务管理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值