第二部份,引起锁机制的原理和解决方案:

测试环境搭建
第一步先建一个数据库表用于模拟商品购买。

CREATE TABLE product (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
stock INT NOT NULL,
version INT NOT NULL DEFAULT 0
);

  第二步,新建一个商品类,用于等于mapper 产生方法,如果用插件lombok,   可以在类上面加上@Data 自动生产 自动生成get set 方法

import com.baomidou.mybatisplus.annotation.*;

@TableName("product")
public class Product {
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
private Integer stock;
private Integer version;
// 省略Getter和Setter方法
}

第三步,编写 mapper 继承baseMapper, mapper 文件下面新建ProductMapper 接口

public interface ProductMapper extends BaseMapper<Product> {
}

最后编写测试用例:

 public void testConcurrentUpdate() {

        //1、小李获取数据
Product p1 = productMapper.selectById(1L);
System.out.println("小李取出的价格:" + p1.getPrice()); //2、小王获取数据
Product p2 = productMapper.selectById(1L);
System.out.println("小王取出的价格:" + p2.getPrice()); //3、小李加了50,存入数据库
p1.setPrice(p1.getPrice() + 50);
productMapper.updateById(p1);

p2.setPrice(p2.getPrice()-30);
productMapper.updateById(p2);
//最后的结果
//用户看到的商品价格
Product p3 = productMapper.selectById(1L);
System.out.println("最后的结果:" + p3.getPrice());
}

运行结果:明明正确的 业务逻辑  应该是120  结果双方都取出数据来修改,后面修改的覆盖了前面的,

解决方法一

直接锁住这个数据  实际上,在MyBatis中,并不直接使用Java代码来加数据库锁,而是通过编写特定的SQL语句来实现。使用数据库的 ​for update​语句可以进行行级锁定,这通常是在SQL查询中进行,例如:

SELECT * FROM product WHERE id = 1 FOR UPDATE; 这将锁定id为1的商品记录,直到事务结束才会释放锁。
在MyBatis中可以使用如下方式来执行带有 ​for update​的SQL语句: 
@Select("SELECT * FROM product WHERE id = #{id} FOR UPDATE") Product lockProductForUpdate(Long id); 

注意,​FOR UPDATE​关键字的使用可能因数据库类型而异,你需要根据使用的具体数据库来调整相应的SQL语句。另外,在实际业务中,请留意事务的控制和锁的释放时机,避免出现死锁等问题。并且要手工编写sql 比较麻烦我们后面乐观锁,只需要配置可以直接自动应用,以下是悲观锁的流程示例,属于伪代码

@Test
public void testConcurrentUpdateWithPessimisticLock() {
Product p = productMapper.selectById(1L); // 加悲观锁
productMapper.lockProductForUpdate(1L); // 修改价格
p.setPrice(p.getPrice() + 50);
productMapper.updateById(p); // 解锁 操作完后自动解锁 // 获取最终结果
Product finalProduct = productMapper.selectById(1L);
System.out.println("Final price: " + finalProduct.getPrice());
}

  解决方二   乐观锁

核心代码思路为以下截图:如果更新失败需要重新业务

操作流程:

第一步:加入乐观锁插件‘

在配置 文件中增加以下配置,这个配置文件由于要写很多配置 ,所以单独搞一个config 目录,下面新建一个配置文件,这个文件写类,然后加入配置 的注释,第统会自动识别为配置

说细配置如下:

package com.example.demo.config;

import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement; @EnableTransactionManagement
@Configuration
//@MapperScan("com.example.demo.mapper")
public class MyBatisPlusConfig {
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor(){
return new OptimisticLockerInterceptor();
} @Bean
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}
}

乐观锁配置

第二步

第三步,在类上写上版本注解“  到此为这经配置好 ,在没试的时候如果操作失败重新插入或更新就好,

测试代码:

 public void testConcurrentUpdate() {

        //1、小李获取数据
Product p1 = productMapper.selectById(1L);
System.out.println("小李取出的价格:" + p1.getPrice());
//2、小王获取数据
Product p2 = productMapper.selectById(1L);
System.out.println("小王取出的价格:" + p2.getPrice());
//3、小李加了50,存入数据库
p1.setPrice(p1.getPrice() + 50);
productMapper.updateById(p1);
p2.setPrice(p2.getPrice() - 30);
int result = productMapper.updateById(p2);
if (result == 0) {
System.out.println("小王更新失败");
//发起重试
p2 = productMapper.selectById(1L);
p2.setPrice(p2.getPrice() - 30);
productMapper.updateById(p2);
}
//最后的结果
//用户看到的商品价格
Product p3 = productMapper.selectById(1L);
System.out.println("最后的结果:" + p3.getPrice());
}

