乐观锁
什么是乐观锁?用来解决什么问题?怎么实现乐观锁?
乐观锁
主要用来解决丢失更新问题
什么是丢失更新
当两个用户,用户A和用户B对同一数据C的年龄(10)进行修改时,因为即使在高并发的时候,提交也会有先后顺序,A将C的年龄10修改为20之后,B将C的年龄10修改为30,最后查询数据时,查询的是B修改后的数据,这就是丢失更新正确的情况应该是,A先将年龄修改为20,B修改20为30,而不是两个用户同时修改10.
怎么解决丢失更新问题
针对丢失更新问题,一般有两种解决方案:悲观锁、乐观锁。悲观锁: 当A用户进行修改时,其他用户等待,不能对数据进行操作,等待A操作完成,效率低乐观锁:在数据中添加一个字段,版本version,当查询数据时获取当前版本号,更新时带着版本号执行更新后版本号+1,如果版本号不对,则更新失败。
什么时候使用乐观锁
提到乐观锁就要简单回顾一下事务,事务有四大特性:原子性 一致性 隔离性 持久性当我们单个用户操作的时候,则不需要考虑事务的隔离性,当产生高并发时,多个用户同时对数据库进行操作,如果不考虑事务隔离性,在用户进行查询操作时,会产生脏读、幻读、不可重复读等问题.在用户进行修改操作时,会产生丢失更新问题.此时用到乐观锁
乐观锁的实际应用场景
12306抢票,多人同时抢同一张票,当第一个人支付成功之后,其他人则不能进行支付.
OptimisticLockerInnerInterceptor
当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:
- 取出记录时,获取当前version
- 更新时,带上这个version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果version不对,就更新失败
乐观锁配置需要两步
package com.xmx.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* 拦截器
*/
@Configuration
@EnableTransactionManagement
public class MyInterceptor {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
//乐观锁
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
//分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
在实体类的字段上加上@Version注解,数据库中的字段名要和实体类中的属性一一对应
package com.xmx.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.Version;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UsersPlus {
/*
类名要和表名一致
驼峰命名
*/
private Long id;
private String name;
private Integer age;
private String email;
@TableField(fill = FieldFill.INSERT)
private Date inserttime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updatetime;
//逻辑删除字段 0表示正常,1表示删除
@TableField
private Integer deleted;
//乐观锁
@Version
private Integer version;
public UsersPlus(Long id, String name, Integer age, String email) {
this.id = id;
this.name = name;
this.age = age;
this.email = email;
}
}
说明:
支持的数据类型有:
int,Integer,long,Long,Date,Timestamp,LocalDateTime
整数类型下
newVersion = oldVersion + 1
newVersion 会回写到 entity 中
仅支持 updateById(id) 与 update(entity, wrapper) 方法
在 update(entity, wrapper) 方法下, wrapper 不能复用!!!