事务最全教程
最全事务
https://blog.csdn.net/weixin_30531261/article/details/79479895
https://tech.meituan.com/2014/08/20/innodb-lock.html 美团技术博客
1 什么是事务
大家所了解的事务Transaction,它是一些列严密操作动作,要么都操作完成,要么都回滚撤销。Spring事务管理基于底层数据库本身的事务处理机制。数据库事务的基础,是掌握Spring事务管理的基础。这篇总结下Spring事务。
事务具备ACID四种特性,ACID是Atomic(原子性)、Consistency(一致性)、Isolation(隔离性)和Durability(持久性)的英文缩写。
(1)原子性(Atomicity)
事务最基本的操作单元,要么全部成功,要么全部失败,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样。
(2)一致性(Consistency)
事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。
(3)隔离性(Isolation)
指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
(4)持久性(Durability)
指的是只要事务成功结束,它对数据库所做的更新就必须永久保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。
2 事务之间的缺陷
在事务处理中有违返ACID特性的3个问题:脏读取,不可重复读和幻读行。如果存在多个并发事务在运行,而这种事务操作了同一个数据来完成它们的任务,就会导致3个问题的存生。要解决它们,就必须在事务之间定义合适的隔离级别。
1 简单的叙述
为保证事务的完整性,必须解决事务之间可能存在的3个问题。
(1)脏读取
当一个事务读取了另一个事务尚未提交的更新,就叫脏读取。在另一个事务回滚的情况下,当前事务所读取的另一个事务的数据就是无效的。
(2)不可重复读取(一个事务读到另一个事务已提交的数据(update))
在一个事务中执行多次同样的查询操作,但每次查询的结果都不一样,就叫做不可重复读取,通常这种情况是由于数据在二次查询之间被另一个并发的事务所修改。
(3)虚读(一个事务读到另一个事务已提交的数据(insert))
这是对事务危害最小的一个问候,它类似不可重复读取,也是一个事务的更新结果影响到另一个事务问题。但是它不仅影响另一个事务查询结果,而且还会使查询语句返回一些不同的行录行。
这3个问题危害程度依次为:脏读取最大-->不可重复读取-->虚读最小。
隔离级别:
数据库通过设置事务的隔离级别防止以上情况的发生:
* 1、READ UNCOMMITTED: 赃读、不可重复读、虚读都有可能发生。
* 2、READ COMMITTED: 避免赃读。不可重复读、虚读都有可能发生。(oracle默认的)
* 4、REPEATABLE READ:避免赃读、不可重复读。虚读有可能发生。(mysql默认)
* 8、SERIALIZABLE: 避免赃读、不可重复读、虚读。
级别越高,性能越低,数据越安全
在实现上,数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。
“读未提交”隔离级别下直接返回记录上的最新值,没有视图概念;
READ UNCOMMITTED: 赃读、不可重复读、虚读都有可能发生。
在“读提交”隔离级别下,这个视图是在每个SQL语句开始执行的时候创建的。这里需要注意的是,
READ COMMITTED: 避免赃读。不可重复读、虚读都有可能发生。(oracle默认的)
在“可重复读”隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。
REPEATABLE READ:避免赃读、不可重复读。虚读有可能发生。(mysql默认)
“串行化”隔离级别下直接用加锁的方式来避免并行访问。
SERIALIZABLE: 避免赃读、不可重复读、虚读。
https://blog.csdn.net/weixin_41563161/article/details/102373535
2 详解介绍
1、共享锁(Shared Lock)
读锁,保证数据只能读取,不能被修改。如果事务A对数据M加上S锁,则事务A可以读记录M但不能修改记录M,其他事务(这里用事务B)只能对记录M再加上S锁,不能加X锁,直到事务A释放了记录M上的S锁,保证了其他事务(事务B)可以读记录M,但在事务A释放M上的S锁之前不能对记录M进行任何修改。
2、排他锁(X锁)
写锁,若事务A对数据对象M加上X锁,事务A可以读记录M也可以修改记录M,其他事务(事务B)不能再对记录M加任何锁,直到事务A释放记录M上的锁,保证了其他事务(事务B)在事务A释放记录M上的锁之前不能再读取和修改记录M。
3、悲观锁
对数据被外界修改保持保守态度,在整个数据处理过程中,数据处于锁定状态,依赖于数据库提供的锁机制。
4、乐观锁
采用宽松的加锁机制,基于数据版本记录机制,具体做法:数据库表增加一个"version"字段来实现,读取数据时,将版本号一同读出,之后更新,对版本号加1,将提交数据的版本数据与数据库对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库的数据,则予以更新,否则,被认为是过期数据。
导致的情况
1、丢失更新
事务A和事务B,同时获得相同数据,然后在各自的事务中修改数据M,事务A先提交事务,数据M假如为M+,事务B后提交事务,数据M变成了M++,最终结果变成M++,覆盖了事务A的更新。
例子:
2、脏读
允许事务B可以读到事务A修改而未提交的数据,可能会造成了脏读(脏读本质就是无效的数据,只有当事务A回滚,那么事务B读到的数据才为无效的,所以这里只是可能造成脏读,当事务A不回滚的时候,事务B读到的数据就不为脏数据,也就是有效的数据,脏数据会导致以后的操作都会发生错误,一定要去避免,不能凭借侥幸,事务A不能百分之百保证不回滚,所以这种隔离级别很少用于实际应用,并且它的性能也不比其他级别好多少)。
例子:
3、不可重复读
不可重复读是指在一个事务范围中2次或者多次查询同一数据M返回了不同的数据,例如:事务B读取某一数据,事务A修改了该数据M并且提交,事务B又读取该数据M(可能是再次校验),在同一个事务B中,读取同一个数据M的结果集不同,这个很蛋疼。
例子:
4、幻读
当用户读取某一个范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现会有新的“幻影行”,例如:事务B读某一个数据M,事务A对数据M增加了一行并提交,事务B又读数据M,发生多出了一行造成的结果不一致(如果行数相同,则是不可重复读)。
例如:
(封锁协议)
在运用S锁和X锁对数据M加锁的时候,需要约定一些规则,例如何时申请S锁或者X锁,持锁时间,这些规则就是封锁协议。其中不同的封锁协议对应不同的隔离级别。
1、一级封锁协议
事务 T 要修改数据 A 时必须加 X 锁,直到事务结束才释放锁。
可以解决丢失修改问题;
缺点:
可能会造成如下后果
- 脏读。
- 不可重复读。
- 幻读
2、二级封锁协议
在 1 级的基础上,要求读取数据 A 时必须加 S 锁,读取完马上释放 S 锁。
可以解决读脏数据问题,因为如果一个事务在对数据 A 进行修改,根据 1 级封锁协议,会加 X 锁,那么就不能再加 S 锁了,也就是不会读入数据。
缺点:
可能会造成如下后果
- 不可重复读。
- 幻读。
3、三级封锁协议
在 2 级的基础上,要求读取数据 A 时必须加 S 锁,直到事务结束了才能释放 S 锁。
可以解决不可重复读的问题,因为读 A 时,其它事务不能对 A 加 X 锁,从而避免了在读的期间数据发生改变。
缺点:1.幻读。
4、最强封锁协议
最强封锁协议对应Serialization隔离级别,本质是从MVCC并发控制退化到基于锁的并发控制,对事务中所有读取操作加S锁,写操作加X锁,这样可以避免脏读,不可重复读,幻读,更新丢失,开销也最大,会造成读写冲突,并发程度也最低。
示例 设置事务的隔离级别为 read uncommitted
3 数据库设置隔离级别
查看当前的事务隔离级别:SELECT @@TX_ISOLATION;
更改当前的事务隔离级别:SET TRANSACTION ISOLATION LEVEL 四个级别之一。
设置隔离级别必须在事务之前
3 Spring事务管理介绍
1核心接口图解
2 Spring事务管理
1jar包
2三个顶级接口
1 PlatformTransactionManager 平台事务管理器,spring要管理事务,必须使用事务管理器
进行事务配置时,必须配置事务管理器。
2TransactionDefinition:事务详情(事务定义、事务属性),spring用于确定事务具体详情,
例如:隔离级别、是否只读、超时时间 等
进行事务配置时,必须配置详情。spring将配置项封装到该对象实例。
3 TransactionStatus:事务状态,spring用于记录当前事务运行状态。例如:是否有保存点,事务是否完成。
spring底层根据状态进行相应操作。
1PlatformTransactionManager 事务管理器
导入jar包:需要时平台事务管理器的实现类
引入上面的jar包之后
- 常见的事务管理器
DataSourceTransactionManager ,jdbc开发时事务管理器,采用JdbcTemplate
HibernateTransactionManager,hibernate开发时事务管理器,整合hibernate
api详解
TransactionStatus getTransaction(TransactionDefinition definition) ,事务管理器 通过“事务详情”,获得“事务状态”,从而管理事务。
void commit(TransactionStatus status) 根据状态提交
void rollback(TransactionStatus status) 根据状态回滚
2 TransactionStatus
3TransactionDefinition
- 传播行为:在两个业务之间如何共享事务。
PROPAGATION_REQUIRED , required , 必须 【默认值】
支持当前事务,A如果有事务,B将使用该事务。
如果A没有事务,B将创建一个新的事务。
PROPAGATION_SUPPORTS ,supports ,支持
支持当前事务,A如果有事务,B将使用该事务。
如果A没有事务,B将以非事务执行。
PROPAGATION_MANDATORY,mandatory ,强制
支持当前事务,A如果有事务,B将使用该事务。
如果A没有事务,B将抛异常。
PROPAGATION_REQUIRES_NEW , requires_new ,必须新的
如果A有事务,将A的事务挂起,B创建一个新的事务
如果A没有事务,B创建一个新的事务
PROPAGATION_NOT_SUPPORTED ,not_supported ,不支持
如果A有事务,将A的事务挂起,B将以非事务执行
如果A没有事务,B将以非事务执行
PROPAGATION_NEVER ,never,从不
如果A有事务,B将抛异常
如果A没有事务,B将以非事务执行
PROPAGATION_NESTED ,nested ,嵌套
A和B底层采用保存点机制,形成嵌套事务。
掌握:PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_NESTED
4 Spring事务管理代码示例
前提
dao层
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
@Override public void out(String outer, Integer money) { this.getJdbcTemplate().update("update account set money = money - ? where username = ?", money,outer); }
@Override public void in(String inner, Integer money) { this.getJdbcTemplate().update("update account set money = money + ? where username = ?", money,inner); }
} |
service层
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } @Override public void transfer(String outer, String inner, Integer money) { accountDao.out(outer, money); //断电 // int i = 1/0; accountDao.in(inner, money); }
}
|
spring配置
<!-- 1 datasource --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/ee19_spring_day03"></property> <property name="user" value="root"></property> <property name="password" value="1234"></property> </bean>
<!-- 2 dao --> <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 3 service --> <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> </bean> |
测试
@Test public void demo01(){ String xmlPath = "applicationContext.xml"; ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath); AccountService accountService = (AccountService) applicationContext.getBean("accountService"); accountService.transfer("jack", "rose", 1000); } |
1 手动管理事务(了解)
修改service
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
//需要spring注入模板
private TransactionTemplate transactionTemplate;
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
@Override
public void transfer(final String outer,final String inner,final Integer money) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus arg0) {
accountDao.out(outer, money);
//断电
// int i = 1/0;
accountDao.in(inner, money);
}
});
}
}
修改spring配置
<!-- 3 service --> <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> <property name="transactionTemplate" ref="transactionTemplate"></property> </bean>
<!-- 4 创建模板 --> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="txManager"></property> </bean>
<!-- 5 配置事务管理器 ,管理器需要事务,事务从Connection获得,连接从连接池DataSource获得 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> |
2工厂bean 生成代理:半自动
spring提供 管理事务的代理工厂bean TransactionProxyFactoryBean
1.getBean() 获得代理对象
2.spring 配置一个代理
spring配置
<!-- 4 service 代理对象 4.1 proxyInterfaces 接口 4.2 target 目标类 4.3 transactionManager 事务管理器 4.4 transactionAttributes 事务属性(事务详情) prop.key :确定哪些方法使用当前事务配置 prop.text:用于配置事务详情 格式:PROPAGATION,ISOLATION,readOnly,-Exception,+Exception 传播行为 隔离级别 是否只读 异常回滚 异常提交 例如: <prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_DEFAULT</prop> 默认传播行为,和隔离级别 <prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly</prop> 只读 <prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_DEFAULT,+java.lang.ArithmeticException</prop> 有异常扔提交 --> <bean id="proxyAccountService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="proxyInterfaces" value="com.itheima.service.AccountService"></property> <property name="target" ref="accountService"></property> <property name="transactionManager" ref="txManager"></property> <property name="transactionAttributes"> <props> <prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_DEFAULT</prop> </props> </property> </bean>
<!-- 5 事务管理器 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean>
|
测试
3AOP 配置基于xml【掌握】
在spring xml 配置aop 自动生成代理,进行事务的管理
1.配置管理器
2.配置事务详情
3.配置aop
<!-- 4 事务管理 --> <!-- 4.1 事务管理器 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 4.2 事务详情(事务通知) , 在aop筛选基础上,对ABC三个确定使用什么样的事务。例如:AC读写、B只读 等 <tx:attributes> 用于配置事务详情(属性属性) <tx:method name=""/> 详情具体配置 propagation 传播行为 , REQUIRED:必须;REQUIRES_NEW:必须是新的 isolation 隔离级别 --> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT"/> </tx:attributes> </tx:advice> <!-- 4.3 AOP编程,目标类有ABCD(4个连接点),切入点表达式 确定增强的连接器,从而获得切入点:ABC --> <aop:config> <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.itheima.service..*.*(..))"/> </aop:config> |
4AOP配置基于注解【掌握】
- 1.配置事务管理器,将并事务管理器交予spring
- 2.在目标类或目标方法添加注解即可 @Transactional
spring配置
<!-- 4 事务管理 --> <!-- 4.1 事务管理器 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 4.2 将管理器交予spring * transaction-manager 配置事务管理器 * proxy-target-class true : 底层强制使用cglib 代理 --> <tx:annotation-driven transaction-manager="txManager"/> |
service 层
@Transactional public class AccountServiceImpl implements AccountService { |
事务详情配置
@Transactional(propagation=Propagation.REQUIRED , isolation = Isolation.DEFAULT) public class AccountServiceImpl implements AccountService {
|
5 mysql 事务操作--简单
a、mysql引擎是支持事务的
b、mysql默认自动提交事务。每条语句都处在单独的事务中。
c、手动控制事务
开启事务:start transaction | begin
提交事务:commit
回滚事务:rollback
全部回滚
ABCD 一个事务
Connection conn = null;
try{
//1 获得连接
conn = ...;
//2 开启事务
conn.setAutoCommit(false);
A
B
C
D
//3 提交事务
conn.commit();
} catche(){
//4 回滚事务
conn.rollback();
}
回滚一半
- mysql 事务操作--Savepoint
需求:AB(必须),CD(可选) Connection conn = null; Savepoint savepoint = null; //保存点,记录操作的当前位置,之后可以回滚到指定的位置。(可以回滚一部分) try{ //1 获得连接 conn = ...; //2 开启事务 conn.setAutoCommit(false); A B savepoint = conn.setSavepoint(); C D //3 提交事务 conn.commit(); } catche(){ if(savepoint != null){ //CD异常 // 回滚到CD之前 conn.rollback(savepoint); // 提交AB conn.commit(); } else{ //AB异常 // 回滚AB conn.rollback(); } } |