03 MyBatisPlus之条件构造器Wrapper+三个核心注解

2. 条件构造器

2.1 条件构造器作用

//创建一个查询条件构造器对象,所有条件都放进去
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", "John"); // eq添加等于条件
queryWrapper.ne("age", 30); // ne添加不等于条件
queryWrapper.like("email", "@gmail.com"); // like添加模糊匹配条件
等同于: 
delete from user where name = "John" and age != 30
                                  and email like "%@gmail.com%"
// 根据 entity 条件,删除记录
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);

使用MyBatis-Plus的条件构造器,你可以构建灵活、高效的查询条件,而不需要手动编写复杂的 SQL 语句。它提供了许多方法来支持各种条件操作符,并且可以通过链式调用来组合多个条件。这样可以简化查询的编写过程,并提高开发效率。

2.2 条件构造器继承结构

条件构造器类结构:
在这里插入图片描述
Wrapper : 条件构造抽象类,最顶端父类

  • AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
    • QueryWrapper : 查询/删除条件封装
    • UpdateWrapper : 修改条件封装
    • AbstractLambdaWrapper : 使用Lambda 语法
      • LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper
      • LambdaUpdateWrapper : Lambda 更新封装Wrapper

2.3 基于QueryWrapper条件构造器

在这里插入图片描述

