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

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

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. MindSponge分子动力学模拟——多路径分子模拟(2024.05)

    技术背景 在前面的MindSponge教程系列博客中,我们已经介绍过MindSponge分子动力学模拟框架的基础功能使用方法,例如MindSponge的安装与使用.定义分子系统.计算单点能和迭代器等等 ...

  2. SSH-Web 工具之 shellinabox:一款使用 AJAX 的基于 Web 的终端模拟器 安装及使用教程

    本文转载自: shellinabox:一款使用 AJAX 的基于 Web 的终端模拟器 一.shellinabox简介 通常情况下,我们在访问任何远程服务器时,会使用常见的通信工具如OpenSSH和P ...

  3. vmware vmnat1和vmnat8在真机网络适配器中消失

    在真机的网络适配器中,发现只有两张网卡.缺少vmnat1和vmnat8 一,查看虚拟网络编辑器是否连接 二,如果没有连接,勾选连接就好了. 三,如果连接了,真机网络适配器仍然只有两张网络适配器. 1. ...

  4. Linux设备驱动--阻塞与非阻塞I/O

    注:本文是<Linux设备驱动开发详解:基于最新的Linux 4.0内核 by 宋宝华 >一书学习的笔记,大部分内容为书籍中的内容. 书籍可直接在微信读书中查看:Linux设备驱动开发详解 ...

  5. 小程序转发 搜索wxml

    新闻转发 在小程序中要不通过菜单项来完成分享功能,只能通过表单组件中的按钮来完成. 它是通过按钮中的开放能力完成 按钮自定义处理 新闻搜索 搜索wxml 搜索业务的js

  6. Vulkan Support Check and Dynamic Loader C++ code sample

    很多时候不想静态依赖VulkanSDK所提供的静态库,因为会遇到一些过早的电脑不支持vulkan, 那么就需要使用动态加载vulkan-1.dll(for Windows)或libMoltenVK.d ...

  7. java把时间戳转换成时间_(转)java时间与时间戳互转

    java中时间精确到毫秒级,所以需求时间需要 除以1000 //将时间转换为时间戳 public static String dateToStamp(String s) throws Exceptio ...

  8. 贝壳找房: 为 AI 平台打造混合多云的存储加速底座

    贝壳机器学习平台的计算资源,尤其是 GPU,主要依赖公有云服务,并分布在不同的地理区域.为了让存储可以灵活地跟随计算资源,存储系统需具备高度的灵活性,支持跨区域的数据访问和迁移,同时确保计算任务的连续 ...

  9. rust 程序设计笔记(2)所有权 & 引用

    所有权 数据存储在栈和堆上,存放在栈上的数据都是已知所占据空间的 突然的问题 // 内存中的栈是怎么存储数据的? 好的,想象一下你有一摞盘子.你只能从上面放盘子,也只能从上面拿盘子,这就是栈的工作方式 ...

  10. http请求方式-HttpURLConnection

    http请求方式-HttpURLConnection import com.alibaba.fastjson.JSON; import com.example.core.mydemo.http.Ord ...