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"));
    });
}
View Code

 

高极查询构建器

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

  

 

posted @ 2024-04-20 10:09  谢双元小号  阅读(1)  评论(0编辑  收藏  举报