第一节 Mysql悲观锁
- 悲观锁分为下面这两种锁,读锁与写锁。
1.1 读锁/共享锁【用的少】
- 读锁可被其它线程所共享,如果是读取,大家都可以用这把锁读到数据。
- select * from table lock in share mode;#(读锁、共享锁)
- 演示步骤:(终端左A右B)
- A终端开启事务添加读锁,此时A拿到锁(共享锁,都可以拿)
- B终端开启事务添加读锁,此时B拿到锁,锁在B中
- A终端执行更新操作,不执行,要等待B提交事务后(释放锁),A才能执行,否则A一直等待直到超时(超时后也不更新)
- B终端提交后,A才执行更新
- A终端提交,结束
#开启事务
START TRANSACTION;
#添加读锁或者共享锁,其它线程可以用这把锁
SELECT * FROM t_customer LOCK IN SHARE MODE;
#如果其它线程用了这把锁,没有提交或释放锁,不能执行更新
UPDATE t_customer SET `customerName` = 'shuyy' WHERE customerId=1;
#提交事务
COMMIT;
1.2 写锁/排他锁【用的多】
-
写锁不能共享,只要有人为这个数据加入了写锁,其他人就不能为该数据加任何锁。
-
select * from table for update; #(写锁、排它锁)
-
该锁可以锁一张表,或者锁一行记录
-
演示步骤:(终端左A右B)
- A终端开事务添加写锁
- B终端开事务读表添加写锁【读不到数据】
- A终端更新
- A终端提交
- B终端提交
- 锁表:
- 锁一条记录:
#开启事务
START TRANSACTION;
#给表添加写锁,其它线程无法读取这个表,当提交后才可以读取这个表
#锁表【不推荐使用】
SELECT * FROM t_customer FOR UPDATE;
#锁一条记录【常用】例如:对钱的操作
SELECT * FROM t_customer WHERE customerId=1 FOR UPDATE;
#把这条记录锁住后别人不能更新不能添加锁,可以查看到,必须要等操作那一条记录的事务提交后才能操作这条记录
#更新
UPDATE t_customer SET `customerName` = 'shuyy1' WHERE customerId=1;
#提交事务
COMMIT;
1.3 hibernate中使用写锁
通过get添加写锁
//写锁/排他锁(别人拿不到的锁)的实现
@Test
public void test1(){
Session session = HibernateUtils.openSession();
//开启事务
session.beginTransaction();
//给行添加写锁,如:SELECT * FROM t_customer WHERE customerId=1 FOR UPDATE;
Customer customer = (Customer) session.get(Customer.class, 1, LockOptions.UPGRADE);
//session.get(Customer.class,1, LockOptions.READ);//读锁
System.out.println(customer);
//提交事务
session.getTransaction().commit();
session.close();
}
- 在终端开启事务给行添加写锁,再运行程序,程序不会执行(虽然打印了sql但是程序并未执行)会等待终端commit提交事务,否则到超时也不会执行
- 终端提交后,程序会立马执行,能够给行加锁(这里的程序运行完就提交事务了)
注意:这里虽然是在sql后添加 for update实现写锁,但是hql中不能这么直接在hql后写,否则报错。
通过query添加写锁
//写锁/排他锁的注意事项
@Test
public void test2(){
Session session = HibernateUtils.openSession();
//开启事务
session.beginTransaction();
Query query = session.createQuery("from Customer where id = ?");
//Query query = session.createQuery("from Customer where id = ? for update");//错误写法
query.setLockOptions(LockOptions.UPGRADE);//添加写锁
query.setParameter(0,1);
System.out.println(query.uniqueResult());
//提交事务
session.getTransaction().commit();
session.close();
- 效果相同
- 注意不能使用update添加写锁,报错。
总结: 一个线程开启事务给一个表或一行数据添加写锁,该线程可以对这个表或一行记录进行查询更新等操作,直到该线程提交事务,别的线程不能给这个表添加写锁和更新数据,可以查询(看)到加锁的表或记录。
第二节 Hibernate乐观锁
- 乐观锁就是在表中添加一个version字段来控制数据的不一致性
- 在PO对象(javabean)提供version字段,表示版本字段,类型一般为Integer。
- 在Customer中添加version字段
- 在Customer.hbm.xml中配置version
- 注意version字段配置的位置,位置不对会报错(通过查看dtd约束知道要配置在id后property前)
- 配置完成后试运行,让表添加上对应的version字段。
- 默认的版本号为0
- 乐观锁,每次更新,数据不同,版本号才会加1(get有session缓存与快照对比,只有不同才会更新到数据库),否则版本号不变。
//乐观锁
@Test
public void test4(){
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer customer = (Customer) session.get(Customer.class, 1);
customer.setCustomerName("shu");//更新id为1的客户名
System.out.println(customer);
session.getTransaction().commit();
session.close();
}
6. 如果当前版本【1】比数据库版本【2】低就不更新,抛出异常
-
断点调试获取此时数据库版本号version为1
-
如果有一个线程在此时更新了数据库字段,版本号加1,为2
-
继续执行程序就会抛出异常,这一行已经被另一个事务更新或删除【版本低,保存不成功】
总结:
写锁是mysql数据库内部实现的,前后台都可以使用,使用时保证数据访问的安全性,即谁先访问加写锁,别的线程无法操作,要等待它结束(事务提交)才能访问操作。
乐观锁是hibernate通过对表添加一个字段version来实现的,通过更新时版本号的增加保证数据的安全。
第三节 Hibernate整合log4j
3.1 Log4j是什么?【java日志】
- Log4j的主要功能是在控制台输出固定格式的日志,并能把日志写在文件中,方便项目上线后的系统维护。
3.2 整合步骤
第一步:导入以下2个jar包
slf4j-log4j12-1.7.2.jar是桥接左右2个jar包作用(桥梁互通)
第二步:把下图的log4j.properties文件导入项目的src目录中