1. 事务的传播属性
# 事务传播属性
事务传播:就是在多个业务层之间相互调用时传递事务的过程称之为事务传播
将事务对象在业务层之间进行传递的过程
(本来不同的业务层是不同的事务对象,那么我们在一个业务层就不能调用其他业务层的方法了,但是spring框架提供了
事务传播属性,可以在一个事务层调用其他事务层的方法时将事务对象也传递过去,这样即使调用了别的事务层,但是由于
事务对象是一样的,那么这就成为了一个原子操作,这样这两个(多个)事务要么一起成功,要么有一个失败就全部回滚),
这就称之为事务的传播属性。
本来不同的业务层时不能相互调用的,但是sprong框架引入了事务的传播属性,事物的传播属性可以使不同的业务层之间
相互调用,进而达到了不同的业务层可以相互调用的结果。
- propagation:事务传播属性
REQUIRED:需要事务 如果外层没有事务 则开启新的事务 如果外层存在事务,则融入当前事务
SUPPORTS:支持事务 如果外层没有事务 不会开启新的事务 如果外层存在事务,则融入当前事务
REQUIRES_NEW:每次开启新的事务 如果外层存在事务,外层事务挂起,自己开启新的事务执行,执行完成,恢复外部事务继续执行
NOT_SUPPORTED:不支持事务 如果外层存在事务,外层事务挂起,自己以非事务方式执行,执行完成,恢复外部事务执行
NEVER:不能有事务 存在事务报错
MANDATORY:强制事务 没有事务报错
NESTED:嵌套事务 事务之间可以嵌套运行 数据库 oracle mysql 不支持
上面四个属性用的比较多,下面三个属性用的比较少。
- 一般在给只需要给查询方法的propagation属性设置为SUPPORTS即可,增、删、改方法一般都不用额外加
<!--增、删、改一般设置为REQUIRED(默认,所以可以不写propagation这个属性),查询一般设置为SUPPORTS-->
<tx:method name="save*"/>
<tx:method name="update*"/>
<tx:method name="delete*"/>
<tx:method name="find*" propagation="SUPPORTS"/>
由外层方法把事务对象传给内层业务调用的过程(其实本质传的是connection连接对象),称之为事务的传播。
propagation:事务传播属性
- REQUIRED
- REQUIRED:需要事务 如果外层没有事务 则开启新的事务 如果外层存在事务,则融入当前事务
- spring中事务属性的默认值
- 要么一起成功,要么只要有一个失败就一起回滚
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
- SUPPORTS
- SUPPORTS:支持事务 如果外层没有事务 不会开启新的事务 如果外层存在事务,则融入当前事务
- 一般不用于增删改,因为没有谁主动开启事务,一直没有事务管理,那数据不就乱套了嘛
- 一般用于查询操作(查询操作一般没有事务配置,事务属性一般设置为SUPPORTS),给find方法配一个SUPPORTS,不是说要给find方法加事务,而是让它去融入事务,支持事务传播
<tx:method name="save*" propagation="SUPPORTS"/>
<tx:method name="update*" propagation="SUPPORTS"/>
注意:SUPPORTS一般用于查询操作(查询操作一般没有事务配置,事务属性一般设置为SUPPORTS),给find方法配一个SUPPORTS,不是说要给find方法加事务,而是让它去融入事务,支持事务传播。
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="delete*"/>
<tx:method name="find*" propagation="SUPPORTS"/>
- REQUIRES_NEW
- 每次开启新的事务 如果外层存在事务,外层事务挂起,自己开启新的事务执行,执行完成,恢复外部事务继续执行
- 不关心外层有没有事务,有也好,没有也好,我每次都会开启一个新的。你没有,我也不管你,我就开启一个新的,以事务的方式(开启的新事务)运行,运行完之后外部正常走;如果外部有事务,我也会自己开一个新的,我先走,这个新的走完之后恢复外部的事务,相当于外部有事务的话,外部的事务必须等待我自己的事务走完之后你外部的事务才能走。
- 这种方式和外部的事务完全是两个连接对象,内部事务的出错不会影响外部事务,外部事务的出错也不会影响内部事务,以独立的事务方式运行。但是内部事务运行的时候外部事务得堵塞,我执行完了才放行你。
- 在做银行日志系统时,去给数据加日志的时候会用到这个。在去银行取钱时,无论出错了也好,它都会有一笔交易记录,这个日志事务的运行就要独立于额外的业务系统,取钱钱不够了或者转账失败了会发现转账这个功能回滚了,但是会留了一个转账记录。针对于日志这个功能,一般都是以自己的事务方式运行
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRES_NEW"/>
-
NOT_SUPPORTED
- NOT_SUPPORTED:不支持事务 如果外层存在事务,外层事务挂起,自己以非事务方式执行,执行完成,恢复外部事务执行。
<tx:method name="save*" propagation="NEVER"/> <tx:method name="update*" propagation="NOT_SUPPORTED"/>
- NEVER
- NEVER:不能有事务 存在事务报错
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="NEVER"/>
- MANDATORY
- 强制事务 没有事务报错
<tx:method name="save*" propagation="MANDATORY"/>
<tx:method name="update*" propagation="NEVER"/>
- NESTED
- NESTED:嵌套事务 事务之间可以嵌套运行 数据库 oracle mysql 不支持
- 到现在为止关系型数据库支持对这个都不太友好
实际操作:
需要用到的类及结构:
AService接口
public interface AService {
void save();
String find();
}
BService接口
public interface BService {
void update();
String find();
}
BService实现类
public class BServiceImpl implements BService{
@Override
public void update() {
System.out.println("BService Update");
}
@Override
public String find() {
System.out.println("BService Find");
return "BService find";
}
}
AService实现类
public class AServiceImpl implements AService{
private BService bbbService;
public void setBbbService(BService bbbService) {
bbbService.update();
this.bbbService = bbbService;
}
@Override
public void save() {
System.out.println("AService Save");
}
@Override
public String find() {
System.out.println("AService Find");
return "AService find";
}
}
spring.xml配置文件
<?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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--创建数据源-->
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/lb?characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!--创建sqlSessionFactoy-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactory">
<!--注入datasource mapperLocations typeAliasesPackage-->
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath:com/baizhi/mapper/*.xml"/>
<property name="typeAliasesPackage" value="com.baizhi.eneity"/>
</bean>
<!--创建DAO-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--注入sqlSessionFactory dao接口所在包-->
<!--value要的是sqlSessionFactory在工厂中的名字-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="com.baizhi.dao"/>
</bean>
<!--创建事务管理器-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--创建事务环绕通知并进行事务细粒度控制-->
<!--transaction-manager找ref -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--
propagation:事务传播属性
REQUIRED:需要事务 如果外层没有事务 则开启新的事务 如果外层存在事务,则融入当前事务
SUPPORTS:支持事务 如果外层没有事务 不会开启新的事务 如果外层存在事务,则融入当前事务
REQUIRES_NEW:每次开启新的事务 如果外层存在事务,外层事务挂起,自己开启新的事务执行,执行完成,恢复外部事务继续执行
NOT_SUPPORTED:不支持事务 如果外层存在事务,外层事务挂起,自己以非事务方式执行,执行完成,恢复外部事务执行
NEVER:不能有事务 存在事务报错
MANDATORY:强制事务 没有事务报错
NESTED:嵌套事务 事务之间可以嵌套运行 数据库 oracle mysql 不支持
-->
<!--下面这两行propagation的值不确定,会根据实际的测试而修改-->
<tx:method name="save*" propagation="NESTED"/>
<tx:method name="update*" propagation="NEVER"/>
<tx:method name="delete*"/>
<tx:method name="find*" propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
<!--配置事务切面-->
<aop:config>
<aop:pointcut id="pc" expression="execution(* com.baizhi.service.*ServiceImpl.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/>
</aop:config>
<!--管理Service层组件-->
<bean class="com.baizhi.service.UserServiceImpl" id="userService">
<property name="userDAO" ref="userDAO"/>
</bean>
<bean class="com.baizhi.service.BServiceImpl" id="bService"></bean>
<bean class="com.baizhi.service.AServiceImpl" id="aService">
<property name="bbbService" ref="bService"/>
</bean>
</beans>
测试类
public class TestAservice {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
AService aService = (AService) context.getBean("aService");
aService.save();
}
}
REQUIRED:需要事务 如果外层没有事务 则开启新的事务 如果外层存在事务,则融入当前事务
SUPPORTS:支持事务 如果外层没有事务 不会开启新的事务 如果外层存在事务,则融入当前事务
REQUIRES_NEW:每次开启新的事务 如果外层存在事务,外层事务挂起,自己开启新的事务执行,执行完成,恢复外部事务继续执行
NOT_SUPPORTED:不支持事务 如果外层存在事务,外层事务挂起,自己以非事务方式执行,执行完成,恢复外部事务执行
NEVER:不能有事务 存在事务报错
MANDATORY:强制事务 没有事务报错
NESTED:嵌套事务 事务之间可以嵌套运行 数据库 oracle mysql 不支持
- REQUIRED:需要事务
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
- SUPPORTS:支持事务
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="SUPPORTS"/>
<!--不给save方法配置事务,也就是此时save方法不会开启事务,外部无事务,update方法也不会开启新事务,update以非事务的方式运行-->
<!-- <tx:method name="save*" propagation="REQUIRED"/>-->
<tx:method name="update*" propagation="SUPPORTS"/>
- REQUIRES_NEW:每次开启新的事务
- 不管外部有没有事务,我自己都创建一个新的事务
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRES_NEW"/>
- NOT_SUPPORTED:不支持事务
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="NOT_SUPPORTED"/>
- NEVER:不能有事务 存在事务报错
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="NEVER"/>
- MANDATORY:强制事务 没有事务报错
<!--save的事务去掉,对于update方法来说外部无事务-->
<!--<tx:method name="save*" propagation="REQUIRED"/>-->
<tx:method name="update*" propagation="MANDATORY"/>
- NESTED:嵌套事务 事务之间可以嵌套运行 数据库 oracle mysql 不支持
- 这个没办法演示,因为现在的数据库也不支持
2. 事务的其他属性
关于事务的隔离级别以及脏读、不可重复读、幻读是什么,建议大家参考一下我以前写的博客,里面有更清晰的详解:
# 事务的隔离级别
isolation :事务隔离级别
DEFAULT: 使用数据库默认的隔离级别 [推荐]
READ_UNCOMMITTED: 读未提交 一个客户端读到了另一个客户端没有提交的数据 脏读现象
READ_COMMITTED : 读提交 一个客户端只能读到另一个客户端提交的数据 避免脏读现象 [oracle默认的隔离级别]
REPEATABLE_READ : 可重复读 主要是用来避免不可重复读实现的 行锁 [mysql默认的隔离级别]
SERIALIZABLE : 序列化读 主要是用来避免幻影读现象的出现 表锁
注意:隔离级别越高,查询效率越低 一般推荐使用数据库默认隔离级别
# tx:method标签的isolation属性
在spring中可以给tx:method标签的isolation属性赋值设置隔离级别
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="save*" isolation="DEFAULT"/>
<tx:method name="update*"/>
<tx:method name="delete*"/>
<tx:method name="find*" propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
# tx:method标签的read-only属性
read-only:事务读写性 true 只读 不能执行增删改操作 false: 可读可写(mysql支持,oracle不支持)
read-only这里一般都不加
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="save*" isolation="DEFAULT" read-only="true"/>
<tx:method name="update*"/>
<tx:method name="delete*"/>
<tx:method name="find*" propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
# tx:method标签的rollback-for属性
rollback-for:出现什么类型异常回滚 默认出现RuntimeException(运行时异常)及其子类异常回滚
一般不设置
<tx:method name="save*" rollback-for=""/>
# tx:method标签的no-rollback-for属性
no-rollback-for:出现什么类型异常不回滚
一般也不会做设置
<tx:method name="save*" no-rollback-for="">
# tx:method标签的timeout属性
timeout:事务超时性 -1 永不超时(默认)
设置>=0正整数 代表设置超时时间 单位秒 假如设置1秒,1秒还没执行完,直接报错
一般我们也不会修改timeout这个属性
在演示事务传播时经常会出现外部事务挂起,内部事务执行,那外部事务挂起,它挂起多长时间,如果内部事务阻塞,外部事务得一直挂起
如果没有做任何配置,内部事务没有执行完,外部事务就老老实实的等待,永不超时
<tx:method name="save*" timeout="-1"/>
测试类:
@Test
public void testSave(){
UserService userService = (UserService) context.getBean("userService");
User user = new User();
user.setBir(new Date());
user.setName("小陈");
user.setAge(23);
userService.save(user);
}
- 演示read-only
<tx:method name="save*" read-only="true"/>
- 演示no-rollback-for
<!--设置出现运行时异常不回滚-->
<tx:method name="save*" no-rollback-for="java.lang.RuntimeException"/>