@Test
public void test01(){
    //查询用户名包含a,年龄在20到30之间,并且邮箱不为null的用户信息
    //SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (username LIKE '%a%' AND age >=20 AND age <=30 AND email IS NOT NULL)
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    
     /**普通调用,代码稍显冗长
     queryWrapper.like("username", "a")
     queryWrapper.between("age", 20, 30)
     queryWrapper.isNotNull("email");
     **/
    //链式调用
    queryWrapper.like("username", "a")
            .between("age", 20, 30)
            .isNotNull("email");
    //返回的对象可能是多个,故用selectList
    List<User> list = userMapper.selectList(queryWrapper);
    list.forEach(System.out:: println);

封装排序条件(orderByDesc/orderByAsc):

@Test
public void test02(){
    //按年龄降序查询用户,如果年龄相同则按id升序排列
    //SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 ORDER BY age DESC,id ASC
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper
            .orderByDesc("age")
            .orderByAsc("id");
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}

封装删除条件:(仍未QueryWrapper)

@Test
public void test03(){
    //删除email为空的用户
    //DELETE FROM t_user WHERE (email IS NULL)
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.isNull("email");
    //条件构造器也可以构建删除语句的条件
    int result = userMapper.delete(queryWrapper);
    System.out.println("受影响的行数:" + result);
}

and和or关键字使用(修改):

@Test
public void test04() {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    //将年龄大于20并且用户名中包含有a或邮箱为null的用户信息修改
    //UPDATE t_user SET age=18, [email protected] WHERE username LIKE '%a%' OR email IS NULL AND age > 20)
    queryWrapper
            //(like or isnull) and gt
            .like("username", "a")
            .or()
            .isNull("email");
            .gt("age", 20)
    User user = new User();
    user.setAge(18);
    user.setEmail("[email protected]");
    int result = userMapper.update(user, queryWrapper);
    System.out.println("受影响的行数:" + result);
}

指定列映射查询(select):

@Test
public void test05() {
    //查询用户信息的username和age字段
    //SELECT username,age FROM t_user
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.select("username", "age");
    //selectMaps()返回Map集合列表,通常配合select()使用,避免User对象中没有被查询到的列值为null
    List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
    maps.forEach(System.out::println);
}

condition实现条件判断,可以叠加上述所有语句使用

 @Test
public void testQuick3(){
    
    String name = "root";
    int    age = 18;

    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    //当name不为null, age > 1 时,才允许作为条件添加进构造器,一个作为相等判断,一个作为大于判断
    //例如仅当前端传来的name不为空时,才判断是否相等
    //方案1: 手动判断
    if (!StringUtils.isEmpty(name)){
        queryWrapper.eq("name",name);
    }
    if (age > 1){
        queryWrapper.gt("age",age);
    }
    
    //方案2: 拼接condition判断.格式为eq(condition,列名,值)
    //每个条件拼接方法都condition参数,这是一个比较运算,为true追加当前条件!
   //仅当name非空时,才加入name = root条件进构造器中
    queryWrapper.eq(!StringUtils.isEmpty(name),"name",name)
            .gt(age>1,"age",age);   
}

2.4 基于 UpdateWrapper条件构造器

使用queryWrapper进行更新的两个问题
a. 要准备一个实体类,将要改成的数据放入实体类
b. 原本为null的数据改不了(update方法只能改非空的数据)

@Test
public void test04() {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    //将年龄大于20并且用户名中包含有a或邮箱为null的用户信息修改
    //UPDATE t_user SET age=18, [email protected] WHERE username LIKE '%a%' AND age > 20 OR email IS NULL)
    queryWrapper
            .like("username", "a")
            .gt("age", 20)
            .or()
            .isNull("email");
    //要修改成下列数据
    User user = new User();
    user.setAge(18);
    user.setEmail("[email protected]");
    int result = userMapper.update(user, queryWrapper);
    System.out.println("受影响的行数:" + result);
}

使用updateWrapper:
a. 直接携带修改数据,无需声明实体对象
b. 指定任意修改值

@Test
public void testQuick2(){

    UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
    //将id = 3 的email设置为null, age = 18
    updateWrapper.eq("id",3)
            .set("email",null)  // set 指定列和结果
            .set("age",18);
    //如果使用updateWrapper 实体对象写null即可!
    int result = userMapper.update(null, updateWrapper);
    System.out.println("result = " + result);

}

LambdaQueryWrapper属于QueryWrapper
LambdaUpdateWrapper属于UpdateWrapper
用法一样 , 功能增强 , 类似MB与MP


2.5 基于LambdaQueryWrapper组装条件

LambdaQueryWrapper对比QueryWrapper优势

QueryWrapper 示例代码:

QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", "John")
  .ge("age", 18)
  .orderByDesc("create_time")
  .last("limit 10");
List<User> userList = userMapper.selectList(queryWrapper);

LambdaQueryWrapper 示例代码:

LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();

lambdaQueryWrapper.eq(User::getName, "John")
  .ge(User::getAge, 18)
  .orderByDesc(User::getCreateTime)
  .last("limit 10");
List<User> userList = userMapper.selectList(lambdaQueryWrapper);
从上面的代码对比可以看出,相比于 QueryWrapper,LambdaQueryWrapper 使用了实体类的属性引用(例如 `User::getName`、`User::getAge`),
而不是字符串来表示字段名,这提高了代码的可读性和可维护性。

2.6 方法引用回顾:

方法引用是 Java 8 中引入的一种语法特性,它提供了一种简洁的方式来直接引用已有的方法或构造函数。方法引用可以替代 Lambda 表达式,使代码更简洁、更易读。

Java 8 支持以下几种方法引用的形式:

1. 静态方法引用: 引用静态方法,语法为 `类名::静态方法名`。
2. 实例方法引用: 引用实例方法,语法为 `实例对象::实例方法名`。
3. 对象方法引用: 引用特定对象的实例方法,语法为 `类名::实例方法名`。
4. 构造函数引用: 引用构造函数,语法为 `类名::new`。

演示代码:

import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;

public class MethodReferenceExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("John", "Tom", "Alice");
        // 使用 Lambda 表达式
        names.forEach(name -> System.out.println(name));
        // 使用方法引用
        names.forEach(System.out::println);
    }
}

lambdaQueryWrapper使用案例:

