java srpint boot 2.2.1 第二部份,乐观锁机制, 构造复杂查询条件,分页查询 相关内容,删除与软删除
第二部份,引起锁机制的原理和解决方案:
测试环境搭建
第一步先建一个数据库表用于模拟商品购买。
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 中常用的构建器及其条件方法:
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): 不为空条件- ... 以及其他多个方法。
- 这两个构建器都继承自
LambdaQueryWrapper 和 LambdaUpdateWrapper:
- 这两个构建器与
QueryWrapper和UpdateWrapper类似,但它们支持 Lambda 表达式,使代码更加简洁且类型安全。 - 条件方法与上述类似,但可以通过 Lambda 表达式直接引用实体类的属性,而不是使用字符串列名。
- 这两个构建器与
QueryChainWrapper:
- 这是一个链式查询构建器,允许你通过链式方法调用来构建查询条件。
- 条件方法与
QueryWrapper类似,但可以通过链式调用来组合多个条件。
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 第二部份,乐观锁机制, 构造复杂查询条件,分页查询 相关内容,删除与软删除的更多相关文章
- 【java】spring-data-jpa 集成hibernate实现多条件分页查询
初次接触spring-data-jpa,实现多条件分页查询. 基础环境 Spring Boot+spring-data-jpa+hibernate+mysql 1.接口 要继承这个接口,这个接口提供了 ...
- Java并发编程(05):悲观锁和乐观锁机制
本文源码:GitHub·点这里 || GitEE·点这里 一.资源和加锁 1.场景描述 多线程并发访问同一个资源问题,假如线程A获取变量之后修改变量值,线程C在此时也获取变量值并且修改,两个线程同时并 ...
- posix第二篇-----linux 锁机制
1 简介 锁机制(lock) 是多线程编程中最常用的同步机制,用来对多线程间共享的临界区(Critical Section) 进行保护. Pthreads提供了多种锁机制,常见的有: 1) Mutex ...
- Java 多线程与并发【原理第二部分笔记】
Java 多线程与并发[原理第二部分笔记] 什么是Java内存模型中的happens-before Java内存模型,即JMM,本身是一种抽象的概念,并不是真实存在的,他描述的是一组规则或者说是一种规 ...
- 五分钟学会悲观乐观锁-java vs mysql vs redis三种实现
1 悲观锁乐观锁简介 乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果 ...
- Java并发 行级锁/字段锁/表级锁 乐观锁/悲观锁 共享锁/排他锁 死锁
原文地址:https://my.oschina.net/oosc/blog/1620279 前言 锁是防止在两个事务操作同一个数据源(表或行)时交互破坏数据的一种机制. 数据库采用封锁技术保证并发操作 ...
- Java Hibernate中的悲观锁和乐观锁的实现
锁(locking) 业务逻辑的实现过程中,往往需要保证数据访问的排他性.如在金融系统的日终结算 处理中,我们希望针对某个cut-off时间点的数据进行处理,而不希望在结算进行过程中 (可能是几秒种, ...
- 对Java提供的锁机制的一些思考
Java的数据会在CPU.Register.Cache.Heap和Thread stack之间进行复制操作,而前面四个都是在Java Threads之间共享,因此Java的锁机制主要用于解决Racin ...
- Java Spring Boot VS .NetCore (五)MyBatis vs EFCore
Java Spring Boot VS .NetCore (一)来一个简单的 Hello World Java Spring Boot VS .NetCore (二)实现一个过滤器Filter Jav ...
- Java Spring Boot VS .NetCore (七) 配置文件
Java Spring Boot VS .NetCore (一)来一个简单的 Hello World Java Spring Boot VS .NetCore (二)实现一个过滤器Filter Jav ...
随机推荐
- WPF开发快速入门【6】下拉框与枚举类型
概述 本文讲述下拉框和枚举类型进行绑定的一些操作. 下拉框的基本操作 设计部分: <ComboBox ItemsSource="{Binding Fruits}" Selec ...
- nginx map模块使用和配置
主机 IP 备注 master1 10.0.0.63 master2 10.0.0.64 1. NGINX MAP模块与NGINX GEO模块 在通常情况下,使用nginx基于 ip 限制访问请求频率 ...
- 记一次 .NET某工业设计软件 崩溃分析
一:背景 1. 讲故事 前些天有位朋友找到我,说他的软件在客户那边不知道什么原因崩掉了,从windows事件日志看崩溃在 clr 里,让我能否帮忙定位下,dump 也抓到了,既然dump有了,接下来就 ...
- 开源一站式敏捷测试管理,极简项目管理平台 itest(爱测试) 6.6.2 发布,便捷迫切功能增强
(一)itest 简介及更新说明 itest 开源敏捷测试管理,testOps 践行者,极简的任务管理,测试管理,缺陷管理,测试环境管理,接口测试5合1,又有丰富的统计分析.可按测试包分配测试用例执行 ...
- ajax兼容处理
// ajax的兼容处理 // 1.创建ajax对象 // 标准浏览器 // const xhr = new XMLHttpRequest(); ...
- pandas、spark计算相关性系数速度对比
pandas.spark计算相关性系数速度对比 相关性计算有三种算法:pearson.spearman,kenall. 在pandas库中,对一个Dataframe,可以直接计算这三个算法的相关系数c ...
- 项目管理--PMBOK 读书笔记(3)【项目经理的角色 】
思维导图软件工具:https://www.xmind.cn/ 源文件地址:https://files-cdn.cnblogs.com/files/zj19940610/项目经理的角色.zip
- __proto__和[[Prototype]]的区别
__proto__和[[Prototype]]的区别 先看下面这一段代码: const obj1 = Object.create(null); // very plain object obj1.__ ...
- n. Elasticsearch JAVA API操作
引言 Elasticsearch所支持的客户端连接方式有两种 Transport 连接 底层使用socket连接,用官方提供的TransPort客户端,网络IO框架使用的是netty Http连接(R ...
- [一句话说iOS]dispatch如何造成死锁
dispatch_sync执行了两件事:把代码块放入指定线程的任务队列中.堵塞当前线程直到代码块执行结束,如果出现了堵塞的线程和代码块所在的线程为同一线程的话,这个时候代码无法在此线程执行继续下去,即 ...