事务的定义
1.一个事务单元中包含多个数据库表的操作,可能有增、删、改等操作,有一个sql语句执行失败,整个事务都执⾏失败。
作用:执行增删改时,保持数据的一致、安全,不会像无事务那样出错了仍然提交
把将要执行的命令步骤给包裹在事务中,使之随时能检测命令的运行过程的异常,
及时止血,防止半途而废,事务是逻辑上的一组操作,要么都执行,要么都不执行。
事务开启后
开启事务后,执⾏的语句均属于当前事务,成功再执⾏ COMIIT,失 败要进⾏ ROLLBACK
spring事务也是通过动态代理来实现的,spring事务也是借鉴了AOP的思想
事务数据库执行原理
数据库会为每⼀个客户端都维护⼀个空间独⽴的缓存区(回滚段),⼀个事务中所有的增删改语句的执⾏结果都会缓存在回滚段中,只有当事务中所有SQL 语句 均正常结束(commit),才会将回滚段中的数据同步到数据库。否则⽆论因为 哪种原因失败,整个事务将回滚 (rollback)。
需要注意
事务能否生效与数据库引擎是否支持事务是关键。
绕不开的ACID特性
1.原子性:事务要么全执行,要么全不执行
原子性的特点:
1、针对单事务的控制,命令执行的过程中不会被加塞和干扰
2、针对多个insert,update,delete操作
典型场景,一个方法中包含多个insert,update,delete操作通过添加事务保证原子性,要么全部成功,要么全部失败
2.一致性:事务执行前后,数据完整性一致
事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的
在成功提交或失败回滚事务之后以及正在进行的事务之间,数据库始终保持不变的状态,
如果正在多个表之间更新相关数据,那么查询将看到所有旧值或所有新值,而不会一部分是新值,一部分是旧值。
3.隔离性:一个事务的执行不能被其他事务干扰。
隔离性的特点:
事务处理过程中的中间状态应该对外部不可见,换句话说,
事务在进行过程中是隔离的,事务之间不能互相干扰,不能访问到彼此未提交的数据。
即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
1、针对多事务间数据可见性的控制。
2、控制加锁的粒度和加锁、释放锁的时机,提高事务的并发能力。
4.持久性:指一个事务一旦提交,对数据库中数据的改变是永久性,接下来的其他操作或故障不应该对其有任何影响。
何为脏读
脏读就是读到其他事务没有提交的数据,只要隔离级别不是读未提交(Read Uncommitted)就不会出现。
举例:
A用户修改了数据,随后B用户又读出该数据,但A用户因为某些原因取消了对数据的修改,数据恢复原值,此时B得到的数据就与数据库内的数据产生了不一致
何为回滚
即还原到事务开始前的状态,就像这个事务从来没有执行过一样
什么情况下不需要添加事务?
对于多查询的不进行添加事务,当进行大量的增加、删除、修改表的数据进行添加事务处理。
两种引擎比较
- MyISAM管理非事务表。它提供高速存储和检索,以及全文搜索能力。如果应用中需要执行大量的SELECT查询,那么MyISAM是更好的选择。
- InnoDB用于事务处理应用程序,具有众多特性,包括ACID事务支持。如果应用中需要执行大量的INSERT或UPDATE操作,则应该使用InnoDB,为的是提高多用户并发操作的性能。
不是所有引擎都支持事务的处理
比如常用的 MySQL 数据库默认使用支持事务的 InnoDB引擎。但是,如果把数据库引擎变为 MyISAM,那么程序也就不再支持事务了!
大尺寸的数据集趋向于选择InnoDB引擎,因为它支持事务处理和故障恢复。数据库的大小决定了故障恢复的时间长短,InnoDB可以利用事务日志进行数据恢复,这会比较快。
分布式事务的应用场景
1、支付
2、在线下单
3.金融系统中的银行卡充值(银行系统和金融系统)、
4.教育系统中下单选课业务(订单系统和选课系统)
事务的配置
Spring的配置事务方式:声明式事务处理、注解方式的声明式事务管理
两种方式
1.通过注解的形式
2.通过xml的方式
举例:通过xml 方式配置,
创建文件SpringContext-transaction.xml
(1).在Spring核心配置文件中引入相关约束:
<?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: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
https://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">
(2).配置事务管理器–就是对Bean类注入属性值后的类进行事务管理
id为dataSource_ID1的BasicDataSource类配置文章链接
<!--配置事务管理器-->
<bean id="789——transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
//ref=" " 其数据源已经完成了class="org.apache.commons.dbcp.BasicDataSource"的配置 --仔细去看就知道了
<property name="dataSource" ref="dataSource_ID1"/>
</bean>
(3).配置事务通知
注意:<tx:method name="以XXX开头的方法名有效*", *代表所有
<!-- 配置事务通知-->
<tx:advice id="Advice" transaction-manager="789——transactionManager">
<!-- 配置事务属性,即哪些方法要执行事务-->
<tx:attributes>
<!--自定义所有insert开头的方法配置事务,以下同理 -->
<tx:method name="insert*" propagation="REQUIRED"/>
<!--自定义所有update开头的方法配置事务,以下同理 -->
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
(4).配置事务切面
<!--配置事务切面-->
<aop:config>
<!--要执行的方法在哪个包,意思为:com.wanshi.service下的所有包里面的包含任意参数的所有方法-->
<aop:pointcut id="AdviceAop" expression="execution(public * com.xxx.service.*.*(..))"/>
<!-- 配置为AdviceAop执行哪个事务通知 -->
<aop:advisor advice-ref="Advice" pointcut-ref="AdviceAop"/>
</aop:config>
事务配置的总结
1.事务配置为事务配置管理即为DataSourceTransactionManager类中dataSource属性赋值
2.配置事务通知,通知指的是动作,自定义哪些字符串开头的命名方法添加事务动作。
3.将这些事务动作放在哪些位置或者哪个点上?即配置pointcut,像交警一样设卡查哨
这里边的expression中execution(public * com.xxx.service..(…))表达式
4.配置事务切面即将配置好的事务通知和事务的切点交给事务管理。
关于切入点表达式
主要关注 返回方法、哪个类、类中哪个方法、方法的对应参数限制
execution(* com.test1.ce.epet.dao.mpl.*.*(..))
这是com.test1.ce.epet.dao.impl 包下所有的类的所有方法。
配置事务的方法必须是 public
第一个 * 代表所有的返回值类型
第二个 *代表所有的类
第三个 * 代表类中所有方法
最后一个…代表所有的参数。
Spring七大事务传播行为
1、propagation_required:支持当前事务,无事务、另起新事物
2、propagation_required-new:新建事务,若有旧事务,挂起。
3、propagation_supports:支持雪前事雾,无事务,以非事务执行
4、propagation_mandatory:以事务方式执行,无事务,抛异常
5、propagation_not_supported:不支持事务,如有事务,挂起
6、propagation_never:以非事务执行,有事务,抛异常
7、propagation_nested:内切事务
1、propagation_required
加入当前正要执行的事务不在另外一个事务里,那么就起一个新的事务。
比如说,ServiceB.methodB的事务级别定义为PROPAGATION_REQUIRED,那么由于执行ServiceA.methodA的时候,ServiceA.methodA已经起了事务。
这时调用ServiceB.methodB,ServiceB.methodB看到自己已经运行在ServiceA.methodA的事务内部,就不再起新的事务。
而假如ServiceA.methodA运行的时候发现自己没有在事务中,他就会为自己分配一个事务。
这样,在ServiceA.methodA或者在ServiceB.methodB内的任何地方出现异常,事务都会被回滚。
即使ServiceB.methodB的事务已经被提交,但是ServiceA.methodA在接下来fail要回滚,ServiceB.methodB也要回滚
2.propagation_required-new
比如我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为ROPAGATION_REQUIRES_NEw。
那么当执行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起,
ServiceB.methodB会起一个新的事务,等待ServiceB.methodB的事务完成以后,他才继续执行。
他与PROPAGATION_REQUIRED的事务区别在于事务的回滚程度。
因为ServiceB.methodB是新起一个事务,那么就是存在>两个不同的事务。
如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB是不会回滚的。
如果ServiceB.methodB失败回滚,抛出的异常被ServiceA.methodA捕获,ServiceA.methodA事务仍然可能提交。
3.propagation_supports
如果当前在事务中,即以事务的形式运行,如果当前不再一个事务中,那么就以非事务的形式运行。
4.propagation_mandatory
必须在一个事务中运行。也就是说,他只能被一个父事务调用。否则,他就要抛出异常。
5.propagation_not_supported
当前不支持事务。
比如ServiceA.methodA的事务级别是PROPAGATION_REQUIRED,而ServiceB.methodB的事务级别是PROPAGATION_NOT_SUPPORTED。
那么当执行到ServiceB.methodB时,ServiceA.methodA的事务挂起,而ServiceB.methodB以非事务的状态运行完,再继续ServiceA.methodA的事务。
6.propagation_never
不能在事务中运行。
假设ServiceA.methodA的事务级别是PROPAGATION_REQUIRED,而ServiceB.methodB的事务级别是PROPAGATION_NEVER,
那么ServiceB.methodB就要抛出异常了。
7、propagation_nested:内切事务
没有在EJB中定义
理解Nested的关键是savepoint
他与PROPAGATION_REQUIRES_NEW的区别是: PROPAGATION_REQUIRES_NEW另起一个事务,将会与他的父事务相互独立;而Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,他也要回滚的。
外围方法开启事务的情况下**Propagation.NESTED**修饰的内部方法属于外部事务的子事务,
外围主事务回滚,子事务一定回滚,而内部子事务可以单独回滚而不影响外围主事务和其他子事务。
举例
@Transactional
@Override
public void transaction_nested_nested_exception_try(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addNested(user1);
User2 user2=new User2();
user2.setName("李四");
try {
user2Service.addNestedException(user2);
} catch (Exception e) {
System.out.println("方法回滚");
}
}
ServiceA {
“事务漓性配置为PROPAGATION_REQUIRED"
void methodA(){
try {
iisavepoint
ServiceB.methodB();!PROPAGATION_NESTED 级别
}
catch (SomeException) {
//执行其他业务
}
}
}
ServiceC.methodC();
也就是说ServiceB.methodB失败回滚,那么ServiceA.methodA也会回滚到savepoint点上,ServiceA.methodA可以选择另外一个分支,
比如
ServiceC.methodC,继续执行,来尝试完成自己的事务。
使用注解的方式声明事务并且加深理解Spring七大事务
使用注解的方式声明事务并且加深理解Spring七大事务
PROPAGATION_REQUIRED
支持当前事务,如果不存在则创建一个新事务。最常用的选择
场景一:service层方法内部开启事务
@Service
public class test1ServiceImpl implements test1Service {
//省略其他...
@Override
#方法内部开启事务
@Transactional(propagation = Propagation.REQUIRED)
public void addRequired(test1 user){
test1Mapper.update(user);
}
}
@Service
public class test2ServiceImpl implements test2Service {
//省略其他...
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void addRequired(test2 user){
test2Mapper.update(user);
}
@Override
#方法内部开启事务
@Transactional(propagation = Propagation.REQUIRED)
public void addRequiredException(test2 user){
test2Mapper.update(user);
throw new Exception();
}
}
结论:在外围方法未开启事务的情况下,PROPAGATION_REQUIRED修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰.
============================================
场景2:外部开启事务
开发中使用较高的场景(默认情况)
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void transaction_exception_required_required(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addRequired(user2);
throw new RuntimeException();
}
结论:外围开启事务,内部方法出现一个异常均回滚。原因是外围开启事务,PROPAGATION_REQUIRED修饰的所有内部方法会加入到外围方法的事务中,内部事务和外部事务均属于同一个事务。
============================
PROPAGATION_REQUIRES_NEW
创建一个新事务,如果当前事务存在,则暂停当前事务
场景一:外部不开启事务
Service1
@Service
public class test1ServiceImpl implements test1Service {
//省略其他...
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addRequiresNew(test1 user){
test1Mapper.insert(user);
}
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void addRequired(test1 user){
test1Mapper.insert(user);
}
}
Service2
@Service
public class User2ServiceImpl implements User2Service {
//省略其他...
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addRequiresNew(User2 user){
user2Mapper.insert(user);
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addRequiresNewException(User2 user){
user2Mapper.insert(user);
throw new RuntimeException();
}
}
验证方法1:外围方法没有事务,插入“张三”、“李四”方法都在自己的事务中独立运行,外围方法抛出异常回滚不会影响内部方法。
验证方法2:张三正常插入,李四无法插入,李四在自己的方法中开启新事务,但是抛出异常回滚。
场景2:外部开启事务
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void transaction_required_requiresNew_requiresNew_exception_try(){
User1 user1=new User1();
user1.setName("张三");
user1Service.addRequired(user1);
User2 user2=new User2();
user2.setName("李四");
user2Service.addRequiresNew(user2);
User2 user3=new User2();
user3.setName("王五");
try {
user2Service.addRequiresNewException(user3);
} catch (Exception e) {
System.out.println("回滚");
}
}
结论:外围方法开启事务的情况下,Propagation.REQUIRES_NEW修饰的内部方法全部单独开启独立事务,不同的内部方法、外部事务间相互独立,互不干扰。
点解连接
第一章:Spring流程执行步骤
第二章:Spring核心思想和IOC和AOP依赖注入
第三章:Spring常用注解解释
第四章:Spring七大核心模块Bean、Core、Context
第五章:Spring细分一如何让mapper文件夹下的SQL语句对数据库生效,jdbc和mybatis-config.xml
第六章:Springmvc事务管理和配置文件xml详解和七大事务和注解@Transactional