Spring的7种传播特性

声明式事务

在Spring中,声明式事务是用事务参数来定义的。一个事务参数就是对事务策略应该如何应用到某个方法的一段描述,如下表所示一个事务参数共有7个方面组成:
查下接口TransactionDefinition.java(版本4.1.2),一共有7中传播特性:

public interface TransactionDefinition {
	int PROPAGATION_REQUIRED = 0;
	int PROPAGATION_SUPPORTS = 1;
	int PROPAGATION_MANDATORY = 2;
	int PROPAGATION_REQUIRES_NEW = 3;
	int PROPAGATION_NOT_SUPPORTED = 4;
	int PROPAGATION_NEVER = 5;
	int PROPAGATION_NESTED = 6;
	//省略
}
传播行为(PROPAGATION)描述
PROPAGATION_REQUIRED如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。
PROPAGATION_SUPPORTS支持当前事务,如果没有当前事务,就以非事务方法执行。
PROPAGATION_MANDATORY使用当前事务,如果没有当前事务,就抛出异常。
PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER以非事务方式执行操作,如果当前事务存在则抛出异常。
PROPAGATION_NESTED如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED 类似的操作

spring默认是PROPAGATION_REQUIRED

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
	//省略
	Propagation propagation() default Propagation.REQUIRED;
	//省略
}

public enum Propagation {
	REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
	SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
	MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
	REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
	NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
	NEVER(TransactionDefinition.PROPAGATION_NEVER),
	NESTED(TransactionDefinition.PROPAGATION_NESTED);
	private final int value;
	Propagation(int value) { this.value = value; }
	public int value() { return this.value; }
}

示例:

建表account

