事务的四个特性
要了解事务并发我们先要了解事务的四个特性:
原子性(Atomic),事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行
一致性(Consistent),事务在完成时,必须使所有的数据都保持一致状态。
隔离性(Insulation),由事务并发所作的修改必须与任何其它并发事务所作的修改隔离。
持久性(Duration),事务完成之后,它对于系统的影响是永久性的。
事务并发
为获得更好的运行性能,各类数据库都允许多个事务同时运行,这便是事务并发
事务并发所带来的的问题
① 第一类丢失更新
② 第二类丢失更新
③ 脏读
④ 幻读
⑤ 不可重复读
事务隔离机制
由于事务并发会带来以上五种问题,每一款数据库都有自己的事务处理机制
未提交读 Read UnCommited
提交读 Read Commited
可重复读 Read Repeatable
可串行读 Read Serializable
市面上的数据库都不会选择可串行读和未提交读,而是选择可重复读和提交读,数据库本身默认事务隔离级别可以解决事务并发中的脏读、幻读、不可重复读,剩下的第一类和第二类丢失更新需要由JPA解决:
悲观锁:修改一行数据的时候,将整张表锁定(用户体验非常差)
乐观锁:修改一行数据的时候,就只锁定当前行数据
乐观锁
乐观锁的实现方式有两种:
1.时间戳:datetime作为数据类型
2.版本号:integer作为数据类型
实现原理都一样:在一张表中添加一个列,数据类型采用datetime或者是integer,区别在于integer比datetime所占用的数据库空间更小
代码模拟
我们通过模拟商品秒杀来实现乐观锁
domain实体类:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Version;
@Entity
public class Product {
@Id
@GeneratedValue
private Long id;
//商品名称
private String name;
//商品剩余数量
private Integer number;
//@Version 注解就表示JPA要使用乐观锁
@Version
private Integer version;
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
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 Integer getNumber() {
return number;
}
public void setNumber(Integer number) {
this.number = number;
}
}
测试类:
import cn.itsource.domain.Product;
import org.junit.Test;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class OptimisticLock_Test {
@Test
public void test() throws Exception{
EntityManagerFactory factory = Persistence.createEntityManagerFactory("jpa_day04");
EntityManager entityManager01 = factory.createEntityManager();
EntityManager entityManager02 = factory.createEntityManager();
entityManager01.getTransaction().begin();
entityManager02.getTransaction().begin();
Product product01 = entityManager01.find(Product.class, 1L);
Product product02 = entityManager02.find(Product.class, 1L);
//开始秒杀
product01.setNumber(product01.getNumber() - 1);
entityManager01.getTransaction().commit();
/**
* 第二个事务开始秒杀
* 这一行数据已经被另一个事务修改了或者删除了
* 使用JPA的乐观锁,秒杀失败就一定会报出StaleObjectStateException异常
*/
product02.setNumber(product02.getNumber() - 1);
entityManager02.getTransaction().commit();
entityManager01.close();
entityManager02.close();
factory.close();
}
}
数据库的变化:
秒杀前:
秒杀后: