JPA进阶-事务隔离

JPA进阶–事务隔离

JPQL查询

JPQL和SQL很像,查询关键字都是一样的
唯一的区别是:JPQL是面向对象的

规则

JPA的查询语言,类似于sql

里面不能出现表名,列名,只能出现java的类名,属性名,区分大小写

出现的sql关键字是一样的意思,不区分大小写

不能写select * 要写select 别名

事务

事务四个特性:

原子性

一组操作不可分割(同生共死)

一致性

操作结果动态一致(能量守恒)

隔离性

同时多个线程操作,相互没有影响

持久性

修改以后将数据保存到数据库中

事务产生的问题

1.丢失更新

当事务A和事务B同时修改某行的值,

事务A将数值改为0并提交,购买了一件

事务B将数值改为0并提交,也购买了一件。这时数据的值为0,事务A所做的更新将会丢失。(相当于就卖出去2件商品)

2.脏读

读取未提交数据,A事务读取B事务尚未提交的数据,此时如果B事务发生错误并执行回滚操作,那么A事务读取到的数据就是脏数据

3.幻读

前后多次读取的数据总量不一致

事务A在执行读取操作,需要两次统计数据的总量,前一次查询数据总量后,此时事务B执行了新增数据的操作并提交后,这个时候事务A读取的数据总量和之前统计的不一样,这便产生了幻读

4.不可重读读

前后多次读取,数据内容不一致

事务A将自己读取了自己的年龄为18,此时操作还在继续,这时候事务B修改了事务A的年龄为20,提交了事务,事务A再次读取自己的年龄时变为了20

(面试必问)数据库事务的隔离机制(4种)

READ UNCOMMITTED(未提交读)

这个级别,事务中的修改,即使没有提交,对其他事务也都是可见的。事务可以读取未提交的数据,被称为脏读(Dirty Read),这个级别性能不会比其他级别好太多,但缺乏其他级别的很多好处,一般很少使用。

READ COMMITTED(提交读)
 这个级别是大多数数据库系统的默认隔离级别(但MySQL不是)。一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的。这个级别也叫作不可重复读(nonrepeatable read),因为两次执行同样的查询,可能会得到不一样的结果。

REPEATABLE READ(可重复读)
 该级别保证了在同一个事务中多次读取同样记录的结果是一致的,但依然无法解决另外一个幻读(Phantom Read)的问题。幻读,指的是当某个事物在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行(Phantom Row)。InnoDB 和 XtraDB 存储引擎通过多版本并发控制(MVCC)解决了幻读的问题。可重复读是MySQL的默认事务隔离级别。

ERIALIZABLE(可串行化)
 最高的隔离级别,强制事务串行执行,避免了前面说的幻读的问题。但每次读都需要获得表级共享锁,读写相互都会阻塞

对于隔离机制,我们一般使用默认的即可

悲观所

给事务加上锁,同一时间只允许一个线程进操作,如果事务没有被释放,就会造成其他事务处于等待,效率低,一般不太用到

乐观锁

总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,允许同时读取,但是不可以修改

使用乐观锁实现一个秒杀场景

实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package com.ifueen.domian;

import javax.persistence.*;

@Entity
@Table(name = "shaky")
public class Shaky {
@Id
@GeneratedValue
private Long id;
private String name;

@Column(name = "num")
private Integer num;

@Version
private Integer version;

public Integer getVersion() {
return version;
}

public void setVersion(Integer version) {
this.version = version;
}

public Integer getNum() {
return num;
}

public void setNum(Integer num) {
if (num<0){
throw new RuntimeException("商品已经抢完,请下次早点");
}else {
this.num = num;
}
}

@Override
public String toString() {
return "Shaky{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Shaky() {
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.ifueen.test;

import com.ifueen.domian.Shaky;
import com.ifueen.util.JPAUtil;
import org.junit.Test;

import javax.persistence.EntityManager;

public class ShakyTest {

/**
* 测试秒杀
*/
@Test
public void test(){
//第一次开启
EntityManager jpa = JPAUtil.getJpa();
jpa.getTransaction().begin();

//第二次开启
EntityManager jpa1 = JPAUtil.getJpa();
jpa1.getTransaction().begin();


//第一次查询
Shaky shaky = jpa.find(Shaky.class, 1L);
shaky.setNum(shaky.getNum()-1);

//同时第第二个人查询
Shaky shaky1 = jpa1.find(Shaky.class, 1L);

//第一个人提交事务
jpa.getTransaction().commit();
jpa.close();

shaky1.setNum(shaky1.getNum()-1);
//第二个人提交事务
jpa1.getTransaction().commit();
jpa1.close();
}


}
❤赏点钱让我买杯快乐水8❤