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

Spring 声明式事务

程序员文章站 2022-05-21 09:32:13
...

Spring的声明式事务管理是通过Spring AOP实现的,默认情况下,Spring事务只在遇见RuntimeException时才会回滚,可以通过配置来设置其他类型异常。

概念上来说,在事务代理上调用方法的工作过程看起来像这样:

Spring 声明式事务
            
    
    博客分类: Spring spring声明式事务事务 

基于@Transactional注解的事务方式

 

首先配置Spring容器:

 

<!-- 激活annotation功能 -->
<context:annotation-config />
<!-- 开启使用@Transactional注解方式 -->
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />
<context:component-scan base-package="com.sohu.tv.crm"
    scoped-proxy="targetClass">
</context:component-scan>-->

<bean id="jdbcTemplate"  class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!-- DataSource -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
      destroy-method="close">
    <property name="driverClass" value="com.mysql.jdbc.Driver"/>
    <property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/test"/>
    <property name="user" value="root"/>
    <property name="password" value="root"/>
    <!-- 指定连接池里最小连接数 -->
    <property name="minPoolSize" value="2"/>
    <!-- 指定连接池里最大连接数 -->
    <property name="maxPoolSize" value="5"/>
    <!-- 连接最大空闲时间,超过时间将被丢弃,单位是秒 -->
    <property name="maxIdleTime" value="60"/>
    <!-- 当连接池里面的连接用完的时候,C3P0一次获取的新的连接数 -->
    <property name="acquireIncrement" value="3"/>
    <!-- 指定连接池里最大缓存多少个Statement对象 -->
    <property name="maxStatements" value="10"/>
    <!-- 初始创建连接的数量 -->
    <property name="initialPoolSize" value="2"/>
    <!-- 每隔XX秒检查连接池里的空闲连接 ,单位是秒 -->
    <property name="idleConnectionTestPeriod" value="900"/>
    <property name="numHelperThreads" value="10" />
    <property name="preferredTestQuery" value="select 1 from dual" />
</bean>
<!-- DataSourceTransactionManager是用于JDBC、ibatis/MyBatis类型的Spring内置事务管理器 -->
<bean id="transactionManager"
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
    <property name="rollbackOnCommitFailure" value="true" />
</bean>

 配置中的<tx:annotation-driven/>属性proxy-target-class决定为那些使用了@Transactional注解的类创建何种事务代理。 如果 "proxy-target-class" 属性被设为 "true", 那么基于类的代理就会被创建。即使用CGLib创建增强的代理,如果 "proxy-target-class"属性被设为"false" 或者没设,那么基于接口的标准JDK代理就会被创建。

 

@Transactional注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。 然而,请注意只是使用@Transactional注解并不会启用事务行为, 它仅仅 是一种元数据,能够被可以识别@Transactional注解和上述的配置适当的具有事务行为的beans所使用。其实是<tx:annotation-driven/>元素的出现 开启了事务行为。

Spring团队的建议是你只在具体的类上使用@Transactional注解, 而不要注解在接口上。我在实际项目中一般只在操作数据库的方法上使用,而不是在整个类上使用。

 

@Service
public class BlogServiceImpl implements BlogService {

    @Resource
    private BlogDao blogDao;

    @Override
    @Transactional
    public void insert(Blog blog) {
        // 一些其他逻辑...
        blogDao.insert(blog);
        // 一些其他逻辑...
    }

    @Override
    @Transactional
    public void update(Blog blog) {
        // 一些其他逻辑...
        blogDao.update(blog);
        // 一些其他逻辑...
    }

    @Override
    @Transactional
    public void insertOrUpdate(Blog blog) {
        if (blog.getId() == null) {
            insert(blog);
        } else {
            update(blog);
        }
    }

    @Override
    public Blog get(Integer id) {
        return blogDao.get(id);
    }
}

 这样在调用BlogService方法的时候,带有@Transactional的方法就会在事务的控制下。但是这里需要注意和Spring AOP一样的场景,即方法调用同一个类中的另一个方法时不能被拦截的问题。从上面来看,即insertOrUpdate方法中调用了insert()和update()方法,则insert()或者update()方法使用的实际是insertOrUpdate()方法的事务,如果insertOrUpdate()方法没有@Transactional注解,则调用该方法时,内部的insert()方法的事务是不起作用的,具体原因参见 http://my.oschina.net/mushui/blog/161387.

 

