一、引言
先来大概普及一下数据库锁的概念,以下解释来源网络部分
悲观锁:时刻保持着一个悲观的态度,对谁都不信任,总想着别人会修改我的数据,所以为了防止别人修改,每次都会上锁,防止别人修改自己的数据。导致的后果就是每次想要拿到数据就必须要等待拿到锁,是一个很浪费时间的过程,如果访问量很大就是悲观锁一个致命的缺陷。
乐观锁:乐观锁对这个世界都很乐观,对每个想要获取数据的操作,他都会认为大家不会对自己的数据进行修改,所以不会上锁,在访问量很大的时候相比于悲观锁,节省了很多时间,用户不需要等待获取锁。
如果说大量读取数据操作的时候,适合使用乐观锁。如果冲突较多建议使用悲观锁。
悲观锁实现方式是数据库采用加锁的机制,而乐观锁最常见的手动就是通过版本号,每次更新的时候需要判断版本号是否一致,如果一致才能正常更新,反之更新失败。
二、具体现实
步骤一:配置乐观锁插件,这个采用的SpringBoot的配置方式。
@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { // MybatisPlus拦截器 MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 添加:分页插件 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 添加:乐观锁插件 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); // 添加:防止全表更新与删除插件 interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor()); return interceptor; } }
步骤二:接着数据库表中增加一个字段用于记录版本,我这里使用 int
类型,默认值为 0(默认值不能为 null,否则乐观锁无效)
步骤三:在实体类版本号的字段上加注解
@Data public class UserInfo { private Integer id; private String userName; private String passWord; private Integer age; private String email; @Version private Integer version; }
步骤四:测试调用,从SQL语句得出MP会把设置进去的版本号当作更新条件,并且版本号+1更新进去。
@Test public void update(){ // 更新对象 User user = new User(); user.setEmail("Test1111@email.com"); user.setId(1L); // 需要把之前从数据库读出来的版本号设置进去 user.setVersion(1); int update = userMapper.updateById(user); System.out.println(update); }
日志信息:
DEBUG==> Preparing: UPDATE sys_user SET email=?, update_time=?, version=? WHERE id=? AND version=? AND is_delete='0' DEBUG==> Parameters: Test1111@email.com(String), 2019-09-19T16:00:38.149(LocalDateTime), 2(Integer), 1(Long), 1(Integer) DEBUG<== Updates: 1
三、使用注意细节
细节一:支持的数据类型只有:int, Integer, long, Long, Date, Timestamp, LocalDateTime
细节二:整数类型下 newVersion = oldVersion + 1
细节三:newVersion
会回写到 entity
中
细节四:仅支持 updateById(id)
与 update(entity, wrapper)
方法
细节五:在 update(entity, wrapper)
方法下, wrapper
不能复用,这里小编给大家演示一下把,通过执行SQL语句看出,第一次更新是成功了,但是第二次更新失败了,在条件后面有两个版本号的条件。
@Test public void updateByMyWrapper() { // 条件构造器 QueryWrapper<User> wrapper = Wrappers.query(); wrapper.eq("name", "admin"); // 对象1 User user = new User(); user.setEmail("Test@email.com"); user.setVersion(2); userMapper.update(user,wrapper); // 对象2 User user2 = new User(); user2.setEmail("Test2@email.com"); user2.setVersion(3); // 注意,注意,这里的wrapper,和上面是是用的同一个 userMapper.update(user2,wrapper); }
日志信息:
updateFill...... DEBUG==> Preparing: UPDATE sys_user SET email=?, update_time=?, version=? WHERE is_delete='0' AND name = ? AND version = ? DEBUG==> Parameters: Test@email.com(String), 2019-09-19T16:06:26.172(LocalDateTime), 3(Integer), admin(String), 2(Integer) DEBUG<== Updates: 1 updateFill...... DEBUG==> Preparing: UPDATE sys_user SET email=?, update_time=?, version=? WHERE is_delete='0' AND name = ? AND version = ? AND version = ? DEBUG==> Parameters: Test2@email.com(String), 2019-09-19T16:06:26.238(LocalDateTime), 4(Integer), admin(String), 2(Integer), 3(Integer) DEBUG<== Updates: 0
其他Demo代码参考:
1. 我们先后获取同一条数据,获取后再依次修改更新:
UserInfo user1 = userInfoMapper.selectById(53); UserInfo user2 = userInfoMapper.selectById(53); user1.setAge(111); if(userInfoMapper.updateById(user1) > 0 ){ System.out.println("user1 更新成功"); } else { System.out.println("user1 更新失败,该记录已被其他人修改!"); } user2.setAge(222); if(userInfoMapper.updateById(user2) > 0 ){ System.out.println("user2 更新成功"); } else { System.out.println("user2 更新失败,该记录已被其他人修改!");
2. 运行结果如下,可以看到第一次更新成功,第二次失败,说明乐观锁生效了:
摘自: