Spring5.0源码学习系列之事务管理概述
Spring5.0源码学习系列之事务管理概述(十一),在学习事务管理的源码之前,需要对事务的基本理论比较熟悉,所以本章节会对事务管理的基本理论进行描述
1、什么是事务?
事务就是一组原子性的SQL操作,或者说一个独立的工作单元。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)
注意:Spring的事务支持是基于数据库事务的,在MySQL数据库中目前只有InnoDB或者NDB集群引擎才支持,MySQL5.0之前的默认MyISAM存储引擎是不支持事务的
2、事务的ACID特性
ACID其实是事务特性的英文首字母缩写,具体含义是:原子性(atomicity)、一致性(consistency)、隔离性(isolation)、持久性(durability)
- 原子性(atomicity):事务是一个原子操作,由一系列动作组成。整个事务中的所有操作要么全部提交成功,要么全部失败回滚;
- 一致性(consistency):数据库总是从一个一致性的状态转换到另外一个一致性的状态,执行事务前后,数据保持一致;
- 隔离性(isolation): 因为有多个事务处理同个数据的情况,因此每个事务都应该与其他事务隔离开来,防止数据脏读、不可重复读等等情况;
- 持久性(durability):一旦事务提交,则其所做的修改就会永久保存到数据库中。此时即使系统崩溃,修改的数据也不会丢;
3、什么是脏读、不可重复读、幻读?
- 脏读
在A事务修改数据,提交事务之前,另外一个B事务读取了A事务未提交事务之前的数据,这种情况称之为脏读(Dirty Read) - 不可重复读
一个A事务在读取某些数据,第1次读取出来的数据结果和第2次读取出来的不一致,因为在两次数据读取期间,另外的事务对数据进行了更改 - 幻读
幻读和不可重复读是很类似的,不同的地方在于幻读侧重于事务对数据的删除或者新增,都是因为在两次数据读取期间,因为另外事务对数据的删除还是新增,导致第2次读取的数据和第1次不一致
4、Spring事务管理核心接口




5、事务隔离级别
定义:事务的隔离级别定义了一个事务可能受其他并发事务影响的程度。隔离级别可以不同程度的解决脏读、不可重复读、幻读。
| 隔离级别 | 描述 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|---|
| ISOLATION_DEFAULT | 使用后端数据库默认的隔离级别,默认的为Repeatable read (可重复读) | 否 | 否 | 是 |
| ISOLATION_READ_UNCOMMITTED | 不可提交读,允许读取尚未提交事务的数据,可能会导致脏读、不可重复读、幻读 | 是 | 是 | 是 |
| ISOLATION_READ_COMMITTED | 提交读,读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生 | 否 | 是 | 是 |
| ISOLATION_REPEATABLE_READ | 可重复读,可以阻止脏读和不可重复读,但幻读仍有可能发生 | 否 | 否 | 是 |
| ISOLATION_SERIALIZABLE | 串行化,这种级别是最高级别,服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读 | 否 | 否 | 否 |
6、事务的传播行为
| 事务传播行为 | 描述 |
|---|---|
| PROPAGATION_REQUIRED | 必须,默认值。如果A有事务,B将使用该事务;如果A没有事务,B将创建一个新的事务 |
| PROPAGATION_SUPPORTS | 支持。如果A有事务,B将使用该事务;如果A没有事务,B将以非事务执行 |
| PROPAGATION_MANDATORY | 强制。A如果有事务,B将使用该事务;如果A没有事务,B将抛异常 |
| PROPAGATION_REQUIRES_NEW | 必须新的。如果A有事务,将A的事务挂起,B创建一个新的事务;如果A没有事务,B创建一个新的事务。 |
| PROPAGATION_NOT_SUPPORTED | 不支持。如果A有事务,将A的事务挂起,B将以非事务执行;如果A没有事务,B将以非事务执行。 |
| PROPAGATION_NEVER | 从不。如果A有事务,B将抛异常;如果A没有事务,B将以非事务执行 |
| PROPAGATION_NESTED | 嵌套。A和B底层采用保存点机制,形成嵌套事务。 |
7、事务管理其它属性
前面介绍了事务管理的隔离级别和传播行为这两个重要的属性,接着介绍一下事务的其它属性
- 事务超时属性
事务超时,属性值是timeout,指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。以 int 的值来表示超时时间,其单位是秒,默认值为-1。 - 事务只读属性
属性值readOnly,对于只有读取数据查询的事务,可以指定事务类型为 readonly,即只读事务。只读事务不涉及数据的修改,数据库会提供一些优化手段,所以对于业务很明确的接口,可以适当加上只读属性 - 事务回滚规则
属性值rollbackFor,默认情况下,事务只有遇到运行期异常(RuntimeException 的子类)时才会回滚,Error 也会导致事务回滚
| 属性名 | 说明 |
|---|---|
| propagation | 事务的传播行为,默认值为 REQUIRED |
| isolation | 事务的隔离级别,默认值采用 DEFAULT |
| timeout | 事务的超时时间,默认值-1,表示不会超时,如果设置其它值,超过该时间限制但事务还没有完成,则自动回滚事务 |
| readOnly | 指定事务为只读事务,默认值false |
| rollbackFor | 指定能够触发事务回滚的异常类型,并且可以指定多个异常类型。 |
8、Spring事务实现方式
Spring事务代码实现方式有两种,一种是编程式事务,一种是声明式事务。所谓编程式事务,是指通过Spring框架提供的TransactionTemplate或者直接使用底层的PlatformTransactionManager。声明式事务,依赖Spring AOP,配置文件中做相关的事务规则声明或者直接使用@Transactional注解
下面给出一个典型的转账汇款例子,先不用事务的方式实现,接着使用编程式事务和声明式事务进行事务管理
package com.example.springframework.dao.impl;
import com.example.springframework.dao.AccountDao;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
/**
* <pre>
* AccountDaoImpl
* </pre>
*
* <pre>
* @author mazq
* 修改记录
* 修改后版本: 修改人: 修改日期: 2021/03/25 15:51 修改内容:
* </pre>
*/
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
@Override
public void out(String outer, int money) {
super.getJdbcTemplate().update("update account set money = money - ? where usercode=?",money,outer);
}
@Override
public void in(String inner, int money) {
super.getJdbcTemplate().update("update account set money = money + ? where usercode = ?",money , inner);
}
}
package com.example.springframework.service.impl;
import com.example.springframework.dao.AccountDao;
import com.example.springframework.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.stereotype.Service;
/**
* <pre>
* AccountServiceImpl
* </pre>
*
* <pre>
* @author mazq
* 修改记录
* 修改后版本: 修改人: 修改日期: 2021/03/25 15:55 修改内容:
* </pre>
*/
@Service
public class AccountServiceImpl extends JdbcDaoSupport implements AccountService {
AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void transfer(final String outer,final String inner,final int money){
accountDao.out(outer , money);
// exception
// int i = 1 / 0;
accountDao.in(inner , money);
}
}
代码例子看起来是挺正常的,不过假如在accountDao.out(outer , money);, accountDao.in(inner , money);两个事务执行期间,发生异常,这时会怎么样?效果如图:Jack的账号已经转账成功,转了1000,不过Tom并没有收到汇款,这种情况在实际生活中肯定是不允许的,所以需要使用事务进行管理

