文章目录
到处抄的,有问题评论写一下。
一、理解
为了实现数据的安全访问。
二、spring传播机制
需要注意的是,如果里面的事务代码报错了,不管你是什么类型,只要你不捕获报错,外面的事务也会回滚。
1、七种类型
- REQUIRED
默认的传播特性,如果当前没有事务,则新建一个事务,如果当前存在事务,则加入这个事务 - SUPPORTS
当前存在事务,则加入当前事务,如果当前没有事务,则以非事务的方式执行 - MANDATORY
当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常 - REQUIRED_NEW
创建一个新事务,如果存在当前事务,则挂起该事务 - NOT SUPPORTED
以非事务方式执行,如果存在当前事务,则挂起当前事务 - NEVER
不使用事务,如果当前事务存在,则抛出异常 - NESTED
如果当前事务存在,则在嵌套事务中执行,否则REQUIRED的操作一样;
相当于设置保存点,嵌套的事务失败了就会回到外层的保存点那里。
父事务回滚会导致子事务也回滚。
2、类型的区别
-
NESTED和REQUIRED_NEW的区别
REQUIRED_NEW是新建一个事务并且新开始的这个事务与原有事务无关,而NESTED则是当前存在事务时会开启一个嵌套事务,在NESTED情况下,父事务回滚时,子事务也会回滚,而REQUIRED_NEW情况下,原有事务回滚,不会影响新开启的事务 -
NESTED和REQUIRED的区别:
REQUIRED情况下,调用方存在事务时,则被调用方和调用方使用同一个事务,那么被调用方出现异常时,由于共用一个事务,**所以无论是否catch异常,事务都会回滚,**而在NESTED情况下,被调用方发生异常时,调用方可以catch其异常,这样只有子事务回滚,父事务不会回滚。
3、其他情况
1、在同一个类,无事务方法调用有事务的方法,事务不会生效。(因为动态代理的原因,最外层没加注解导致没有进行AOP操作)
2、同一类中事务方法调非事务方法,两个方法都会生效事务(应该是因为后面的非事务方法并没有开启一个新的数据库连接,使用的还是最外层方法的数据库连接,相当于加了个REQUIRED的事务)
public class AdminService {
@Autowired
AdminDao adminDao;
@Transactional(rollbackFor = Exception.class)
public int updateAdmin(AdminPo adminPo) {
int result = adminDao.update(adminPo);
int num = this.updateAdmin();
// 造异常语句
// int exception = 1/0;
return result;
}
private int insertAdmin() {
AdminPo adminPo = new AdminPo();
adminPo.setId(2);
adminPo.setName("jackson");
adminPo.setPassWord("196516");
int result = adminDao.insert(adminPo);
return result;
}
}
三、事务的隔离级别
1、隔离级别分别
- read uncommitted
- read committed
- repeatable read
- serializable
在进行配置的时候,如果数据库和spring代码中的隔离级别不同,那么以spring的配置为主
四、事务的实现原理
1、编程式事务
2、声明式事务
使用@Transactional,这个可以加在类上
五、事务失效的情况
1、bean对象没有被spring容器管理
加上@Controller、@service、@Component、@Repository等注解可以自动实现bean实例化和依赖注入的功能。
2、方法的访问修饰符不是public
3、自身调用问题
原因:在本方法类中调用,是不会走AOP动态代理过程的,所以就没办法执行事务。
解决方案:
1、新加一个方法,把要用到事物的东西放在这个新方法里面,在新方法上加注解。
2、在该类中注入自己(但是有些问题)。
3、使用((ServiceA)AopContext.currentProxy()).doSave(user)。
4、数据源没有配置事务管理器
5、数据库不支持事务
myISAM类型的数据表不支持事务,Innodb支持。
6、异常被捕获
原因:被自己try catch了
7、异常类型错误或者配置错误
自定义了回滚异常
8、final类和static类
9、多线程
原因:不同线程创建的数据库连接不一样,所以就没办法实现不同线程的同时回滚。(同一个事务本质上是指的同一个数据库连接)
10、没有开启事务
springboot会默认开启事务,老版本的spring是不会默认开启的
六、事务的回滚
spring的事务是由aop来实现的,首先要生成具体的代理对象,然后按照aop的整套流程来执行具体的操作逻辑,正常情况下要通过通知来完成核心功能,但是事务不是通过通知来实现的,而是通过一个Transactionlnterceptor来实现的,然后调用invoke来实现具体的逻辑
1、先做准备工作,解析各个方法上事务相关的属性,根据具体的属性来判断是否开始新事务
2、当需要开启的时候,获取数据库连接,关闭自动提交功能,开起事务
3、执行具体的sql逻辑操作
4、在操作过程中,如果执行失败了,那么会通过cmpleteTransactionAfterThrowing看来完成事务的回滚操作,回滚的具体罗辑是通过doRollBack方法来实现的,实现的时候也是要先获取连接对象,通过连接对象来回滚
5、如果执行过程中,没有任何意外情况的发生,那么通过commitTransationAfterReturning来完成事务的提交操作,提交的具体逻辑是通过doCommit方法来实现的,实现的时候也是要获取连接,通过连接对象来提交
6、当事务执行完毕之后需要清除相关的事务信息cleanupTransactionlnfo
七、spring事务的属性
传播性:getPropagationBehavior(): int
隔离性:getlsolationLevel(): int
超时时间:getTimeout(): int
是否只读:isReadOnly(): boolean
回滚规则:
rollbackFor:设置事务回滚异常 (class),rollbackFor ={NullPointException.classl
rollbackForClassName:设置事务回滚异常 (string), 同上格式为字符串
noRollbackFor:设置事务不回滚异常 (class),noRollbackFor =(NullPointException.class}
noRollbackForClassName:设置事务不回滚异常 (string), 同上格式为字符串
八、事务的示例(以默认接口)
1、A类中的a方法,调用B类中的b方法
- a方法weijia
九、数据库事务和锁机制
1、理解
数据库事务:事务时数据库管理系统(DBMS)执行过程中的一个逻辑单位,由一个有限的数据库操作**(DML:增删改)**序列构成。
存储引擎:根据存储和使用的要求选择不同的存储引擎。每个表都有自己的存储引擎(就相当于表的类型)。
2、事务的四大特性(ACID)
原子性(Atomicity)
一致性(Consistency)
隔离性(Isolation)
持久性(Durablity)
3、事务的开启
自动开启:
mysql数据库默认是自动开启事务的。
手动开启:
// 开启
begin;
// 数据库操作
upadte/delete/insert
// 提交/回滚
commit;/rollback
4、出现的问题
标准出处:http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt
脏读:读取到其他事务未提交的数据
不可重复读:读取到其他事务已提交的数据(update/delete)
幻读:读取到其他事务已提交的数据(insert)
5、隔离级别
四种隔离级别:
Read Uncommitted (未提交读):
啥也没解决。
不加锁。
Serializable (串行化) :
解决事务并发的所有问题。
所有的select语句都会被隐式的转化为select … lock in share mode会和update、delete互斥。
Read Committed (已提交读) :
解决脏读。
Repeatable Read (可重复读):
解决脏读、不可重复读。
隔离级别的解决方案
- 在读取数据前,对其加锁,阻止其他事务对数据进行修改–Lock Based Concurrency Control (LBCC)
- 生成一个数据请求时间点的一致性数据快照(Snapshot),并用这个快照来提供一定级别 (语句级或事务级)的一致性读取–Multi Version Concurrency Control(MVCC)
mysql中的隔离级别
mysql中默认的隔离级别是RR
6、锁
开启事务时加锁,事务结束解锁。
锁的作用:资源的管理
锁定粒度: 表锁 > 行锁
加锁效率: 表锁 > 行锁
冲突概率: 表锁 > 行锁
并发性能: 表锁 < 行锁
MyISAM:表锁
InnoDB:表锁和行锁
行锁
共享锁;
据可以共享一把锁,又称为读锁,顾名思义,共享锁就是多个事务对于同一数都能访问到数据,但是只能读不能修改;
加锁方式: select * from student where id=1 LOCK INSHARE MODE;
释放锁: commit/rollback;
排他锁:
又称为写锁,排他锁不能与其他锁并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的锁(共享锁、排他锁),只有该获取了排他锁的事务是可以对数据行进行读取和修改。
加锁释锁方式:
自动: delete / update /insert 默认加上X锁
手动: select * from student where id=1 FOR UPDATE;
释放锁:
commit/rollback;
表锁
意向锁:
意向锁是由数据引擎自己维护的,用户无法手动操作意向锁意向共享锁 (Intention Shared Lock,简称IS锁)。表示事务准备给数据行加入共享锁,也就是说一个数据行加共享锁前必须先取得该表的IS锁。
意向排他锁 (Intention Exclusive Lock,简称IX锁)表示事务准备给数据行加入排他锁作用:用于提高加表锁的效率,表达的意思就是这个表有没有加锁。
索引(InnoDB)
本质上锁的索引,表里面没有索引就会锁表(但是一张表一定会有一个聚集索引来决定数据的存放顺序)。
聚集索引——索引的逻辑顺序和物理顺序是一致的。
1、primary key
2、unique key – no null(没有第一个,就用这个)
3、_rowid(没有前两个,就用这个,这个是隐藏的字段)
二级索引——除了聚集索引以外的索引
记录锁:
间隙锁:解决了RR中幻读的问题
临键锁:
相关
锁表的相关问题
Innodb:行锁,但是如果不走索引的话还是表锁。
测试结果
一、A方法中多次调用B方法
1、A未开启事务,B开启事务
public void A() {
// 循环调用
foreach(B());
}
@Transactional
public void B() {
}
代码:
@Component
public class TestClass {
@Autowired
private ReminderRepository reminderRepository;
private static int num = 0;
public void mainFunction() {
List<Reminder> reminders = reminderRepository.selectByIds(ids);
// 三条数据,循环三次(这里使用AopContext.currentProxy()是为了让同一个类中的public方法能够执行事务)
reminders.forEach(x->AopContext.currentProxy().update(x));
}
@Transactional
public void update(Reminder reminder) {
reminder.setFeedbackNum(String.valueOf(num));
reminderRepository.updateOptional(reminder, FIELD_FEEDBACK_NUM);
num++;
if (num == 2) {
throw new CommonException("第二次失败");
}
}
}
执行结果:第二个B报错
只有第一次的成功且未回滚;第二次的回滚;第三次没有执行
易得A方法也不会回滚
2、A开启事务,B开启事务
@Transactional
public void A() {
// 循环调用
foreach(B());
}
@Transactional
public void B() {
}
执行结果:第二个B报错
第一次更新成功但回滚,第二次回滚,第三次未执行
易得A方法会回滚
二、A方法中调用B,C,D方法
其实跟上面是一个意思,只是把第二个B换成C,第三个换成D
1、A未开启事务,B、C、D未开启事务
public void A() {
B();
C();
D();
}
@Transactional
public void B() {
}
@Transactional
public void C() {
}
@Transactional
public void D() {
}
执行结果:C报错
B方法未回滚,C方法回滚,D方法未执行
易得A方法也不会回滚
三、A方法调用同一个类中(public)B方法
class Test {
@Transactional
public void A() {
update// 第一次更新
B(); // 调用B方法
update// 第二次更新
}
public void B() {
update// 第一次更新
throw Exception // 报错
update// 第二次更新
}
}
执行结果:
更新A1回滚,更新B1回滚,更新B2未执行,更新A2未执行
四、A方法调用同一个类中(private)B方法
class Test {
@Transactional
public void A() {
update// 第一次更新
B(); // 调用B方法
update// 第二次更新
}
private void B() {
update// 第一次更新
throw Exception // 报错
update// 第二次更新
}
}
执行结果:
更新A1回滚,更新B1回滚,更新B2未执行,更新A2未执行