Spring transaction propagation

来源:互联网 发布:php fstat 编辑:程序博客网 时间:2024/04/30 13:07

Introduction

Java中存在事务传播的原因是当我们在调用某个业务逻辑方法A时该方法可能会调用其它的业务逻辑方法B,而这两个方法都有可能在不同的事务中,这样我们需要确定方法A和方法B的事务关系,以便其能协调完成整体的某个业务逻辑,这种关系就是事务间的传播关系。Spring的声明试事务能很好的控制这种传播关系,不用我们手动编程实现,只需在声明事务的时候指定传播类型即可。

PROPAGATION TYPE

DESCRIPTION

PROPAGATION_REQUIRED支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
如果正要执行的事务不在另外一个事务里,那么就起一个新的事务; 比如说,ServiceB.methodB的事务级别定义为PROPAGATION_REQUIRED, 那么由于执行ServiceA.methodA的时候, ServiceA.methodA已经起了事务,这时调用ServiceB.methodB,ServiceB.methodB看到自己已经运行在ServiceA.methodA 的事务内部,就不再起新的事务。而假如ServiceA.methodA运行的时候ServiceB.methodB发现自己没有在事务中,他就会为自己新建一个事务。 这样,在ServiceA.methodA或者在ServiceB.methodB内的任何地方出现异常,事务都会被回滚。即使ServiceB.methodB的事务已经被提交,但是ServiceA.methodA在接下来fail要回滚,ServiceB.methodB也要回滚。PROPAGATION_SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行。
如果当前在事务中,即以事务的形式运行,如果当前不在一个事务中,那么就以非事务的形式运行。PROPAGATION_MANDATORY支持当前事务,如果当前没有事务,就抛出异常。
必须在一个事务中运行,也就是说,他只能被一个父事务调用。否则,他就要抛出异常。PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起。
比如我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW,那么当执行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起,ServiceB.methodB会起一个新的事务,等待ServiceB.methodB的事务完成以后,他才继续执行。他与PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为ServiceB.methodB是新起一个事务,那么就是存在两个不同的事务。如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB是不会回滚的。如果ServiceB.methodB失败回滚,如果他抛出的异常被ServiceA.methodA捕获,ServiceA.methodA事务仍然可能提交。PROPAGATION_NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
不支持当前事务。比如ServiceA.methodA的事务级别是PROPAGATION_REQUIRED,而ServiceB.methodB的事务级别是PROPAGATION_NOT_SUPPORTED,那么当执行到ServiceB.methodB时,ServiceA.methodA的事务挂起,而ServiceB.methodB则以非事务的方式运行完,再继续ServiceA.methodA的事务。PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,则抛出异常。
不能在事务中运行。假设ServiceA.methodA的事务级别是PROPAGATION_REQUIRED,而ServiceB.methodB的事务级别是PROPAGATION_NEVER, 那么ServiceB.methodB就要抛出异常了。PROPAGATION_NESTED支持当前事务,新增Savepoint点,与当前事务同步提交或回滚。
理解Nested的关键是savepoint。他与PROPAGATION_REQUIRES_NEW的区别是,PROPAGATION_REQUIRES_NEW另起一个事务,将会与他的父事务相互独立,而Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,他也要回滚的。 而Nested事务的好处也是他有一个savepoint。



Java实例

为了简化操作,这里采用MySQL数据库,InnoDB引擎,两张数据库表,DDL如下: 

DROP DATABASE IF EXISTS transaction_propagation;CREATE DATABASE transaction_propagation;USE transaction_propagation;DROP TABLE IF EXISTS user1;CREATE TABLE user1 (id INT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(128)) ENGINE=INNODB CHARSET=UTF8;DROP TABLE IF EXISTS user2;CREATE TABLE user2 (id INT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(128)) ENGINE=INNODB CHARSET=UTF8;

在Java侧,主要代码及配置文件如下:

package com.my.study.springbasics.transaction;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.stereotype.Repository;@Repositorypublic class User1DaoImpl implements IUser1Dao {@Autowiredprivate JdbcTemplate jdbcTempalte;public void addUser(String name) throws Exception {jdbcTempalte.execute("insert into user1 (name) values (' " + name+ "')");}}package com.my.study.springbasics.transaction;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Propagation;import org.springframework.transaction.annotation.Transactional;@Service@Transactional(propagation = Propagation.REQUIRED)public class User1Service {@Autowiredprivate IUser1Dao user1Dao;@Autowiredprivate User2Service user2Service;public void addUser(String name) throws Exception {user1Dao.addUser(name);user2Service.addUser(name);}}package com.my.study.springbasics.transaction;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.stereotype.Repository;@Repositorypublic class User2DaoImpl implements IUser2Dao {@Autowiredprivate JdbcTemplate jdbcTempalte;public void addUser(String name) throws Exception {jdbcTempalte.execute("insert into user2 (name) values (' " + name+ "')");}}package com.my.study.springbasics.transaction;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Propagation;import org.springframework.transaction.annotation.Transactional;@Service@Transactional(propagation = Propagation.NESTED)public class User2Service {@Autowiredprivate IUser2Dao user2Dao;public void addUser(String name) throws Exception {user2Dao.addUser(name);}}package com.my.study.springbasics.transaction;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class TestTransaction {public static void main(String[] args) {@SuppressWarnings("resource")ApplicationContext context = new ClassPathXmlApplicationContext("com/my/study/springbasics/transaction/transaction_propagation.xml");User1Service userService = context.getBean(User1Service.class);try {userService.addUser("user");} catch (Exception e) {e.printStackTrace();}}}

<?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:p="http://www.springframework.org/schema/p"xmlns:context="http://www.springframework.org/schema/context"xmlns:task="http://www.springframework.org/schema/task" xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsdhttp://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.1.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd "><context:component-scan base-package="com.my.study.springbasics.transaction" /><bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"destroy-method="close"><property name="jdbcUrl"value="jdbc:mysql://localhost:3306/transaction_propagation?useUnicode=true&characterEncoding=UTF-8&"></property><property name="driverClass" value="com.mysql.jdbc.Driver"></property><property name="user" value="root"></property><property name="password" value="root"></property><property name="acquireIncrement" value="3"></property><property name="initialPoolSize" value="2"></property><property name="maxIdleTime" value="60"></property><property name="maxPoolSize" value="50"></property><property name="minPoolSize" value="2"></property><property name="acquireRetryDelay" value="1000"></property><property name="acquireRetryAttempts" value="30"></property><property name="checkoutTimeout" value="0" /><property name="breakAfterAcquireFailure" value="false" /></bean><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="dataSource" /></bean><bean id="txManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"p:dataSource-ref="dataSource"></bean><tx:annotation-driven transaction-manager="txManager" /></beans>

通过上面的代码可以看出,在User1Service中调用了User2Service的方法,下面我们通过改变User1Service和User2Service的事务传播类型来分析事务的执行过程。另外,为了得到详细的日志结果,这里使用了Log4j日志框架,并且日志Level为debug。

User1ServiceUser2Service

Main Logs

REQUIREDREQUIRED无事务REQUIREDREQUIREDREQUIRES_NEW无事务SUPPORTSREQUIREDSUPPORTSREQUIREDMANDATORY无事务MANDATORYREQUIREDNOT_SUPPORTED无事务NOT_SUPPORTEDREQUIREDNEVER无事务NEVERREQUIREDNESTED


回滚机制:

默认情况下,会自动回滚运行时异常,对于非运行时异常不会回滚,但可以在定义事务的时候设置,哪些类型的异常会回滚,哪些不会,可通过制定类型名称,或者异常名字符串来设定。

0 0
原创粉丝点击