CREATE TABLE `account` (
  `userid` varchar(64) NOT NULL,
  `username` varchar(64) NOT NULL,
  `accountbalance` decimal(10,2) NOT NULL DEFAULT '0.00',
  `createtime` datetime DEFAULT NULL,
  `updatetime` datetime DEFAULT NULL,
  PRIMARY KEY (`userid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `account` VALUES ('1001', '张三', '1000.00', '2018-11-09 09:39:52', '2018-11-09 09:39:55');
INSERT INTO `account` VALUES ('1002', '李四', '1000.00', '2018-11-09 09:40:12', '2018-11-09 09:40:14');

新建maven工程,引jar包

<!-- 编码 -->
	<properties>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
		<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
		<jdk.version>1.8</jdk.version>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<spring.version>4.1.2.RELEASE</spring.version>
	</properties>

	<dependencies>
		<!--Spring -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aop</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context-support</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-beans</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aspects</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<!--mysql驱动包 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.47</version>
		</dependency>
		<!--druid连接池 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.1.10</version>
		</dependency>
	</dependencies>

配置applicationContext.xml和jdbc.properties

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/zhd?useUnicode=true&characterEncoding=UTF-8&InnoDB=true&useSSL=false
jdbc.username=root
jdbc.password=123456
<?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:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans   
        http://www.springframework.org/schema/beans/spring-beans.xsd    
        http://www.springframework.org/schema/mvc   
        http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd    
        http://www.springframework.org/schema/context   
        http://www.springframework.org/schema/context/spring-context.xsd  
        http://www.springframework.org/schema/tx    
        http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">

	<!-- 开启注解 -->
	<mvc:annotation-driven />
	<!-- 配置自定扫描包 -->
	<context:component-scan base-package="com.zhd.cpexploit.*" />
	
	<!-- Mybatis 和 Spring的整合 -->
	<context:property-placeholder location="classpath:/jdbc.properties" />
	<!-- 1.数据源:DriverManagerDataSource -->
	<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
		<property name="driverClassName" value="${jdbc.driverClassName}"></property>
		<property name="url" value="${jdbc.url}"></property>
		<property name="username" value="${jdbc.username}"></property>
		<property name="password" value="${jdbc.password}"></property>

		<property name="maxActive" value="${maxActive}" />
		<property name="initialSize" value="${initialSize}" />
		<property name="maxWait" value="${maxWait}" />
		<property name="minIdle" value="${minIdle}" />

		<!-- 验证连接是否成功, SQL SELECT 指令至少要返回一行 (测试/验证连接池连接的SQL语句也因数据库而异) -->
		<property name="validationQuery" value="${validationQuery}" />
		<!-- 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 -->
		<property name="testOnBorrow" value="${testOnBorrow}" />
		<!-- 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 -->
		<property name="testOnReturn" value="${testOnReturn}" />
		<!-- 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 -->
		<property name="testWhileIdle" value="${testWhileIdle}" />
		<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
		<property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}" />
		<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
		<property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}" />
		<!-- 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。 -->
		<property name="maxOpenPreparedStatements" value="${maxOpenPreparedStatements}" />
		<!-- 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql5.5以下的版本中没有PSCache功能,建议关闭掉。5.5及以上版本有PSCache,建议开启。 -->
		<property name="poolPreparedStatements" value="${poolPreparedStatements}" />
		<!-- 指定每个PSCache连接上PSCache的大小 -->
		<property name="maxPoolPreparedStatementPerConnectionSize"
			value="${maxPoolPreparedStatementPerConnectionSize}" />
		<!-- 配置监控统计拦截的filters 去掉后监控界面sql无法统计 开启web监控、开启sql防火墙 -->
		<property name="filters" value="${filters}"></property>

	</bean>

	<!-- 2.Mybatis 的 SqlSession的工厂:SqlSessionFactoryBean dataSource引用数据源 Mybatis 
		定义数据源,同意加载配置 -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource"></property>
		<!-- 自动扫描mapping.xml文件 -->
		<property name="mapperLocations" value="classpath:META/sqlmap/*.xml"></property>
		<property name="configLocation" value="classpath:mybatis-config.xml"></property>
	</bean>


	<!-- 3. Mybatis自动扫描加载Sql映射文件/接口:MapperScannerConfigurer sqlSessionFactory 
		basePackage:指定sql映射文件/接口所在的包(自动扫描) -->
	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<property name="basePackage" value="com.zhd.cpexploit.mapper"></property>
		<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
	</bean>
	<!-- 4.JdbcTemplate -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>

	<!-- 5.事务管理:DataSourceTransactionManager dataSource 引用上面定义好的数据源 -->
	<bean id="txManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"></property>
	</bean>

	<!-- 6.使用声明式事务: transaction-manager = "txManager" tx:advice 这种 是用 aop方式管理事物 
		annotation-driven 这种是注解方式管理事物 第一种方式,需要在spring配置文件配置一些参数 第二种方式,需要在 类里 加一些注解进行事物管理 
		用一种就行,没必须都用 -->
	<tx:annotation-driven transaction-manager="txManager" />
</beans>

新建TestService和TestServiceImpl

public interface TestService {
	
	public void doWork(Boolean isExce);
}

@Service
public class TestServiceImpl implements TestService {
	@Autowired
	private JdbcTemplate jdbcTemplate;

	@Override
	@Transactional(propagation = Propagation.REQUIRED)
	public void doWork(Boolean isExce) {
		String sql = "update account set accountbalance = accountbalance + ? where userid = ?";
		jdbcTemplate.update(sql, 10, "1001");
		if (isExce) {
			throw new RuntimeException();
		}
		jdbcTemplate.update(sql, -10, "1002");
	}

}

测试类:

public class SpringTest {
	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
		TestService testService = context.getBean(TestService.class);
		try {
			testService.doWork(false);
		} catch (Exception e) {
			System.out.println("异常");
		}
		System.out.println("success");
	}
}

测试事务的传播特性,每次测试完,账户金额恢复为1000
A调用B,对B的propagation进行7中情况测试

public interface TestServiceA {
	public void doWorkA(Boolean isExce);
}
public interface TestServiceB {
	public void doWorkB(Boolean isExce);
}

@Service
public class TestServiceAImpl implements TestServiceA {
	@Autowired
	private JdbcTemplate jdbcTemplate;
	@Autowired
	private TestServiceB testServiceB;

	@Override
	@Transactional(propagation = Propagation.REQUIRED)
	public void doWorkA(Boolean isExce) {
		System.out.println("进入方法doWorkA");
		String sql = "update account set accountbalance = accountbalance + ? where userid = ?";
		jdbcTemplate.update(sql, 10, "1001");
		testServiceB.doWorkB(isExce);
		jdbcTemplate.update(sql, -10, "1002");
		System.out.println("方法doWorkA结束");
	}
}
@Service
public class TestServiceBImpl implements TestServiceB {
	@Autowired
	private JdbcTemplate jdbcTemplate;

	@Override
	@Transactional(propagation = Propagation.REQUIRED)
	public void doWorkB(Boolean isExce) {
		System.out.println("进入方法doWorkB");
		String sql = "update account set accountbalance = accountbalance + ? where userid = ?";
		jdbcTemplate.update(sql, 10, "1003");
		if (isExce) {
			throw new RuntimeException();
		}
		jdbcTemplate.update(sql, -10, "1004");
		System.out.println("方法doWorkB结束");
	}
}

测试类:

public class SpringTest {
	public static void main(String[] args) {
//		init();
		doWork();

	}

	private static void doWork() {
		ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
		TestServiceA testServiceA = context.getBean(TestServiceA.class);
		try {
			testServiceA.doWorkA(true);
		} catch (Exception e) {
			System.out.println("异常:"+e.getClass().getName());
		}
		System.out.println("success");
	}

	private static void init() {
		ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
		JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class);
		String sql = "update  `account` t set t.accountbalance=1000";
		jdbcTemplate.update(sql);
	}
}

测试前的数据情况:
在这里插入图片描述

  1. propagation =Propagation.REQUIRED

情况一:

public void doWorkA(Boolean isExce) {...}
@Transactional(propagation = Propagation.REQUIRED)
public void doWorkB(Boolean isExce) {...}

测试结果:

进入方法doWorkA
进入方法doWorkB
异常:java.lang.RuntimeException
success

在这里插入图片描述

情况二:

@Transactional(propagation = Propagation.REQUIRED)
public void doWorkA(Boolean isExce) {...}
@Transactional(propagation = Propagation.REQUIRED)
public void doWorkB(Boolean isExce) {...}

测试结果:

进入方法doWorkA
进入方法doWorkB
异常:java.lang.RuntimeException
success

在这里插入图片描述

  1. propagation =Propagation.SUPPORTS

情况一:

public void doWorkA(Boolean isExce) {...}
@Transactional(propagation = Propagation.SUPPORTS)
public void doWorkB(Boolean isExce) {...}

测试结果:

进入方法doWorkA
进入方法doWorkB
异常:java.lang.RuntimeException
success

在这里插入图片描述
情况二:

@Transactional(propagation = Propagation.REQUIRED){...}
public void doWorkA(Boolean isExce) 
@Transactional(propagation = Propagation.SUPPORTS){...}
public void doWorkB(Boolean isExce) 

测试结果:

进入方法doWorkA
进入方法doWorkB
异常:java.lang.RuntimeException
success

在这里插入图片描述

  1. propagation =Propagation.MANDATORY

情况一:

public void doWorkA(Boolean isExce) {...}
@Transactional(propagation = Propagation.MANDATORY)
public void doWorkB(Boolean isExce) {...}

测试结果:
外围没有事务,B抛出异常

进入方法doWorkA
异常:org.springframework.transaction.IllegalTransactionStateException
success

在这里插入图片描述

情况二:

@Transactional(propagation = Propagation.REQUIRED){...}
public void doWorkA(Boolean isExce) 
@Transactional(propagation = Propagation.MANDATORY){...}
public void doWorkB(Boolean isExce) 

测试结果:

进入方法doWorkA
进入方法doWorkB
异常:java.lang.RuntimeException
success

在这里插入图片描述

  1. propagation =Propagation.REQUIRES_NEW

情况一:

public void doWorkA(Boolean isExce) {...}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void doWorkB(Boolean isExce) {...}

测试结果:

进入方法doWorkA
进入方法doWorkB
异常:java.lang.RuntimeException
success

在这里插入图片描述

情况二:

@Transactional(propagation = Propagation.REQUIRED){...}
public void doWorkA(Boolean isExce) 
@Transactional(propagation = Propagation.REQUIRES_NEW){...}
public void doWorkB(Boolean isExce) 

测试结果:

进入方法doWorkA
进入方法doWorkB
异常:java.lang.RuntimeException
success

在这里插入图片描述

  1. propagation =Propagation.NOT_SUPPORTED

情况一:

public void doWorkA(Boolean isExce) {...}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void doWorkB(Boolean isExce) {...}

测试结果:
NOT_SUPPORTED表示不支持事务

进入方法doWorkA
进入方法doWorkB
异常:java.lang.RuntimeException
success

在这里插入图片描述

情况二:

@Transactional(propagation = Propagation.REQUIRED){...}
public void doWorkA(Boolean isExce) 
@Transactional(propagation = Propagation.NOT_SUPPORTED){...}
public void doWorkB(Boolean isExce) 

测试结果:

进入方法doWorkA
进入方法doWorkB
异常:java.lang.RuntimeException
success

在这里插入图片描述

  1. propagation =Propagation.NEVER

情况一:

public void doWorkA(Boolean isExce) {...}
@Transactional(propagation = Propagation.NEVER)
public void doWorkB(Boolean isExce) {...}

测试结果:

进入方法doWorkA
进入方法doWorkB
异常:java.lang.RuntimeException
success

在这里插入图片描述

情况二:

@Transactional(propagation = Propagation.REQUIRED){...}
public void doWorkA(Boolean isExce) 
@Transactional(propagation = Propagation.NEVER){...}
public void doWorkB(Boolean isExce) 

测试结果:

进入方法doWorkA
异常:org.springframework.transaction.IllegalTransactionStateException
success

在这里插入图片描述

  1. propagation =Propagation.NESTED

情况一:

public void doWorkA(Boolean isExce) {...}
@Transactional(propagation = Propagation.NESTED)
public void doWorkB(Boolean isExce) {...}

测试结果:

进入方法doWorkA
进入方法doWorkB
异常:java.lang.RuntimeException
success

在这里插入图片描述

情况二:

@Transactional(propagation = Propagation.REQUIRED){...}
public void doWorkA(Boolean isExce) 
@Transactional(propagation = Propagation.NESTED){...}
public void doWorkB(Boolean isExce) 

测试结果:

进入方法doWorkA
进入方法doWorkB
异常:java.lang.RuntimeException
success

在这里插入图片描述

总结:PROPAGATION_NESTEDPROPAGATION_REQUIRES_NEW最容易混淆, PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 “内部” 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行。
另一方面, PROPAGATION_NESTED 开始一个 “嵌套的” 事务, 它是已经存在事务的一个真正的子事务. 潜套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交。
由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 潜套事务也会被 commit, 这个规则同样适用于 roll ba 。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值