文章目录
Spring 声明式事务的支持
- 编程式事务 :在业务代码中添加事务控制代码,这样的事务控制机制就叫做编程式事务
- 声明式事务 :通过xml或者注解配置的方式达到事务控制的目的,叫做声明式事务
事务回顾
事务的概念
事务是指逻辑上的一组操作,组成这组操作的各个单元,要么全部成功,要么全部失败,从而确保了数据的准确和安全
例如:A ——> B 转账,对应的SQL语句
# 转出账号扣钱
update account set money = (money - 100) where name = 'A';
# 转入账号加钱
update account set money = (money + 100) where name = 'B';
这两句语句的执行,要么全部成功,要么全部不成功
事务的四大特性ACID
-
原子性(Atomicity)
原子性指是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
从操作的角度来描述,事务中的各个操作要么都成功,要么都失败。
-
一致性(Consistency)
事务必须使数据库从一个一致性状态变换到另一个一致性状态。
例如转账前A有1000,B有1000,转账后A+B还得是2000。
一致性是从数据的角度来说的,(1000,1000)=> (900,1100),不应该出现(900,1000)
-
隔离性(Isolation)
事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,每个事务不能被其他事务的操作数据所干扰,多个并发事务之间要互相隔离。
比如:事务1: A余额有1000,B给A转账1000,但是事务并没有提交,事务2发起查询余额,发现余额已经是2000了,读到了事务1中尚未提交的数据(脏读)
-
持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不因该对其有任何影响。
事务的隔离级别
不考虑隔离级别,会出现以下情况(以下情况全是错误的),也即为隔离级别在解决事务并发问题:
-
脏读
一个线程中的事务读到了另一个线程中未提交的事务
场景:如上面隔离性的例子
-
不可重复读
一个线程中的事务读到了另一个线程中已经提交的update数据(前后数据不一样)
场景:
A余额有1000,事务1发起查询,得到余额1000,此时事务1并未关闭;事务2修改了A的余额为900,提交了事务,事务1再次发起查询,得到余额900,与第一次查询的1000不一样,原来读出来的1000读不到了,叫做不可重复读
-
幻读
一个线程中的事务读到了另一个线程中已经提交的insert或者delete的数据(前后数据条数不一样)
场景:
事务1查询余额为1000的人,总数有10个,此时事务尚未关闭
事务2新增了2个余额为1000的人,并且提交了事务
事务1再次查询余额为1000的人,总数有12个,跟之前查出来的条数不一致
数据库定义了四种隔离级别,从隔离级别的低到高:
-
读未提交(Read umcommmitted)
最低级别,无法保证脏读、不可重复读、幻读的情况
-
读提交(Read committed)
可避免脏读的情况发生,不可重复读和幻读一定会发生
-
可重复读(Repeatable read)
可避免脏读、不可重复读发生,幻读有可能发生
-
串行化(Serializable)
可避免以上所有情况
注意:隔离的越严实,效率就越低
MySQL的默认隔离级别是可重复读(Repeatable read)
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
设置MySQL事务的隔离级别:(设置的是当前MySQL连接会话,并不是永久改变的)
set session transaction isolation level;
Spring事务的7种传播行为
事务往往在service层进行控制,如果service层的一个方法A调用了另一个方法B,这两个方法本身都已经添加了事务控制,那么A调用B时,就需要进行事务的一些协商,这叫做事务的传播行为
A调用B,我们站在B的角度来观察和定义事务的传播行为:
事务行为 | 说明 |
---|---|
PROPAGATION_REQUIRED | 表示当前方法必须运行在事务中。如果已经存在一个事务,方法将会在该事务中运行。否则,会启动一个新的事务。(增删改最常见的选择) |
PROPAGATION_SUPPORTS | 表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行。(查询最常见的选择) |
PROPAGATION_MANDATORY | 表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常 |
PROPAGATION_REQUIRES_NEW | 表示当前方法必须运行在它自己的事务中,会新建一个事务,假设当前存在事务。把当前事务挂起 |
PROPAGATION_NOT_SUPPORTED | 表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用 |
PROPAGATION_NEVER | 以非事务方式运行,假设当前存在事务,则抛出异常 |
PROPAGATION_NESTED | 表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。 |
Spring 中事务的API
org.springframework.transaction.PlatformTransactionManager
事务的根接口,提供了以下方法:
public interface PlatformTransactionManager extends TransactionManager {
/**
* 获取事务状态信息
*/
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException;
/**
* 提交事务
*/
void commit(TransactionStatus status) throws TransactionException;
/**
* 回滚事务
*/
void rollback(TransactionStatus status) throws TransactionException;
}
此接口是Spring的事务管理器的核心接口。Spring本身并不支持事务实现,只是负责提供标准,应用底层支持什么样的事务,需要提供具体实现类。在Spring框架中,也为我们内置了一些策略,例如:
DataSourceTransactionManager
、HibernateTransactionManager
等。
如果持久层框架使用的是 SpringJdbcTemplate
/ MyBatis(mybatis-spring.jar)
则可以使用DataSourceTransactionManager
,Hibernate
则使用 HibernateTransactionManager
(在spring-orm-5.1.12.RELEASE.jar 中)
Spring 声明式事务配置
纯xml模式
-
导入依赖
<!-- 引入spring ioc容器功能--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.5.RELEASE</version> </dependency> <!-- 引入AOP --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.2.5.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.5</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.2.5.RELEASE</version> </dependency>
-
applicationContext.xml配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd"> ... <!-- spring声明式事务配置,声明式事务无非就是配置一个AOP 只不过有一些标签不一样罢了 --> <!-- spring提供的事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"/> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <!--定制事务细节,传播⾏为、隔离级别等--> <tx:attributes> <!--⼀般性配置--> <tx:method name="*" read-only="false" propagation="REQUIRED" isolation="DEFAULT" timeout="-1"/> <!--针对查询的覆盖性配置--> <tx:method name="query*" read-only="true" propagation="SUPPORTS"/> </tx:attributes> </tx:advice> <aop:config> <!--advice-ref指向增强=横切逻辑+⽅位--> <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.test.service.impl.TransferServiceImpl.*(..))"/> </aop:config> </beans>
基于xml+注解
-
xml配置
<!--配置事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManage r"> <property name="dataSource" ref="dataSource"></property> </bean> <!--开启spring对注解事务的⽀持--> <tx:annotation-driven transaction-manager="transactionManager"/>
-
在接口、类或者方法上添加**@Transactional** 注解
@Transactional(readOnly = true,propagation = Propagation.SUPPORTS)
基于纯注解
基于上面的xml+注解方式,只需把xml的配置删掉
<!--开启spring对注解事务的⽀持-->
<tx:annotation-driven transaction-manager="transactionManager"/>
然后在Spring的配置类上添加 @EnableTransactionManagement 即可