9、Spring编程式事务
Spring编程式事务实现,通过Spring框架提供的TransactionTemplate或者直接使用底层的PlatformTransactionManager
- 使用
TransactionTemplate的方式
private AccountDao accountDao;
private TransactionTemplate transactionTemplate;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
@Override
public void transfer(final String outer,final String inner,final int money){
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
accountDao.out(outer , money);
// exception
int i = 1 / 0;
accountDao.in(inner , money);
}
});
}
- 使用
PlatformTransactionManager的方式
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transferTrans(String outer, String inner, int money) {
DataSourceTransactionManager dataSourceTransactionManager =
new DataSourceTransactionManager();
// 设置数据源
dataSourceTransactionManager.setDataSource(super.getJdbcTemplate().getDataSource());
DefaultTransactionDefinition transDef = new DefaultTransactionDefinition();
// 设置传播行为属性
transDef.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = dataSourceTransactionManager.getTransaction(transDef);
try {
accountDao.out(outer , money);
// exception
int i = 1 / 0;
accountDao.in(inner , money);
//commit
dataSourceTransactionManager.commit(status);
} catch (Exception e) {
// rollback
dataSourceTransactionManager.rollback(status);
}
}
10、Spring声明式事务
Spring声明式事务依赖于Spring AOP,通过配置文件中做相关的事务规则声明或者直接使用@Transactional注解
- AOP规则声明方式
这种方式在 applicationContext.xml 文件中配置 aop 自动生成代理,进行事务管理
<?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 https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test"></property>
<property name="username" value="root"></property>
<property name="password" value="minstone"></property>
</bean>
<bean id="accountDao" class="com.example.springframework.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="accountService" class="com.example.springframework.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 事务管理器配置 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- xml配置事务 propagation 传播行为isolation 隔离级别-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT"/>
</tx:attributes>
</tx:advice>
<!-- 配置所有的Service方法都支持事务-->
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.example.springframework.service..*.*(..))"/>
</aop:config>
</beans>
- 使用AOP注解方式
在applicationContext.xml 配置事务管理器,将并事务管理器交予spring,在目标类或目标方法添加注解即可 @Transactional, proxy-target-class设置为 true : 底层强制使用cglib 代理
注意点:@Transactional只能用于public方法,不管是加上类上还是方法上
<!-- 事务管理器配置 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置 Annotation 驱动,扫描@Transactional注解的类定义事务 -->
<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/>
通过上述管理之后,一旦发生异常,两边都会进行事务回滚,没有异常,正常提交事务

