什么是锁?
- 业务逻辑的实现过程中,往往需要保证数据访问的排他性,也就是说,我们需要一些机制来保证选取的数据在使用过程中不会被外界修改,这样的机制,就是“锁”,即给我们选定的目标数据上锁,使其无法被其它程序来修改
- 从另外的一个角度的看在想 “隔离级别” 不就是为了保证高并发数据安全性吗。既然有了这个为什么还有锁机制啊?隔离级别的安全控制是整体一个大的方面,而锁机制更加的灵活,它执行的粒度可以很小,可以在一个事务中存在。
- 而且 有了锁机制数据安全可谓是如虎添翼安全性更是大大的提高
- 所谓的锁就是暴力的把资源归为自己所有
- 针对锁的问题,Hibernate也做出了两种设定:悲观锁和乐观锁
悲观锁
-
1.悲观锁:悲观锁,正如其名,他是对数据库而言的,数据库悲观了,他感觉每一个对他操作的程序都有可能产生并发。它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)
简而言之:假定任何时刻存取数据时,都可能有另一个客户也正在存取同一笔数据,为了保持数据被操作的一致性,于是对数据采取了数据库层次的锁定状态
再次简而言之:害怕数据出现读错就整体的上严格的锁
补充模块:Hibernate的悲观锁是依靠数据库的支持实现的,本质上就是数据库的悲观锁 -
1.3那么Hibernate 的加锁模式有那些:
LockMode.NONE :无锁机制。
LockMode.WRITE :Hibernate 在 Insert 和 Update 记录的时候会自动获取。
LockMode.READ :Hibernate 在读取记录的时候会自动获取
LockMode.FORCE:强制上锁
以上这三种锁机制一般由 Hibernate 内部使用,言外之意就是我们不用管了
-
1.4Hibernate中怎么实现使用悲观锁呢?
Criteria.setLockMode()
Query.setLockMode()
session.load()
session.get() -
在Hibernate高版本中的时候已经不让用户自己设定锁机制了(了解即可)
小结:HIbernate的悲观锁的实现是依靠数据库来实现的
在低版本的hibernate中,程序员可以通过setLoclMode( )实现对程序添加悲观锁
Query query = session.createQuery("from StudentEntity ");
query.setLockMode("student",LockMode.WRITE); //加锁
Thread.sleep(9000);
List <StudentEntity> list = query.list();
for (StudentEntity student : list) {
System.out.println(student);
}
上锁,要在hql语句执行之前进行加锁功能,然后让其睡眠不让其结束
然后打来mysql可视化工具进行修改数据,会发现一直转圈圈,无法完成,证明悲观锁完成
- 咱们的版本是5所以没有效果,(今天写了一天笔记懒得改了)了解即可,就是通过 select xxxx from 表名 for update 来上锁,其他用户无法对表进行操作
乐观锁
1.乐观锁:相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本(Version)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个"version"字段来实现。
乐观锁的工作原理:读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
简而言之:乐观不害怕读脏数据,所以不上严格的锁
Hibernate为乐观锁提供了2中实现:
1)基于version:说白了就是给表中添加一个标识列
2) 基于timestamp:说白了就是给表添加上时间戳
2.我们的例子是基于version来实现的
- 数据库添加字段
- 在项目中添加属性,get set方法和tostring方法
- 配置使用乐观锁
- 原本的数据
package bj.ft.an.test;
import bj.ft.an.enity.UserEntity;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import java.util.List;
/**
* @author LXY
* @desc
* @time 2022-10-15 20:20
*/
public class test002 {
public static void main(String [] args){
//1.加载配置
//默认去加载Src下的名字为hibernate.cfg.xml的文件为了启动hibernate 框架
Configuration config= new Configuration().configure();
//2.创建sesionFactory对象
SessionFactory sesionFactory =config.buildSessionFactory();
//3.获取session
Session session = sesionFactory.openSession();
//4.开启事务
Transaction transation = session.beginTransaction();
//3.获取session
Session session01 = sesionFactory.openSession();
//4.开启事务
UserEntity userEntity = session.get(UserEntity.class, 3);
UserEntity userEntity01 = session01.get(UserEntity.class, 3);
System.out.println("修改完之前的值是"+userEntity01.getVrrsion()+"====="+userEntity.getVrrsion());
Transaction transation01 = session01.beginTransaction();
transation01.commit();
System.out.println("修改完之后的值是"+userEntity01.getVrrsion()+"====="+userEntity.getVrrsion());
//6.提交事务
transation.commit();
//7:关闭相关对象
session.close();
sesionFactory.close();
}
}
- 查询出来的版本号是一样的
- 接下来赋值,我们给age把当前查询的版本号赋值上去(为了能每次直观的看清版本号的变化)
因为执行了一次修改,版本号变为了11
public static void main(String [] args){
//1.加载配置
//默认去加载Src下的名字为hibernate.cfg.xml的文件为了启动hibernate 框架
Configuration config= new Configuration().configure();
//2.创建sesionFactory对象
SessionFactory sesionFactory =config.buildSessionFactory();
//3.获取session
Session session = sesionFactory.openSession();
//4.开启事务
Transaction transation = session.beginTransaction();
//3.获取session
Session session01 = sesionFactory.openSession();
//4.开启事务
UserEntity userEntity = session.get(UserEntity.class, 3);
UserEntity userEntity01 = session01.get(UserEntity.class, 3);
System.out.println("修改完之前的值是"+userEntity01.getVrrsion()+"====="+userEntity.getVrrsion());
Transaction transation01 = session01.beginTransaction();
userEntity01.setAge(userEntity01.getVrrsion());
transation01.commit();
System.out.println("修改完之后的值是"+userEntity01.getVrrsion()+"====="+userEntity.getVrrsion()) ;
//6.提交事务
transation.commit();
//7:关闭相关对象
session.close();
sesionFactory.close();
}
接下来看控制台
为什版本号不一样呢,我们2个session模拟了两个用户,体现出了并发
- 两个用户同时查询session1 session2,session1和2查询的是一样的数据,2去修改数据,版本号+1,但是1还是查询的之前的数据,这就是并发了。(可能描述的不是很好,看控制台就懂了)
/3.获取session
Session session = sesionFactory.openSession();
//4.开启事务
//3.获取session
Session session01 = sesionFactory.openSession();
//4.开启事务
UserEntity userEntity = session.get(UserEntity.class, 3);
UserEntity userEntity01 = session01.get(UserEntity.class, 3);
System.out.println("修改完之前的值是"+userEntity01.getVrrsion()+"====="+userEntity.getVrrsion());
Transaction transation01 = session01.beginTransaction();
userEntity01.setAge(userEntity01.getVrrsion());
transation01.commit();
System.out.println("修改完之后的值是"+userEntity01.getVrrsion()+"====="+userEntity.getVrrsion()) ;
Transaction transation = session.beginTransaction();
userEntity.setAge(userEntity.getVrrsion());
//6.提交事务
transation.commit();
我们强制更新就会报错
为什么呢,因为获取的版本不一样,加入甲和已两个,获取的版本号都是11,然后甲更新了一下,版本号变为了12,但是已获得的版本号还是11,11<12,所以不能执行操作
-解决方案
f (userEntity01.getVrrsion()!=userEntity.getVrrsion()){
System.out.println("不相同");
session.clear();
Transaction transation = session.beginTransaction();
UserEntity userEntitysss = session.get(UserEntity.class, 3);
userEntitysss.setAge(userEntitysss.getVrrsion());
//6.提交事务
transation.commit();
}