@Test
public void testQuick4(){

    String name = "root";
    int    age = 18;

    //使用QueryWrapper
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    //每个条件拼接方法都condition参数,这是一个比较运算,为true追加当前条件!
    //eq(condition,列名,值)
    queryWrapper.eq(!StringUtils.isEmpty(name),"name",name)
            .eq(age>1,"age",age);

    // 使用lambdaQueryWrapper
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //注意: 需要使用方法引用
    //技巧: 类名::方法名
    lambdaQueryWrapper.eq(!StringUtils.isEmpty(name), User::getName,name);
    List<User> users= userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

LambdaUpdateWrapper使用案例

@Test
public void testQuick2(){

    //使用UpdateWrapper
    UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
    //将id = 3 的email设置为null, age = 18
    updateWrapper.eq("id",3)
            .set("email",null)  // set 指定列和结果
            .set("age",18);

    //使用lambdaUpdateWrapper
    LambdaUpdateWrapper<User> updateWrapper1 = new LambdaUpdateWrapper<>();
    updateWrapper1.eq(User::getId,3)
            .set(User::getEmail,null)
            .set(User::getAge,18);
    
    //如果使用updateWrapper 实体对象写null即可!
    int result = userMapper.update(null, updateWrapper);
    System.out.println("result = " + result);
}

3. 核心注解使用

3.1 @TableName注解

  - 描述:表名注解,将实体类和表绑定(用于实体类名和xxxMapper.java的xxx不匹配时)
  - 一个最简单的例子是,数据库表一般都是t_xxx,但实体类不会有t,这时二者就不匹配了
  - 使用位置:实体类
public interface UserMapper extends BaseMapper<User> {

}

此接口对应的方法为什么会自动触发 user表的crud呢?

默认情况下, 根据指定的<实体类>的名称对应数据库表名,属性名对应数据库的列名!

但是不是所有数据库的信息和实体类都完全映射!

例如: 表名 t_user → 实体类 User 这时候就不对应了!

@TableName("sys_user") //对应数据库表名
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

特殊情况:如果表名和实体类名相同(忽略大小写)可以省略该注解!

其他解决方案:全局设置前缀

mybatis-plus: # mybatis-plus的配置
  global-config:
    db-config:
    #所有的实体类xxx都与sys_xxx表名自动匹配
      table-prefix: sys_ # 表名前缀字符串

3.1 @TableId 注解

  • 描述:主键注解
  • 使用位置:实体类主键字段之上
  • 使用情况:
    a.主键的字段名和属性名不一致,用value属性绑定二者
    b.插入数据时,用type属性指定生成主键的策略

在这里插入图片描述在这里插入图片描述

实体类:

@TableName("sys_user")
public class User {
    //主键自增长,使用的前提是数据库中的主键字段必须提前设置了自增长
    @TableId(value="主键列名",type = IdType_AUTO)
    private Long id;
    private String name;
    private Integer age;
}

测试方法:

public void testTableId(){.
	User user = new User();
	user.setName("zhangsan");
	user.setAge(20);
	userMapper.insert(user);
}

会成功插入一条数据,其中id字段为默认的雪花算法生成的。

当然,也可以全局配置修改主键策略:

mybatis-plus:
  configuration:
    # 配置MyBatis日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      # 配置MyBatis-Plus操作表的默认前缀
      table-prefix: t_
      # 配置MyBatis-Plus的主键策略
      id-type: auto

雪花算法:

  **你需要记住的: 雪花算法生成的数字,需要使用Long 或者 String类型主键!!**

  雪花算法(Snowflake Algorithm)是一种用于生成唯一ID的算法 , 用于解决分布式系统中生成全局唯一ID的需求。

  在传统的自增ID生成方式中,使用单点数据库生成ID会成为系统的瓶颈,而雪花算法通过在分布式系统中生成唯一ID,避免了单点故障和性能瓶颈的问题。

  雪花算法生成的ID是一个64位的整数,由以下几个部分组成:

  1. 时间戳:41位,精确到毫秒级,可以使用69年。
  2. 节点ID:10位,用于标识分布式系统中的不同节点。
  3. 序列号:12位,表示在同一毫秒内生成的不同ID的序号。

  通过将这三个部分组合在一起,雪花算法可以在分布式系统中生成全局唯一的ID,并保证ID的生成顺序性。

  雪花算法的工作方式如下:

  1. 当前时间戳从某一固定的起始时间开始计算,可以用于计算ID的时间部分。
  2. 节点ID是分布式系统中每个节点的唯一标识,可以通过配置或自动分配的方式获得。
  3. 序列号用于记录在同一毫秒内生成的不同ID的序号,从0开始自增,最多支持4096个ID生成。

  需要注意的是,雪花算法依赖于系统的时钟,需要确保系统时钟的准确性和单调性,否则可能会导致生成的ID不唯一或不符合预期的顺序。

  雪花算法是一种简单但有效的生成唯一ID的算法,广泛应用于分布式系统中,如微服务架构、分布式数据库、分布式锁等场景,以满足全局唯一标识的需求。
  1. @TableField
  • 描述:字段注解(非主键)

  • 使用场景 :
    a. 区分该字段是否在数据库中存储.有一些字段仅在程序运行过程中存在 , 不需要持久化保存 ,于是提前声明这个字段不存在表里 , 不要去表里找.
    b. 绑定实体属性和表的字段

    在这里插入图片描述

@TableName("sys_user")
public class User {
    @TableId
    private Long id;
    @TableField("nickname")
    private String name;
    private Integer age;
    @TableField(exist = false)
    private String email;
}

MyBatis-Plus会自动开启驼峰命名风格映射!!!