乐观锁使用测试

备注,由于我专门建了配置类,所以在主类的上配置都 可以迁过去,比如@MapperScan

package com.example.demo.config;

import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement; @EnableTransactionManagement
@Configuration
//@MapperScan("com.example.demo.mapper")
public class MyBatisPlusConfig {
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor(){
return new OptimisticLockerInterceptor();
} @Bean
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}
}

 详细的增删改测试用例

//查询多条
@Test
public void testSelectProductList() {
QueryWrapper<Product> queryWrapper = new QueryWrapper<>();
queryWrapper.gt("price", 50);
List<Product> productList = productMapper.selectList(queryWrapper);
productList.forEach(product -> System.out.println(product.getId() + " - " + product.getName() + " - " + product.getPrice()));
}
//更新多条
@Test
public void testUpdateProductList() {
UpdateWrapper<Product> updateWrapper = new UpdateWrapper<>();
updateWrapper.set("price", 200).ge("price", 100);
int rows = productMapper.update(new Product(), updateWrapper);
System.out.println("Updated rows: " + rows);
}
更新单条
@Test
public void testUpdateSingleProduct() {
Product product = new Product();
product.setId(1L);
product.setName("New Name");
int rows = productMapper.updateById(product);
System.out.println("Updated rows: " + rows);
}
//复杂查询
@Test
public void testSelectProductByComplexCondition() {
QueryWrapper<Product> queryWrapper = new QueryWrapper<>();
queryWrapper.like("name", "Product").or().eq("price", 100);
List<Product> productList = productMapper.selectList(queryWrapper);
productList.forEach(product -> System.out.println(product.getId() + " - " + product.getName() + " - " + product.getPrice()));
}
//联表查询
@Test
public void testSelectProductWithCategory() {
List<Map<String, Object>> productList = productMapper.selectMaps(new QueryWrapper<Product>()
.select("p.id as productId", "p.name as productName", "p.price", "c.name as categoryName")
.leftJoin("category c on c.id = p.category_id")
.orderByAsc("p.id")
);
productList.forEach(item -> {
System.out.println("Product ID: " + item.get("productId") + ", Product Name: " + item.get("productName") +
", Price: " + item.get("price") + ", Category Name: " + item.get("categoryName"));
});
}

高极查询构建器

mybtis-plus 虽然提供了查询 id 删除这些方法,但是涉及到or   条件 等于  多个值这样的查询时需要构造查询构器

wapper构造器蓝图

MyBatis-Plus是一个优秀的MyBatis增强工具,在它的条件构造器中,主要包括以下几个对象:QueryWrapper(普通查询条件构造器)和UpdateWrapper(更新条件构造器)。这两个对象都继承自AbstractWrapper,其中AbstractWrapper提供了一些基础的方法 。以下是 MyBatis-Plus 中常用的构建器及其条件方法:

  1. QueryWrapper 和 UpdateWrapper:

    • 这两个构建器都继承自 AbstractWrapper,因此它们共享许多条件方法。
    • 常用条件方法:
      • eq(String column, Object val): 等于条件
      • ne(String column, Object val): 不等于条件
      • gt(String column, Object val): 大于条件
      • ge(String column, Object val): 大于等于条件
      • lt(String column, Object val): 小于条件
      • le(String column, Object val): 小于等于条件
      • between(String column, Object val1, Object val2): 之间条件
      • notBetween(String column, Object val1, Object val2): 不在之间条件
      • like(String column, Object val): 模糊查询条件(使用 %val%
      • notLike(String column, Object val): 模糊查询的否定条件
      • in(String column, Collection<?> valList): 在集合中的条件
      • notIn(String column, Collection<?> valList): 不在集合中的条件
      • isNull(String column): 为空条件
      • isNotNull(String column): 不为空条件
      • ... 以及其他多个方法。
  2. LambdaQueryWrapper 和 LambdaUpdateWrapper:

    • 这两个构建器与 QueryWrapper 和 UpdateWrapper 类似,但它们支持 Lambda 表达式,使代码更加简洁且类型安全。
    • 条件方法与上述类似,但可以通过 Lambda 表达式直接引用实体类的属性,而不是使用字符串列名。
  3. QueryChainWrapper:

    • 这是一个链式查询构建器,允许你通过链式方法调用来构建查询条件。
    • 条件方法与 QueryWrapper 类似,但可以通过链式调用来组合多个条件。
  4. EntityWrapper(在某些版本中可能已被弃用):

    • 这是一个较早的构建器,用于包装实体类对象,并提供了一系列的条件查询方法。
    • 条件方法与上述类似,但主要是基于实体类属性进行设置。

请注意,随着 MyBatis-Plus 版本的更新,某些构建器或方法可能会被弃用或替换。因此,建议查阅官方文档以获取最新和最准确的信息。

总的来说,MyBatis-Plus 提供了丰富的构建器和条件方法,以满足各种查询和更新需求。你可以根据项目需求选择适合的构建器和方法来简化数据库操作

示例代码:

// 创建QueryWrapper对象
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// 构建查询条件
queryWrapper.eq("age", 25)
.like("name", "Jack")
.in("status", Arrays.asList(1, 2, 3)); // 查询数据库
List<User> userList = userMapper.selectList(queryWrapper);
UpdateWrapper对象:
功能:用于构建更新条件的条件构造器。
常见构建条件的方式:
a) set(String column, Object value):设置更新字段值
b) eq(String column, Object value):等于
c) ne(String column, Object value):不等于
d) gt(String column, Object value):大于
e) lt(String column, Object value):小于
f) ge(String column, Object value):大于等于
g) le(String column, Object value):小于等于
示例代码: // 创建UpdateWrapper对象
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
// 构建更新条件
updateWrapper.eq("age", 25)
.set("status", 1); // 更新数据
int rows = userMapper.update(user, updateWrapper);

  以下是一个复杂的Lambda表达式加上一个复杂的查询示例

