声明式事务
在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);
}
}
测试前的数据情况:
- 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
- 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
- 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
- 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
- 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
- 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
- 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_NESTED和PROPAGATION_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 。