乐观锁是针对某种问题的解决方案,主要解决丢失更新问题。
如果不考虑事务的隔离性,就会产生几种读的问题:脏读、不可重复读、幻读(虚读)
如果不考虑事务的隔离性,也会产生写的问题:丢失更新问题(在并发情况下才有的问题)
事务的隔离性就是指在两个及以上的用户同时对同一个数据进行操作时所产生的问题。
什么是丢失更新?
多个人同时修改同一条记录,在这个过程中,谁最后提交事务,就会把之前的人提交的数据进行覆盖,这个过程就叫做丢失更新。
解决方案:
1、悲观锁(一般不用)
悲观锁的特点就是当一个人在操作一条数据时,别人都不能对这条数据进行操作,只有等这个人操作完成之后,其他人才能再对这条数据进行操作。(也叫串行操作)
例如张三在看新闻,如果按照悲观锁的方式,在张三看新闻的时候全国人民都不能看,如果张三要看一百年,那么全国人民就要等一百年之后才能看新闻,只有当张三不看之后别人才可以看,这个过程就叫做悲观锁。这种方式一定没有丢失更新的问题,但是这种方式的缺点也很明显,因为它是串行的,一次只能一个人操作,因此它的效率很低。所以悲观锁一般不使用。
2、乐观锁
主要适用场景:当要更新一条记录的时候,希望这条记录没有被别人更新,也就是说实现线程安全的数据更新。
乐观锁的实现方式:
①取出记录时,获取当前的version
②更新时,带上这个version
③执行更新时,set version = newVersion where version = oldVersion
④如果version不对,就更新失败
使用场景:12306网站抢票,如果只有一张票,很多人都能查询到,但是只能有一个人付款成功。
乐观锁的实现步骤:
(1)在数据库中添加 version 字段,作为乐观锁的版本号
--在数据库中的user表中添加一个version字段,用于实现乐观锁
ALTER TABLE `user` ADD COLUMN `version` INT
表结构:
(2)在对应的实体类中添加 version 属性,并且在这个属性上面添加 @Version 注解
package cn.henu.domain;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.util.Date;
@Data
public class User {
// @TableId(type = IdType.AUTO)//主键自动增长
@TableId(type = IdType.ID_WORKER)//mp自带策略,生成19位的ID值,数字类型使用这种策略,比如long
// @TableId(type = IdType.ID_WORKER_STR)//mp自带策略,生成19位的ID值,字符串类型使用这种策略,比如string
// @TableId(type = IdType.INPUT)//ID值不会帮我们生成,需要自己手动输入ID
// @TableId(type = IdType.NONE)//不用任何策略,也是需要自己手动输入ID
// @TableId(type = IdType.UUID)//每次帮我们生成一个随机的唯一的ID值
private Long id;
private String name;
private Integer age;
private String email;
//create_time
@TableField(fill = FieldFill.INSERT)//INSERT的含义就是添加,也就是说在做添加操作时,下面一行中的createTime会有值
private Date createTime;
//update_time
@TableField(fill = FieldFill.INSERT_UPDATE)//INSERT_UPDATE的含义就是在做添加和修改时下面一行中的updateTime都会有值,因为是第一次添加,还没有做修改(一般都使用这个)
private Date updateTime;
//version
@Version//版本号,用于实现乐观锁(这个一定要加)
@TableField(fill = FieldFill.INSERT)//添加这个注解是为了在后面设置初始值,不加也可以
private Integer version;
}
(3)写一个配置类,配置乐观锁插件
package cn.henu.config;
import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration//表示将这个类作为配置类
//@MapperScan("cn.henu.mapper"):在启动时扫描Mapper接口,找到里面的内容
@MapperScan("cn.henu.mapper")
public class MpConfig {
//乐观锁插件
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
(4)设置版本号 version 的初始值为1
package cn.henu.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component//注意在这个类上加@Component注解,或者@Service或者@Repository表示将这个类交给Spring进行管理
public class MyMetaObjectHandler implements MetaObjectHandler {
//使用mp实现添加的操作,这个方法就会执行
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
//设置版本号version的初始值为1
//不加这个也可以,version的默认值为null,加了就是设置version的值从1开始
this.setFieldValByName("version",1,metaObject);
}
//使用mp实现修改的操作,这个方法就会执行
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
(5)向表中添加一条数据,看 version 的值是否为1
//插入数据
@Test
void addUser3(){
User user = new User();
user.setName("zhaoliu");
user.setAge(50);
user.setEmail("zhaoliu@qq.com");
int insert = userMapper.insert(user);
System.out.println("insert:"+insert);
}
运行查看数据库中的数据:
(6)测试乐观锁,看 version 的值是否加1
//测试乐观锁,看version的值是否加1
@Test
void testOptimisticLocker(){
//根据id查询数据
User user = userMapper.selectById(1347470387336572930L);
//进行修改
user.setAge(60);
userMapper.updateById(user);
}