Spring事务传播机制 - Spring transaction propagation

事务传播机制

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;

@Repository
public class User1DaoImpl implements IUser1Dao {

	@Autowired
	private 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 {

	@Autowired
	private IUser1Dao user1Dao;

	@Autowired
	private 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;

@Repository
public class User2DaoImpl implements IUser2Dao {

	@Autowired
	private 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 {

	@Autowired
	private 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.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
		http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.1.xsd
		http://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>

Log分析

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

User1Service User2Service

Logs

REQUIRED REQUIRED
无事务 REQUIRED
REQUIRED REQUIRES_NEW
无事务 SUPPORTS
REQUIRED SUPPORTS
REQUIRED MANDATORY
无事务 MANDATORY
REQUIRED NOT_SUPPORTED
事务 NOT_SUPPORTED
REQUIRED NEVER
无事务 NEVER
REQUIRED NESTED

事务回滚

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

猜你喜欢

转载自blog.csdn.net/funnyrand/article/details/45223437