分页插件操作流程

第一步引入插件:

 @Bean
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}

  以下是官方图参考

编写第一个测试用例:

    @Test
public void testSelectPage(){
Page<User> page = new Page<>(1,5);
Page<User> userPage = userMappter.selectPage(page, null);
List<User> records = userPage.getRecords();
// records.forEach(System.out::println);
System.out.println("总页数"+ userPage.getPages());
System.out.println("总计录"+ userPage.getTotal());
System.out.println("当前页"+ userPage.getCurrent());
System.out.println("每页大小"+ userPage.getSize());
System.out.println("有没有下页"+ userPage.hasNext());
System.out.println("有没有上文"+ userPage.hasPrevious());
}

  工作中有时候我们只要查一二例字段,会多一些无用的字段具为空,如下图的结果,解决方法用带map 的方法来构建

解决方法:

    public void testSelectMapsPage() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.select("id", "name");
Page<Map<String, Object>> page = new Page<>(1, 5);
Page<Map<String, Object>> pageParam = userMappter.selectMapsPage(page, queryWrapper);
List<Map<String, Object>> records = pageParam.getRecords();
records.forEach(System.out::println);
}

  

最后引出删除与软删除

先说一下传统怎么山删除。但是我们不建议删除数据,后期有用的数据可以用来做数据分析,所以最下面我们会采用软删除。

//删除一条
public void testDeleteByid(){
int result = userMappter.deleteById(1l);
System.out.println("删除了"+ result + "行");
}
//删除多条
public void testDeleteBatchIds(){
int result = userMappter.deleteBatchIds(Arrays.asList(10,11,12));
System.out.println("删除了"+ result + "行");
} //删除根据条件
@Test
public void testDeleteByMap(){
HashMap<String, Object> map = new HashMap<>();
map.put("name","Hello");
map.put("age",18);
int result = userMappter.deleteByMap(map);
System.out.println("删隆了"+ result+ "行");
}
}

软删除步聚餐

第一步 数据库存新增delect 字段  类型为: tinyint

第二步 在对象上新增注解

@Data
public class User {
// @TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email; @TableField(fill = FieldFill.INSERT)
private Date createTime; @TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime; @TableLogic
private Integer deleted;
}

  