本文例子代码可以在github找到下载链接
Spring5.0源码学习系列之事务管理概述的更多相关文章
- Spring5.0源码学习系列之浅谈BeanFactory创建
Spring5.0源码学习系列之浅谈BeanFactory创建过程 系列文章目录 提示:Spring源码学习专栏链接 @ 目录 系列文章目录 博客前言介绍 一.获取BeanFactory主流程 二.r ...
- Spring5.0源码学习系列之Spring AOP简述
前言介绍 附录:Spring源码学习专栏 在前面章节的学习中,我们对Spring框架的IOC实现源码有了一定的了解,接着本文继续学习Springframework一个核心的技术点AOP技术. 在学习S ...
- Spring5.0源码学习系列之浅谈懒加载机制原理
前言介绍 附录:Spring源码学习专栏 在上一章的学习中,我们对Bean的创建有了一个粗略的了解,接着本文挑一个比较重要的知识点Bean的懒加载进行学习 1.什么是懒加载? 懒加载(Lazy-ini ...
- Spring5.0源码学习系列之浅谈循环依赖问题
前言介绍 附录:Spring源码学习专栏 在上一章的学习中,我们对Bean的创建有了一个粗略的了解,接着本文浅谈Spring循环依赖问题,这是一个面试比较常见的问题 1.什么是循环依赖? 所谓的循环依 ...
- JDK源码学习系列05----LinkedList
JDK源码学习系列05----LinkedList 1.LinkedList简介 LinkedList是基于双向链表实 ...
- JDK源码学习系列04----ArrayList
JDK源码学习系列04----ArrayList 1. ...
- JDK源码学习系列03----StringBuffer+StringBuilder
JDK源码学习系列03----StringBuffer+StringBuilder 由于前面学习了StringBuffer和StringBuilder的父类A ...
- JDK源码学习系列02----AbstractStringBuilder
JDK源码学习系列02----AbstractStringBuilder 因为看StringBuffer 和 StringBuilder 的源码时发现两者都继承了AbstractStringBuil ...
- JDK源码学习系列01----String
JDK源码学习系列01----String 写在最前面: 这是我JDK源码学习系列的第一篇博文,我知道 ...
随机推荐
- 它来了!!!有史以来第一个64位Visual Studio(2022)预览版将在今夏发布!
美国时间2021年4月19日,微软产品研发部一位负责人Amanda Silver在其博客上发布一则<Visual Studio 2022>的消息,表示将在今年(2021年)夏天发布Visu ...
- 04- 移动APP功能测试要点以及具体业务流程测试
5.离线测试: 离线是应用程序在本地的客户端会缓存一部分数据以供程序下次调用. 1.对于一些程序,需要在登录进来后,这是没有网络的情况下可以浏览本地数据. 2.对于无网络时,刷新获取新数据时,不能获取 ...
- 缓冲区溢出分析第06课:W32Dasm缓冲区溢出分析
漏洞报告分析 学习过破解的朋友一定听说过W32Dasm这款逆向分析工具.它是一个静态反汇编工具,在IDA Pro流行之前,是破解界人士必然要学会使用的工具之一,它也被比作破解界的"屠龙刀&q ...
- Day006 什么是方法
什么是方法? 方法是语句的集合,他们在一起执行一个功能. 方法是解决一类问题的步骤的有序集合. 方法包含于类和对象中. 方法在程序中被创建,在其他地方被引用. 设计方法的原则 方法的本意是功能块, ...
- SpringBoot邮件报警
SpringBoot邮件报警 一.介绍 邮件报警,大体思路就是收集服务器发生的异常发送到邮箱,做到服务器出问题第一时间知道,当然要是不关注邮箱当我没说 二.配置邮箱 (1).注册两个邮箱账号(一个用来 ...
- Java版的扫雷游戏源码
package com.xz.sl; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; i ...
- 前端必读:Vue响应式系统大PK
转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 原文参考:https://www.sitepoint.com/vue-3-reactivity-system ...
- readdir_r()读取目录内容
readdir()在多线程操作中不安全,Linux提供了readdir_r()实现多线程读取目录内容操作. #include <stdio.h> #include <stdlib.h ...
- 如何借助CRM销售管理系统提升业绩?
与传统企业销售模式不同,现代企业在网络背书下,销售活动与网络密切相关.销售数据需要网络保存,销售渠道需要网络挖掘.在线的销售软件让销售活动起到了事半功倍的效果.CRM销售管理系统是企业必不可少的在线软 ...
- 『动善时』JMeter基础 — 19、JMeter配置元件【随机变量】
目录 1.随机变量介绍 2.随机变量界面详解 3.随机变量的使用 (1)测试计划内包含的元件 (2)线程组界面内容 (3)随机变量界面内容 (4)HTTP请求界面内容 (5)查看结果 1.随机变量介绍 ...