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; }