Spring 事务管理那些事

源 / 泥瓦匠BYSocket   文 / 青芒@有赞

一、spring事务介绍

spring事务优点

  • 对不同的api进行统一编程模型,如JTA,JDBC,Hibernate,JPA,JDO...

  • 支持声明式事务

  • 简化编程式事务api

  • 对spring数据层的完美抽象

spring事务的传播性

PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中

PROPAGATIONREQUIRESNEW 每一个受影响的事务作用域都使用完全 独立的事务.物理上的事务就不同了并且可以独立的提交或者回滚, 外部事物不会影响到内部事务的回滚 状态

PROPAGATION_NESTED 事务嵌套 子事务是父事务的一部分,父事务失败则全部回滚,如果子事务失败,则回滚到进入子事务之前的状态

官网好像只列出了这几个,但是很多博客都说有9种,不过我们大部分都用的Required级别,其它的就不深究了,如果需要进一步了解可参考 https://my.oschina.net/dongli/blog/56904

二、如何使用spring事务

声明式事务

理解声明式事务实现机制 注解事务的最基本是利用了spring的aop实现,调用者调用的实则是目标类的代理类,代理类有事务的拦截器Interceptor, TransactionInterceptor 来实现对应的事务处理

xml配置方式

  1. <!-- 事务化配置 -->

  2.    <tx:advice id="txAdvice" transaction-manager="txManager">

  3.        <!-- 事务语义... -->

  4.        <tx:attributes>

  5.            <!-- 所有用'get'开头的方法都是只读的 -->

  6.            <tx:method name="get*" read-only="true"/>

  7.            <!-- 其他的方法使用默认的事务配置(看下面) -->

  8.            <tx:method name="*"/>

  9.        </tx:attributes>

  10.    </tx:advice>


  11.    <!-- 定义切面配置 -->

  12.    <aop:config>

  13.        <!-- FooService 下的所有方法-->

  14.        <aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>

  15.        <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>

  16.    </aop:config>


  17.    <!-- DataSource数据源配置 -->

  18.    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">

  19.        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>

  20.        <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>

  21.        <property name="username" value="scott"/>

  22.        <property name="password" value="tiger"/>

  23.    </bean>


  24.    <!-- 声明事务管理器实现 -->

  25.    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

  26.        <property name="dataSource" ref="dataSource"/>

  27.    </bean>

  28. ```

  29. tx-method配置项说明


  30. | 配置项 | 是否必须 | 默认值 | 描述 |

  31. | --------- |-------------| ----------|--------|

  32. | name |是|

  33. | propagation|否| REQUIRED|事务传播行为,参考上文描述|

  34. | isolation|否| DEFAULT|隔离级别 default使用数据库默认的事务隔离级别

  35. | timeout|否|-1|事务超时时间 单位 秒|

  36. | read-only|否|false|事务级别是否只读|

  37. | rollback-for|否|-----|指定回滚异常类型|

  38. | no-rollback-for|否|-----|指定什么异常类型不回滚|


  39. > isolation 可选值

  40. > DEFAULT  使用当前数据库默认的事务界别

  41. > READ_UNCOMMITTED  

  42. > READ_COMMITTED

  43. > REPEATABLE_READ 可重复读

  44. > SERIALIZABLE


  45. **注解方式(常用)**


  46. 配置的方式是采用通配符的方式进行事务切入,不够灵活,在实际开发中注解的方式用的较多,配置可参考下面


  1. <tx:annotation-driven transaction-manager="txManager"/>


  1. 需要事务的方法加上注解即可

//注解加到类上,则该类里所有public方法启用事务 //注解加在方法上,则只有方法启用事务 @Transactional public class DefaultFooService implements FooService {

  1. Foo getFoo(String fooName);


  2. Foo getFoo(String fooName, String barName);


  3. void insertFoo(Foo foo);


  4. void updateFoo(Foo foo);

}

  1. > 方法可见性和@Transactional

  2. 当使用代理时, 你应该只给public可见性的方法添加@Transactional注解. 如果你给protected, private或者包访问的方法添加了@Transactional注解, 不会产生错误, 但是事务不生效. 如果你需要给非公开的方法添加注解可以参考其它文档,此处不做概述


  3. **多数据源,多事务**

//名字和事务管理器同名即可 @Transactional("txManager1") public class DefaultFooService implements FooService {

  1. Foo getFoo(String fooName);


  2. Foo getFoo(String fooName, String barName);


  3. void insertFoo(Foo foo);


  4. void updateFoo(Foo foo);

}

  1. 也可以给txManager加上别名




//名字和事务管理器的qualifier相同即可 @Transactional("order") public class DefaultFooService implements FooService {

  1. Foo getFoo(String fooName);


  2. Foo getFoo(String fooName, String barName);


  3. void insertFoo(Foo foo);


  4. void updateFoo(Foo foo);

}

  1. ***使用注解的方式,注意下面这种情形,防止事务不生效**

  2. 1.不要对自己要进行事务控制的代码进行try catch,spring的事务触发是执行体出现异常,如果自己的程序catch住了异常,spring事务管理器以为执行成功,回滚不生效

//错误示例 @Transactional public void update2(String name){ try { jdbcTemplate.execute("UPDATE t_menua SET mname='"+name+"' WHERE mid=106 "); } catch (DataAccessException e) { e.printStackTrace(); } }

  1. 2. 嵌套执行,A 调用 B ,  A没有加事务注解,事务不生效

//错误案例 @Transactional public void update2(String name){ jdbcTemplate.execute("UPDATE t_menua SET mname='"+name+"' WHERE mid=106 "); }

  1. public void update3(String name) {

  2.    this.update2(name);

  3.    throw new RuntimeException();

  4. }

  1. 调用堆栈如下

  2. ![](http://upload-images.jianshu.io/upload_images/3359971-ca45a85cf3021565.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

//事务生效 @Transactional public void update2(String name){ jdbcTemplate.execute("UPDATE t_menua SET mname='"+name+"' WHERE mid=106 "); }

  1. @Transactional

  2. public void update3(String name) {

  3.    this.update2(name);

  4.    throw new RuntimeException();

  5. }

  1. 调用堆栈如下

  2. ![](http://upload-images.jianshu.io/upload_images/3359971-f7bf09105cef3946.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


  3. 大家对比可以发现,第一种的执行栈里压根就没有事务拦截器,所以事务没有生效


  4. ### 编程式事务

  5. spring 配置


  1. java代码

public class LocalDataService {

  1. /**

  2. * 编程式事务模板

  3. */

  4. @Resource

  5. private TransactionTemplate transactionTemplate;



  6. /**

  7. * dao层执行体,可以是hibernate,mybatis等其它db框架

  8. */

  9. @Resource

  10. private JdbcTemplate jdbcTemplate;



  11. /**

  12. * 修改数据

  13. */

  14. public void update(final String name){


  15.    transactionTemplate.execute(new TransactionCallback<Integer>() {

  16.        public Integer doInTransaction(TransactionStatus transactionStatus) {

  17.            jdbcTemplate.execute("UPDATE t_menua SET  mname='"+name+"' WHERE mid=106 ");

  18.            return 1;

  19.        }

  20.    });


  21.    throw new RuntimeException();

  22. }

} ```

transactionTemplate 官方有两种

TransactionTemplate PlatformTransactionManager

Spring一般都推荐使用TransactionTemplate来进行编程式事务管理. 第二种方式有点类似于使用JTA的 UserTransaction接口

参考文档 spring中文文档 http://spring.cndocs.tk/transaction.html

长按订阅更多精彩▼

如有收获,点个在看,诚挚感谢