Java中的事务


到处抄的,有问题评论写一下。

一、理解

为了实现数据的安全访问。

二、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未执行
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值