spring5 源码深度解析----- @Transactional注解的声明式事物介绍(100%理解事务)
面的几个章节已经分析了spring基于@aspectj
的源码,那么接下来我们分析一下aop的另一个重要功能,事物管理。
事务的介绍
1.数据库事物特性
- 原子性
多个数据库操作是不可分割的,只有所有的操作都执行成功,事物才能被提交;只要有一个操作执行失败,那么所有的操作都要回滚,数据库状态必须回复到操作之前的状态 - 一致性
事物操作成功后,数据库的状态和业务规则必须一致。例如:从a账户转账100元到b账户,无论数据库操作成功失败,a和b两个账户的存款总额是不变的。 - 隔离性
当并发操作时,不同的数据库事物之间不会相互干扰(当然这个事物隔离级别也是有关系的) - 持久性
事物提交成功之后,事物中的所有数据都必须持久化到数据库中。即使事物提交之后数据库立刻崩溃,也需要保证数据能能够被恢复。
2.事物隔离级别
当数据库并发操作时,可能会引起脏读、不可重复读、幻读、第一类丢失更新、第二类更新丢失等现象。
- 脏读
事物a读取事物b尚未提交的更改数据,并做了修改;此时如果事物b回滚,那么事物a读取到的数据是无效的,此时就发生了脏读。 - 不可重复读
一个事务执行相同的查询两次或两次以上,每次都得到不同的数据。如:a事物下查询账户余额,此时恰巧b事物给账户里转账100元,a事物再次查询账户余额,那么a事物的两次查询结果是不一致的。 - 幻读
a事物读取b事物提交的新增数据,此时a事物将出现幻读现象。幻读与不可重复读容易混淆,如何区分呢?幻读是读取到了其他事物提交的新数据,不可重复读是读取到了已经提交事物的更改数据(修改或删除)
对于以上问题,可以有多个解决方案,设置数据库事物隔离级别就是其中的一种,数据库事物隔离级别分为四个等级,通过一个表格描述其作用。
隔离级别 | 脏读 | 不可重复读 | 幻象读 |
---|---|---|---|
read uncommitted | 允许 | 允许 | 允许 |
read committed | 脏读 | 允许 | 允许 |
repeatable read | 不允许 | 不允许 | 允许 |
serializable | 不允许 | 不允许 | 不允许 |
3.spring事物支持核心接口
- transactiondefinition-->定义与spring兼容的事务属性的接口
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; // 如果当前存在事物,则在嵌套事物内执行;如果当前没有事物,则与propagation_required传播特性相同 int propagation_nested = 6; // 使用后端数据库默认的隔离级别。 int isolation_default = -1; // read_uncommitted 隔离级别 int isolation_read_uncommitted = connection.transaction_read_uncommitted; // read_committed 隔离级别 int isolation_read_committed = connection.transaction_read_committed; // repeatable_read 隔离级别 int isolation_repeatable_read = connection.transaction_repeatable_read; // serializable 隔离级别 int isolation_serializable = connection.transaction_serializable; // 默认超时时间 int timeout_default = -1; // 获取事物传播特性 int getpropagationbehavior(); // 获取事物隔离级别 int getisolationlevel(); // 获取事物超时时间 int gettimeout(); // 判断事物是否可读 boolean isreadonly(); // 获取事物名称 @nullable string getname(); }
- spring事物传播特性表:
传播特性名称 | 说明 |
---|---|
propagation_required | 如果当前没有事物,则新建一个事物;如果已经存在一个事物,则加入到这个事物中 |
propagation_supports | 支持当前事物,如果当前没有事物,则以非事物方式执行 |
propagation_mandatory | 使用当前事物,如果当前没有事物,则抛出异常 |
propagation_requires_new | 新建事物,如果当前已经存在事物,则挂起当前事物 |
propagation_not_supported | 以非事物方式执行,如果当前存在事物,则挂起当前事物 |
propagation_never | 以非事物方式执行,如果当前存在事物,则抛出异常 |
propagation_nested |
如果当前存在事物,则在嵌套事物内执行; 如果当前没有事物,则与propagation_required传播特性相同 |
- spring事物隔离级别表:
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
读未提交(read-uncommitted) | 是 | 是 | 是 |
不可重复读(read-committed) | 否 | 是 | 是 |
可重复读(repeatable-read) | 否 | 否 | 是 |
串行化(serializable) | 否 | 否 | 否 |
mysql默认的事务隔离级别为 可重复读repeatable-read
3.platformtransactionmanager-->spring事务基础结构中的中心接口
public interface platformtransactionmanager { // 根据指定的传播行为,返回当前活动的事务或创建新事务。 transactionstatus gettransaction(@nullable transactiondefinition definition) throws transactionexception; // 就给定事务的状态提交给定事务。 void commit(transactionstatus status) throws transactionexception; // 执行给定事务的回滚。 void rollback(transactionstatus status) throws transactionexception; }
spring将事物管理委托给底层的持久化框架来完成,因此,spring为不同的持久化框架提供了不同的platformtransactionmanager接口实现。列举几个spring自带的事物管理器:
事物管理器 | 说明 |
---|---|
org.springframework.jdbc.datasource.datasourcetransactionmanager | 提供对单个javax.sql.datasource事务管理,用于spring jdbc抽象框架、ibatis或mybatis框架的事务管理 |
org.springframework.orm.jpa.jpatransactionmanager | 提供对单个javax.persistence.entitymanagerfactory事务支持,用于集成jpa实现框架时的事务管理 |
org.springframework.transaction.jta.jtatransactionmanager | 提供对分布式事务管理的支持,并将事务管理委托给java ee应用服务器事务管理器 |
- transactionstatus-->事物状态描述
- transactionstatus接口
public interface transactionstatus extends savepointmanager, flushable { // 返回当前事务是否为新事务(否则将参与到现有事务中,或者可能一开始就不在实际事务中运行) boolean isnewtransaction(); // 返回该事务是否在内部携带保存点,也就是说,已经创建为基于保存点的嵌套事务。 boolean hassavepoint(); // 设置事务仅回滚。 void setrollbackonly(); // 返回事务是否已标记为仅回滚 boolean isrollbackonly(); // 将会话刷新到数据存储区 @override void flush(); // 返回事物是否已经完成,无论提交或者回滚。 boolean iscompleted(); }
- savepointmanager接口
public interface savepointmanager { // 创建一个新的保存点。 object createsavepoint() throws transactionexception; // 回滚到给定的保存点。 // 注意:调用此方法回滚到给定的保存点之后,不会自动释放保存点, // 可以通过调用releasesavepoint方法释放保存点。 void rollbacktosavepoint(object savepoint) throws transactionexception; // 显式释放给定的保存点。(大多数事务管理器将在事务完成时自动释放保存点) void releasesavepoint(object savepoint) throws transactionexception; }
spring编程式事物
- 表
create table `account` ( `id` int(11) not null auto_increment comment '自增主键', `balance` int(11) default null comment '账户余额', primary key (`id`) ) engine=innodb auto_increment=5 default charset=utf8 comment='--账户表'
- 实现
1 import org.apache.commons.dbcp.basicdatasource; 2 import org.springframework.dao.dataaccessexception; 3 import org.springframework.jdbc.core.jdbctemplate; 4 import org.springframework.jdbc.datasource.datasourcetransactionmanager; 5 import org.springframework.transaction.transactiondefinition; 6 import org.springframework.transaction.transactionstatus; 7 import org.springframework.transaction.support.defaulttransactiondefinition; 8 9 import javax.sql.datasource; 10 11 /** 12 * spring编程式事物 13 * @author: chenhao 14 * @create: 2019-10-08 11:41 15 */ 16 public class mytransaction { 17 18 private jdbctemplate jdbctemplate; 19 private datasourcetransactionmanager txmanager; 20 private defaulttransactiondefinition txdefinition; 21 private string insert_sql = "insert into account (balance) values ('100')"; 22 23 public void save() { 24 25 // 1、初始化jdbctemplate 26 datasource datasource = getdatasource(); 27 jdbctemplate = new jdbctemplate(datasource); 28 29 // 2、创建物管理器 30 txmanager = new datasourcetransactionmanager(); 31 txmanager.setdatasource(datasource); 32 33 // 3、定义事物属性 34 txdefinition = new defaulttransactiondefinition(); 35 txdefinition.setpropagationbehavior(transactiondefinition.propagation_required); 36 37 // 3、开启事物 38 transactionstatus txstatus = txmanager.gettransaction(txdefinition); 39 40 // 4、执行业务逻辑 41 try { 42 jdbctemplate.execute(insert_sql); 43 //int i = 1/0; 44 jdbctemplate.execute(insert_sql); 45 txmanager.commit(txstatus); 46 } catch (dataaccessexception e) { 47 txmanager.rollback(txstatus); 48 e.printstacktrace(); 49 } 50 51 } 52 53 public datasource getdatasource() { 54 basicdatasource datasource = new basicdatasource(); 55 datasource.setdriverclassname("com.mysql.jdbc.driver"); 56 datasource.seturl("jdbc:mysql://localhost:3306/my_test?usessl=false&useunicode=true&characterencoding=utf-8"); 57 datasource.setusername("root"); 58 datasource.setpassword("chenhao1991@"); 59 return datasource; 60 } 61 62 }
- 测试类及结果
public class mytest { @test public void test1() { mytransaction mytransaction = new mytransaction(); mytransaction.save(); } }
运行测试类,在抛出异常之后手动回滚事物,所以数据库表中不会增加记录。
基于@transactional注解的声明式事物
其底层建立在 aop 的基础之上,对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。通过声明式事物,无需在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过等价的基于标注的方式),便可以将事务规则应用到业务逻辑中。
- 接口
import org.springframework.transaction.annotation.propagation; import org.springframework.transaction.annotation.transactional; /** * 账户接口 * @author: chenhao * @create: 2019-10-08 18:38 */ @transactional(propagation = propagation.required) public interface accountserviceimp { void save() throws runtimeexception; }
- 实现
import org.springframework.jdbc.core.jdbctemplate; /** * 账户接口实现 * @author: chenhao * @create: 2019-10-08 18:39 */ public class accountserviceimpl implements accountserviceimp { private jdbctemplate jdbctemplate; private static string insert_sql = "insert into account(balance) values (100)"; @override public void save() throws runtimeexception { system.out.println("==开始执行sql"); jdbctemplate.update(insert_sql); system.out.println("==结束执行sql"); system.out.println("==准备抛出异常"); throw new runtimeexception("==手动抛出一个异常"); } public void setjdbctemplate(jdbctemplate jdbctemplate) { this.jdbctemplate = jdbctemplate; } }
- 配置文件
<?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" 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"> <!--开启tx注解--> <tx:annotation-driven transaction-manager="transactionmanager"/> <!--事物管理器--> <bean id="transactionmanager" class="org.springframework.jdbc.datasource.datasourcetransactionmanager"> <property name="datasource" ref="datasource"/> </bean> <!--数据源--> <bean id="datasource" class="org.apache.commons.dbcp.basicdatasource"> <property name="driverclassname" value="com.mysql.jdbc.driver"/> <property name="url" value="jdbc:mysql://localhost:3306/my_test?usessl=false&useunicode=true&characterencoding=utf-8"/> <property name="username" value="root"/> <property name="password" value="chenhao1991@"/> </bean> <!--jdbctemplate--> <bean id="jdbctemplate" class="org.springframework.jdbc.core.jdbctemplate"> <property name="datasource" ref="datasource"/> </bean> <!--业务bean--> <bean id="accountservice" class="com.chenhao.aop.accountserviceimpl"> <property name="jdbctemplate" ref="jdbctemplate"/> </bean> </beans>
- 测试
import org.junit.test; import org.springframework.context.applicationcontext; import org.springframework.context.support.classpathxmlapplicationcontext; /** * @author: chenhao * @create: 2019-10-08 18:45 */ public class mytest { @test public void test1() { // 基于tx标签的声明式事物 applicationcontext ctx = new classpathxmlapplicationcontext("aop.xml"); accountserviceimp studentservice = ctx.getbean("accountservice", accountserviceimp.class); studentservice.save(); } }
- 测试
==开始执行sql ==结束执行sql ==准备抛出异常 java.lang.runtimeexception: ==手动抛出一个异常 at com.lyc.cn.v2.day09.accountserviceimpl.save(accountserviceimpl.java:24) at sun.reflect.nativemethodaccessorimpl.invoke0(native method) at sun.reflect.nativemethodaccessorimpl.invoke(nativemethodaccessorimpl.java:62) at sun.reflect.delegatingmethodaccessorimpl.invoke(delegatingmethodaccessorimpl.java:43) at java.lang.reflect.method.invoke(method.java:498)
测试方法中手动抛出了一个异常,spring会自动回滚事物,查看数据库可以看到并没有新增记录。
注意:默认情况下spring中的事务处理只对runtimeexception方法进行回滚,所以,如果此处将runtimeexception替换成普通的exception不会产生回滚效果。
接下来我们就分析基于@transactional注解的声明式事物的的源码实现。