java srpint boot 2.2.1 第二部份,乐观锁机制, 构造复杂查询条件,分页查询 相关内容,删除与软删除的更多相关文章

  1. 【java】spring-data-jpa 集成hibernate实现多条件分页查询

    初次接触spring-data-jpa,实现多条件分页查询. 基础环境 Spring Boot+spring-data-jpa+hibernate+mysql 1.接口 要继承这个接口,这个接口提供了 ...

  2. Java并发编程(05):悲观锁和乐观锁机制

    本文源码:GitHub·点这里 || GitEE·点这里 一.资源和加锁 1.场景描述 多线程并发访问同一个资源问题,假如线程A获取变量之后修改变量值,线程C在此时也获取变量值并且修改,两个线程同时并 ...

  3. posix第二篇-----linux 锁机制

    1 简介 锁机制(lock) 是多线程编程中最常用的同步机制,用来对多线程间共享的临界区(Critical Section) 进行保护. Pthreads提供了多种锁机制,常见的有: 1) Mutex ...

  4. Java 多线程与并发【原理第二部分笔记】

    Java 多线程与并发[原理第二部分笔记] 什么是Java内存模型中的happens-before Java内存模型,即JMM,本身是一种抽象的概念,并不是真实存在的,他描述的是一组规则或者说是一种规 ...

  5. 五分钟学会悲观乐观锁-java vs mysql vs redis三种实现

    1 悲观锁乐观锁简介 乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果 ...

  6. Java并发 行级锁/字段锁/表级锁 乐观锁/悲观锁 共享锁/排他锁 死锁

    原文地址:https://my.oschina.net/oosc/blog/1620279 前言 锁是防止在两个事务操作同一个数据源(表或行)时交互破坏数据的一种机制. 数据库采用封锁技术保证并发操作 ...

  7. Java Hibernate中的悲观锁和乐观锁的实现

    锁(locking) 业务逻辑的实现过程中,往往需要保证数据访问的排他性.如在金融系统的日终结算 处理中,我们希望针对某个cut-off时间点的数据进行处理,而不希望在结算进行过程中 (可能是几秒种, ...

  8. 对Java提供的锁机制的一些思考

    Java的数据会在CPU.Register.Cache.Heap和Thread stack之间进行复制操作,而前面四个都是在Java Threads之间共享,因此Java的锁机制主要用于解决Racin ...

  9. Java Spring Boot VS .NetCore (五)MyBatis vs EFCore

    Java Spring Boot VS .NetCore (一)来一个简单的 Hello World Java Spring Boot VS .NetCore (二)实现一个过滤器Filter Jav ...

  10. Java Spring Boot VS .NetCore (七) 配置文件

    Java Spring Boot VS .NetCore (一)来一个简单的 Hello World Java Spring Boot VS .NetCore (二)实现一个过滤器Filter Jav ...

随机推荐

  1. Qt-FFmpeg开发-视频播放(4)

    音视频/FFmpeg #Qt Qt-FFmpeg开发-视频播放[软解码 + OpenGL显示YUV420P图像] 目录 音视频/FFmpeg #Qt Qt-FFmpeg开发-视频播放[软解码 + Op ...

  2. js 判断闰年

    首先,我们需要了解闰年的判断方式 1.能被4整除 2.并且不能被100整除 3.或者被400整除的 其次我们再来回顾下函数封装的知识,所谓的函数封装就是将一段函数封装成一个工具,有人用到了拿过来就可以 ...

  3. 深入解读Prometheus Adapter:云原生监控的核心组件

    本文详述了Prometheus Adapter的部署与配置,通过三个实践案例展示其在Kubernetes环境中的应用,帮助用户实现基于自定义指标的自动扩展和跨集群统一监控. 关注作者,分享AI全维度知 ...

  4. JDBC的简单使用以及介绍

    JDBC(Java DataBase Connectivity) Java 语言连接数据库 再本模块中,java提供里一组用于连接数据库的类和接口 Java 语言开发者,本身没有提供如何具体连接数据库 ...

  5. UML建模工具Astah Pro 8破解教程2022最新最详细版

    (2022最新最详细版)UML建模工具Astah Pro 8破解教程 本文作者严正声明:拒绝盗版行为,打击盗版,痛恨吃白食的家伙,我一直都是坚定思想,有钱了一定要支持正版,所以此文档贡献,只为学习交流 ...

  6. Easysearch压缩模式深度比较:ZSTD+source_reuse的优势分析

    引言 在使用 Easysearch 时,如何在存储和查询性能之间找到平衡是一个常见的挑战.Easysearch 具备多种压缩模式,各有千秋.本文将重点探讨一种特别的压缩模式:zstd + source ...

  7. ASP.NET MVC 查询加分页

    使用了LinqKit.PagedList.Mvc.EntityFramework 等DLL 直接使用nuget安装即可. 1.表模型: using System.ComponentModel.Data ...

  8. redis简单应用demo - 订单号自增长的思路:业务编码+地区+自增数值

    redis简单应用demo1.字符串127.0.0.1:6379> set hello toneyOK127.0.0.1:6379> type hellostring127.0.0.1:6 ...

  9. Kubernetes(三)实战入门

    实战入门 本章介绍如何在kubernetes集群中部署一个nginx服务,并能够对其进行访问. 1. Namespace Namespace主要作用是实现多套环境的资源隔离或者多租户的资源隔离. 默认 ...

  10. Kubernetes(K8S)基本概念

    前言 有公司用 java 或 go , vue 或 react , linux 或 win ,但所有的大厂都在用k8s,没有或,而且是全世界.一个熟悉k8s的开发,薪资可以轻松上25的 base . ...