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 ...
随机推荐
- MindSponge分子动力学模拟——多路径分子模拟(2024.05)
技术背景 在前面的MindSponge教程系列博客中,我们已经介绍过MindSponge分子动力学模拟框架的基础功能使用方法,例如MindSponge的安装与使用.定义分子系统.计算单点能和迭代器等等 ...
- SSH-Web 工具之 shellinabox:一款使用 AJAX 的基于 Web 的终端模拟器 安装及使用教程
本文转载自: shellinabox:一款使用 AJAX 的基于 Web 的终端模拟器 一.shellinabox简介 通常情况下,我们在访问任何远程服务器时,会使用常见的通信工具如OpenSSH和P ...
- vmware vmnat1和vmnat8在真机网络适配器中消失
在真机的网络适配器中,发现只有两张网卡.缺少vmnat1和vmnat8 一,查看虚拟网络编辑器是否连接 二,如果没有连接,勾选连接就好了. 三,如果连接了,真机网络适配器仍然只有两张网络适配器. 1. ...
- Linux设备驱动--阻塞与非阻塞I/O
注:本文是<Linux设备驱动开发详解:基于最新的Linux 4.0内核 by 宋宝华 >一书学习的笔记,大部分内容为书籍中的内容. 书籍可直接在微信读书中查看:Linux设备驱动开发详解 ...
- 小程序转发 搜索wxml
新闻转发 在小程序中要不通过菜单项来完成分享功能,只能通过表单组件中的按钮来完成. 它是通过按钮中的开放能力完成 按钮自定义处理 新闻搜索 搜索wxml 搜索业务的js
- Vulkan Support Check and Dynamic Loader C++ code sample
很多时候不想静态依赖VulkanSDK所提供的静态库,因为会遇到一些过早的电脑不支持vulkan, 那么就需要使用动态加载vulkan-1.dll(for Windows)或libMoltenVK.d ...
- java把时间戳转换成时间_(转)java时间与时间戳互转
java中时间精确到毫秒级,所以需求时间需要 除以1000 //将时间转换为时间戳 public static String dateToStamp(String s) throws Exceptio ...
- 贝壳找房: 为 AI 平台打造混合多云的存储加速底座
贝壳机器学习平台的计算资源,尤其是 GPU,主要依赖公有云服务,并分布在不同的地理区域.为了让存储可以灵活地跟随计算资源,存储系统需具备高度的灵活性,支持跨区域的数据访问和迁移,同时确保计算任务的连续 ...
- rust 程序设计笔记(2)所有权 & 引用
所有权 数据存储在栈和堆上,存放在栈上的数据都是已知所占据空间的 突然的问题 // 内存中的栈是怎么存储数据的? 好的,想象一下你有一摞盘子.你只能从上面放盘子,也只能从上面拿盘子,这就是栈的工作方式 ...
- http请求方式-HttpURLConnection
http请求方式-HttpURLConnection import com.alibaba.fastjson.JSON; import com.example.core.mydemo.http.Ord ...