Spring支持的事务管理
编程式事务管理
声明式事务管理
- 基于注解方式实现
- 基于xml配置文件实现
基于注解方式实现
- 在spring配置文件配置事务管理器
<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="jdbc:mysql:///user_db" />
<property name="username" value="root" />
<property name="password" value="root" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>
<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- JdbcTemplate对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
- 在spring配置文件开启事务注解
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
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/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
```
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
```
- 在service类上面(或者service类里面方法上面)添加事务注解 (1)@Transactional,这个注解添加到类上面,也可以添加方法上面 (2)如果把这个注解添加类上面,这个类里面所有的方法都添加事务 (3)如果把这个注解添加方法上面,为这个方法添加事务
@Service
@Transactional
public class UserService {
基于xml配置文件实现
第一步 配置事务管理器
<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="jdbc:mysql:///user_db" />
<property name="username" value="root" />
<property name="password" value="root" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>
<!-- JdbcTemplate对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--1 创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
第二步 配置通知
<!--2 配置通知-->
<tx:advice id="txadvice">
<!--配置事务参数-->
<tx:attributes>
<!--指定哪种规则的方法上面添加事务-->
<tx:method name="accountMoney" propagation="REQUIRED"/>
<!--<tx:method name="account*"/>-->
</tx:attributes>
</tx:advice>
第三步 配置切入点和切面
<!--3 配置切入点和切面-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pt" expression="execution(* com.atguigu.spring5.service.UserService.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>
声明式事务管理(注解方式)参数管理
isolation 事务隔离级别
spring有五大隔离级别:默认值为ISOLATION_DEFAULT(使用数据库的设置),其他四个隔离级别和数据库的隔离级别一样:
- ISOLATION_DEFAULT:用底层数据库的设置隔离级别,数据库设置的是什么我就用什么
- ISOLATION_READ_UNCOMMITTED:读未提交,最低隔离级别、事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读)
- ISOLATION_READ_COMMITTED:读已提交,一个事务提交后才能被其他事务读取到(会造成幻读、不可重复度),SQL server的默认级别
- ISOLATION_REPEATABLE_READ:可重复读,一个事务提交后,并且另一个事务提交之后才可以读到上一个事务的值。mysql的默认隔离级别
- ISOLATION_SERIALIZABLE:序列化,代价最高最可靠的隔离级别,该隔离级别能防止脏读、不可重复读、幻读。
timeout 事务超时时间
所谓事务超时,就是指一个事务所允许执行地最长时间,如果超过该事件限制但事务还没有完成,则自动回滚事务。
- 事务需要在一定时间内进行提交,如果不提交则进行回滚
- 默认值是-1(这表示事务的超时时间取决于底层事务系统或者没有超时时间),设置时间以秒为单位进行计算
readOnly 是否只读
- 读:查询操作,写:添加修改删除操作
- readOnly默认值false,表示可以查询,可以添加修改删除操作
- 设置readOnly值是true,设置成true之后,只能查询
rollbackFor 回滚
设置出现哪些异常进行事务回滚,默认遇到运行时异常和Error回滚,遇到检查型异常不回滚
noRollbackFor 不回滚
设置出现哪些异常不进行事务回滚
propagation 传播行为
假设场景:现在有两个方法A和B,方法A执行会在数据库ATable插入一条数据,方法B执行会在数据BTable插入一条数据,伪代码如下:
//将传入参数a存入ATable
pubilc void A(a){
insertIntoATable(a);
}
//将传入参数b存入BTable
public void B(b){
insertIntoBTable(b);
}
方法调用关系如下:
public void testMain(){
A(a1); //调用A入参a1
testB(); //调用testB
}
public void testB(){
B(b1); //调用B入参b1
throw Exception; //发生异常抛出
B(b2); //调用B入参b2
}
REQUIRED Spring默认的事务传播类型
如果当前没有事务,则自己新建一个事务,如果当前存在事务,则加入这个事务
- demo1: testMain和testB上声明事务,设置传播行为REQUIRED
@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){
A(a1); //调用A入参a1
testB(); //调用testB
}
@Transactional(propagation = Propagation.REQUIRED)
public void testB(){
B(b1); //调用B入参b1
throw new RuntimeException(); //发生异常抛出
B(b2); //调用B入参b2
}
testMain方法执行结果:数据库没有插入新的数据,数据库还是保持着执行testMain方法之前的状态,没有发生改变。testMain上声明了事务,在执行testB方法时就加入了testMain的事务(当前存在事务,则加入这个事务),在执行testB方法抛出异常后事务会发生回滚,因为testMain和testB使用的同一个事务,所以事务回滚后testMain和testB中的操作都会回滚,也就使得数据库仍然保持初始状态
- demo2:只在testB上声明事务,设置传播行为REQUIRED
public void testMain(){
A(a1); //调用A入参a1
testB(); //调用testB
}
@Transactional(propagation = Propagation.REQUIRED)
public void testB(){
B(b1); //调用B入参b1
throw new RuntimeException(); //发生异常抛出
B(b2); //调用B入参b2
}
testMain方法执行结果:数据a1存储成功,数据b1和b2没有存储。由于testMai没有声明事务,testB有声明事务且传播行为是REQUIRED,所以在执行testB时会自己新建一个事务(如果当前没有事务,则自己新建一个事务),testB抛出异常则只有testB中的操作发生了回滚,也就是b1的存储会发生回滚,但a1数据不会回滚,所以最终a1数据存储成功,b1和b2数据没有存储成功
SUPPORTS
当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行
- demo1: 当前存在事务,则加入当前事务同 REQUIRED 中的demo1
- demo2: 只在testB上声明事务,设置传播行为SUPPORTS
public void testMain(){
A(a1); //调用A入参a1
testB(); //调用testB
}
@Transactional(propagation = Propagation.SUPPORTS)
public void testB(){
B(b1); //调用B入参b1
throw new RumtimeException(); //发生异常抛出
B(b2); //调用B入参b2
}
testMain方法执行结果:a1,b1存入数据库,b2没有存储数据库。由于testMain没有声明事务,且testB的事务传播行为是SUPPORTS,所以执行testB时就是没有事务的(如果当前没有事务,就以非事务方法执行),因此在testB抛出异常时也不会发生回滚,所以最终结果就是a1和b1存储成功,b2没有存储。
MANDATORY
当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常
- demo1: 当前存在事务,则加入当前事务同 REQUIRED 中的demo1
- demo2: 只在testB上声明事务,设置传播行为MANDATORY
public void testMain(){
A(a1); //调用A入参a1
testB(); //调用testB
}
@Transactional(propagation = Propagation.MANDATORY)
public void testB(){
B(b1); //调用B入参b1
throw new RuntimeException(); //发生异常抛出
B(b2); //调用B入参b2
}
testMain方法执行结果:a1存储成功,而b1和b2没有存储。b1和b2没有存储,并不是事务回滚的原因,而是因为testMain方法没有声明事务,在去执行testB方法时就直接抛出事务要求的异常(如果当前事务不存在,则抛出异常),所以testB方法里面的内容就没有执行。
REQUIRES_NEW
创建一个新事务,如果存在当前事务,则挂起事务 可以理解为设置事务传播类型为REQUIRES_NEW的方法,在执行时,不论当前是否存在事务,总是会新建一个事务。
@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){
A(a1); //调用A入参a1
testB(); //调用testB
throw Exception; //发生异常抛出
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void testB(){
B(b1); //调用B入参b1
B(b2); //调用B入参b2
}
testMain方法执行结果:a1没有存储,而b1和b2存储成功,因为testB的事务传播设置为REQUIRES_NEW,所以在执行testB时会开启一个新的事务,testMain中发生的异常是在testMain所开启的事务中,所以这个异常不会影响testB的事务提交,testMain中的事务会发生回滚,所以最终a1没有存储,而b1和b2存储成功了。
NOT_SUPPORTED
始终以非事务方式执行,如果当前存在事务,则挂起当前事务 testMain传播类型设置为REQUIRED,testB传播类型设置为NOT_SUPPORTED,且异常抛出位置在testB中
@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){
A(a1); //调用A入参a1
testB(); //调用testB
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void testB(){
B(b1); //调用B入参b1
throw Exception; //发生异常抛出
B(b2); //调用B入参b2
}
结果:a1和b2没有存储,而b1存储成功。testMain事务,而testB不使用事务,所以执行testB存储b1成功,然后抛出异常,此时testMain检测到异常事务发生回滚,但是由于testB不在事务中,所以只有testMain的存储a1发生了回滚,最终只有b1存储成功,而a1和b1都没有存储。
NEVER
不使用事务,如果当前存在事务,则抛出异常 testMain设置传播类型为REQUIRED,testB传播类型设置为NEVER
@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){
A(a1); //调用A入参a1
testB(); //调用testB
}
@Transactional(propagation = Propagation.NEVER)
public void testB(){
B(b1); //调用B入参b1
B(b2); //调用B入参b2
}
结果:直接抛出事务异常,且不会有数据存储到数据库。由于testMain事务传播类型为Required,所以testMain是运行在事务中,而testB事务传播类型为NEVER,所以testB不会执行而是直接抛出事务异常,此时testMain检测到异常就发生了回滚,所以最终数据库不会有数据存入。
spring事务传播行为18个示例
18个示例详解 Spring 事务传播机制-腾讯云开发者社区-腾讯云 (tencent.com)
spring事务原理
spring中事务的默认实现使用的是AOP,也就是代理的方式。