@Transactional注解属性

属性 类型 描述
propagation 枚举型:Propagation 可选的传播性设置
isolation 枚举型:Isolation 可选的隔离性级别(默认值:ISOLATION_DEFAULT)
readOnly 布尔型 读写型事务 vs. 只读型事务
timeout int型(以秒为单位) 事务超时
rollbackFor 一组Class类的实例,必须是Throwable的子类 一组异常类,遇到时 必须 进行回滚。默认情况下checked exceptions不进行回滚,仅unchecked exceptions(即RuntimeException的子类)才进行事务回滚。
rollbackForClassname 一组Class类的名字,必须是Throwable的子类 一组异常类名,遇到时 必须 进行回滚
noRollbackFor 一组Class类的实例,必须是Throwable的子类 一组异常类,遇到时 必须不 回滚。
noRollbackForClassname 一组Class类的名字,必须是Throwable的子类 一组异常类,遇到时 必须不 回滚

事务传播行为

Spring管理的事务是逻辑事务,而且物理事务和逻辑事务最大差别就在于事务传播行为,事务传播行为用于指定在多个事务方法间调用时,事务是如何在这些方法间传播的,Spring共支持7种传播行为:

Required:默认的事务传播行为,表示必须有逻辑事务,否则新建一个事务,使用PROPAGATION_REQUIRED指定,表示如果当前存在一个逻辑事务,则加入该逻辑事务,否则将新建一个逻辑事务:

Spring 声明式事务
            
    
    博客分类: Spring spring声明式事务事务 

RequiresNew:创建新的逻辑事务,使用PROPAGATION_REQUIRES_NEW指定,表示每次都创建新的逻辑事务(物理事务也是不同的)因此外部事务可以不受内部事务回滚状态的影响独立提交或者回滚。

Spring 声明式事务
            
    
    博客分类: Spring spring声明式事务事务 

Supports:支持当前事务,使用PROPAGATION_SUPPORTS指定,指如果当前存在逻辑事务,就加入到该逻辑事务,如果当前没有逻辑事务,就以非事务方式执行。

NotSupported:不支持事务,如果当前存在事务则暂停该事务,使用PROPAGATION_NOT_SUPPORTED指定,即以非事务方式执行,如果当前存在逻辑事务,就把当前事务暂停,以非事务方式执行。

Mandatory:使用PROPAGATION_MANDATORY指定,如果当前有事务,使用当前事务执行,如果当前没有事务,则抛出异常(IllegalTransactionStateException)。

Never:不支持事务,如果当前存在是事务则抛出IllegalTransactionStateException异常,使用PROPAGATION_NEVER指定。

 

Nested:嵌套事务支持,使用PROPAGATION_NESTED指定,如果当前存在事务,则在嵌套事务内执行,如果当前不存在事务,则创建一个新的事务,嵌套事务使用数据库中的保存点来实现,即嵌套事务回滚不影响外部事务,但外部事务回滚将导致嵌套事务回滚。

Nested和RequiresNew的区别: 

  • RequiresNew每次都创建新的独立的物理事务,而Nested只有一个物理事务;
  • Nested嵌套事务回滚或提交不会导致外部事务回滚或提交,但外部事务回滚将导致嵌套事务回滚,而 RequiresNew由于都是全新的事务,所以之间是无关联的;
  • Nested使用JDBC 3的保存点实现,即如果使用低版本驱动将导致不支持嵌套事务。

    实际应用中一般使用默认的事务传播行为,偶尔会用到RequiresNew和Nested方式。

基于XML配置的事务

个人比较偏爱使用@Transactional注解的方式,但是很大一部分项目是使用XML方式的,使用XML方式只需要把上面配置中的

<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />

 去掉,然后添加如下配置即可:

<tx:advice id="txAdvice" transaction-manager="transactionManager">
   <tx:attributes>
       <tx:method name="insert*" propagation="REQUIRED" isolation="READ_COMMITTED"/>
       <tx:method name="get*" read-only="true"/>
   </tx:attributes>
</tx:advice>
<aop:config>
   <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.sohu.tv.crm.service.impl.*.*(..))" />
</aop:config>