JPA锁机制

JPA锁机制概述

数据库并发访问的时候,为了保证操作数据的完整性,往往会对并发数据的更新做出限制。例如,允许一个Session进行更新处理,其他Session必须等此Session更新完成后才可以进行更新处理,这样的机制就称为数据库锁机制。JPA中也支持锁机制处理,且主要支持两类锁。

  • 悲观锁(Pessimistic):假设数据访问一直存在并发更新。悲观锁一直都存在,依靠的是数据库的锁机制。
  • 乐观锁(Optimistic):假设不会进行数据并发访问(不会同时出现数据更新处理)。乐观锁主要依靠算法来实现。设置一个版本号,通过版本号来判断当前的Session能否进行更新。

要实现JPA锁机制,可以通过EntityManager接口来完成。EntityManager接口定义的数据锁操作如下。

编号方法类型描述
1public find(Class entityClass,Object primaryKey,LockModeType lockMode)普通使用锁模式查询
2public void lock(Class entityClass,LockModeType lockMode)普通为持久化对象设置锁

锁处理主要是通过javax.persistence.LockModeType枚举类定义锁模式。

测试时使用的SQL

CREATE TABLE `dept` (
  `deptno` bigint NOT NULL AUTO_INCREMENT,
  `avgsal` double NOT NULL,
  `createdate` date DEFAULT NULL,
  `dname` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `num` int NOT NULL,
  `vseq` bigint DEFAULT '0' COMMENT '版本号',
  PRIMARY KEY (`deptno`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

测试时所用的实体类:

package com.xiyue.leaspring.po;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.Cacheable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Version;

@SuppressWarnings("serial")
@Cacheable(true)
@Entity
@Table(name = "dept")
public class Dept implements Serializable {//持久化类
	@Id//主键类
	@GeneratedValue(strategy = GenerationType.IDENTITY)//主键生成方式
	private Long deptno;//字段映射(属性名称=字段名称)
	private double avgsal;
	@Temporal(TemporalType.DATE)
	private Date createdate;
	private String dname;
	private int num;
	@Version
	private Long vseq;
	
	public Long getDeptno() {
		return deptno;
	}
	public void setDeptno(Long deptno) {
		this.deptno = deptno;
	}
	public double getAvgsal() {
		return avgsal;
	}
	public void setAvgsal(double avgsal) {
		this.avgsal = avgsal;
	}
	public Date getCreatedate() {
		return createdate;
	}
	public void setCreatedate(Date createdate) {
		this.createdate = createdate;
	}
	public String getDname() {
		return dname;
	}
	public void setDname(String dname) {
		this.dname = dname;
	}
	public int getNum() {
		return num;
	}
	public void setNum(int num) {
		this.num = num;
	}
	@Override
	public String toString() {
		return "Dept [deptno=" + deptno + ", avgsal=" + avgsal + ", createdate=" + createdate + ", dname=" + dname
				+ ", num=" + num + "]";
	}
}

悲观锁

悲观锁认为,用户的并发访问会一直发生,因此在整个处理中会采用锁机制对事务内的操作数据进行锁定,这样其他的事务就无法进行该数据的更新操作。
LockModeType枚举类对悲观锁提供了4种处理模式。

  • PESSIMISTIC_READ:只要事务读实体,实体管理器就会锁定实体,直到事务完成(提交或回滚)后才解锁处理。这种锁模式不会阻碍其他事务读取数据。
  • PESSIMISTIC_WRITE:只要事务更新实体,实体管理器就会锁定实体。这种锁模式强制尝试修改实体数据的事务串行化,当多个并发更新事务出现,导致更新失败几率较高时,使用这种锁模式。
  • PESSIMISTIC_FORCE_INCREMENT:事务读实体时,实体管理器就锁定实体。事务结束后,增加实体的版本属性,即使实体未做修改。
  • NONE:不使用锁。
/**
 * 使用悲观锁。
 */
@Test
public void findPessimismLock() {
	JPAEntityFactory.getEntityManager().getTransaction().begin();//开启事务
    JPAEntityFactory.getEntityManager().find(Dept.class,3L,
    	LockModeType.PESSIMISTIC_WRITE);
	JPAEntityFactory.getEntityManager().getTransaction().rollback();
}
Hibernate: 
    select
        dept0_.deptno as deptno1_0_0_,
        dept0_.avgsal as avgsal2_0_0_,
        dept0_.createdate as createda3_0_0_,
        dept0_.dname as dname4_0_0_,
        dept0_.num as num5_0_0_ 
    from
        dept dept0_ 
    where
        dept0_.deptno=? for update

查询语句中,使用数据库锁机制FOR UPDATE语句进行了数据锁定,这样当此事务提交或回滚前,该数据都无法被其他Session修改。

乐观锁

JPA早期提供的锁机制是乐观锁。乐观锁通常假设不存在多个事务修改同一条数据的情况。乐观锁需要在数据表上增加一个表示数据版本的编号,进行数据更新时通过此编号判断是否有其他Session进行过数据更新。判断过程如下:

  • 第一个Session与第二个Session同时读取一条数据,该数据的版本号为100。
  • 第一个Session更新数据时(此时第二个Session未关闭),自动将版本号由100修改为101。
  • 第二个Session读取时版本编号为100,保存时数据版本编号为101,由于版本编号不同,无法进行数据更新处理。

对于乐观锁,LockModeType枚举类有如下两种锁处理模式。

  • OPTIMISTIC:和READ锁模式相同,JPA 2.0仍然支持READ锁模式,但明确指出在新应用程序中推荐使用OPTIMISTIC。
  • OPTIMISTIC_FORCE_INCREMENT:和WRITE锁模式相同,JPA 2.0仍然支持WRITE锁模式,在新应用程序中明确推荐使用OPTIMISTIC_FORCE_INCREMENT。
/**
* 定义数据库脚本,为表中追加版本编号
*/
@Test
public void findOptimisticLock() {
	JPAEntityFactory.getEntityManager().getTransaction().begin();//开启事务
	Dept dept = JPAEntityFactory.getEntityManager().find(Dept.class,3L
		,LockModeType.OPTIMISTIC_FORCE_INCREMENT);//乐观锁
	dept.setNum(9999);//修改数据
	JPAEntityFactory.getEntityManager().getTransaction().commit();//可以回滚或提交
}
Hibernate: 
    select
        dept0_.deptno as deptno1_0_0_,
        dept0_.avgsal as avgsal2_0_0_,
        dept0_.createdate as createda3_0_0_,
        dept0_.dname as dname4_0_0_,
        dept0_.num as num5_0_0_,
        dept0_.vseq as vseq6_0_0_ 
    from
        dept dept0_ 
    where
        dept0_.deptno=?
Hibernate: 
    update
        dept 
    set
        avgsal=?,
        createdate=?,
        dname=?,
        num=?,
        vseq=? 
    where
        deptno=? 
        and vseq=?
Hibernate: 
    update
        dept 
    set
        vseq=? 
    where
        deptno=? 
        and vseq=?

如果此时有多个Session进行数据更新处理,当版本号不匹配时,将出现org.hibernate.StaleStateException异常。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

追Star仙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值