SSM框架,MyBatis-Plus的学习(下)

条件构造器

使用MyBatis-Plus的条件构造器,可以构建灵活高效的查询条件,可以通过链式调用来组合多个条件。

条件构造器的继承结构

Wrapper : 条件构造抽象类,最顶端父类

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

QueryWapper的使用

使用案例:

java 复制代码
@org.junit.jupiter.api.Test
    public void test_select(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//        //模糊查询
//        queryWrapper.like("name","a");//名字中包含a字符
//        //范围查询
//        queryWrapper.between("age",20,30);
//        //判断不为空
//        queryWrapper.isNotNull("email");
        //结果的sql语句为:
//SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (username LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)

        //可以链式调用
queryWrapper.like("username", "a")
                .between("age", 20, 30)
                .isNotNull("email");
        userMapper.selectList(queryWrapper);
    }

使用步骤

  1. 创建一个QueryWrapper类型的对象,并指定其泛型(泛型为操作的数据对应的实体类类型)
  2. 用QueryWrapper类型的对象调用相应的添加条件的方法,在方法的参数列表中指定条件
  3. 最后将此QueryWrapper类型的对象加到MyBatis-Plus的crud方法的参数列表中,相应的crud方法便会对满足指定条件的数据操作

QueryWrapper的方法

  • 关于升降序:

升序降序的优先级由方法中参数的前后或者调用方法的先后决定

  • 关于and和or:

调用的多个方法之间默认使用AND连接,在调用两个方法时在中间调用一个or方法即可

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

查询时,默认是查询所有的列,要指定查询的列,调用QueryWrapper的select方法,参数传入要查询的列的列名即可

java 复制代码
@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判断组织条件:
java 复制代码
 @Test
public void testQuick3(){
    
    String name = "root";
    int    age = 18;

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

每个QueryWrapper的条件拼接方法中都可以在参数列表中加上一个布尔类型的参数,这个参数可以加入一个表达式,当这个参数的结果为true时该方法才执行。可以用来动态判断是否要拼接此条件。

使用queryWrapper + 实体类形式可以实现修改,但是无法将列值修改为null值

UpdateWrapper的使用

UpdateWrapper的方法与QueryWrapper的方法类似,但是UpdateWrapper有一个set方法,可以指定数据的相应列做修改,并且可以做到将数据库中的数据修改为null

复制代码
@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和LambdaUpdateWrapper的使用

相比于 QueryWrapper,LambdaQueryWrapper 使用了实体类的属性引用(例如 User::getName、User::getAge),而不是字符串来表示字段名,这提高了代码的可读性和可维护性

java 复制代码
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);

LambdaUpdateWrapper也是类似:

java 复制代码
@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);

    //使用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);
}

MyBatis-Plus核心注解

@TableName注解

表名的注解,用来指定实体类对应的数据库中的表

默认以实体类的名字来对应表,忽略大小写,在实体类名和数据表的名字相同时(忽略大小写),可以不写此注解

当数据库的表名和实体类的命名不同时,在实体类上加上此注解,并在其value属性中指定其实体类对应的表名

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

也可以全局设置前缀,如此在对应实体类对应的数据表时,会先加上前缀,再寻找数据表

在application.yaml中:

java 复制代码
mybatis-plus: # mybatis-plus的配置
  global-config:
    db-config:
      table-prefix: sys_ # 表名前缀字符串

@TableId

加在主键上的注解,当表中的主键的列名与实体类中的表示主键的属性名不一致,并不能完成驼峰映射时,可以用其value属性用来指定实体类的主键属性名对应的表中的主键列名

其type属性用来指定主键策略,即增加数据时,如何让数据库给增加的数据添加主键值。

属性 类型 必须指定 默认值 描述
value String "" 主键字段名
type Enum IdType.NONE 指定主键类型

type属性可选值:

描述
AUTO 数据库 ID 自增 (mysql配置主键自增长)
ASSIGN_ID(默认) 分配 ID(主键类型为 Number(Long )或 String)(since 3.3.0),使用接口IdentifierGenerator的方法nextId(默认实现类为DefaultIdentifierGenerator雪花算法)
  • 使用AUTO,即使用主键自增长,表的主键要有设置自增长
  • 雪花算法可以随机生成一个不重复的long类型的数字
    • 使用雪花算法,数据库主键要是BIGINT类型的或者是VARCHAR(64)类型的
    • 实体类的主键的属性要是Long类型的

也可以全局设置主键策略:

复制代码
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

@TableField注解

加在非主键属性上,当表中的列名与实体类中的属性不一致,并不能完成驼峰映射时,可以用其value属性用来指定实体类的属性名对应的表中的列名

除此之外,还有一个exit属性,用来表明此属性是否与数据表中的列名对应

java 复制代码
@TableName("sys_user")
public class User {
    @TableId
    private Long id;
    @TableField("nickname")
    private String name;
    private Integer age;
    private String email;
}
属性 类型 必须指定 默认值 描述
value String "" 数据表字段名
exist boolean true 是否为数据库表字段

MyBatis-Plus拓展

逻辑删除实现

逻辑删除,即数据表中有一个列专门用来表示是否被删除,一个行被删除后此列的值便会改为用来表示已删除状态的值,通常,1表示逻辑已删除,0表示逻辑未删除

前提:在创建数据表时,加一个表示逻辑删除的字段,默认值约束设置为0

实体类添加逻辑删除属性:

java 复制代码
@Data
public class User {
   // @TableId
    private Integer id;
    private String name;
    private Integer age;
    private String email;
    
    @TableLogic
    //逻辑删除字段 int mybatis-plus下,默认 逻辑删除值为1 未逻辑删除 0 
    private Integer deleted;
}

还可以全局添加实体类的逻辑删除的属性,如此不用在实体类中声明逻辑删除的属性上加上@TableLogic注解

复制代码
mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

设置完逻辑删除后,mybatis-plus中所有的delete语句会更改为update语句,更改其逻辑删除字段的字段值为1

乐观锁实现

乐观锁能解决数据并发问题

MyBatis-Plus使用版本号方式实现乐观锁

前提:创建数据表时,加上一个表示版本号的字段,默认值为1

首先,添加版本号更新插件

java 复制代码
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
    return interceptor;
}

在实体类中加上一个表示版本号的属性,并在这个属性上加上@Version属性

java 复制代码
@Version
private Integer version;

防全表更新和删除操作实现

针对 update 和 delete 语句 作用: 阻止恶意的全表更新删除

添加防止全表更新和删除拦截器

java 复制代码
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
  MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
  interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
  return interceptor;
}
}

测试全部更新或者删除

java 复制代码
@Test
public void testQuick8(){
    User user = new User();
    user.setName("custom_name");
    user.setEmail("xxx@mail.com");
    //Caused by: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of table update operation
    //全局更新,报错
    userService.saveOrUpdate(user,null);
}
相关推荐
一只叫煤球的猫40 分钟前
写代码很6,面试秒变菜鸟?不卖课,面试官视角走心探讨
前端·后端·面试
bobz9651 小时前
tcp/ip 中的多路复用
后端
bobz9651 小时前
tls ingress 简单记录
后端
皮皮林5512 小时前
IDEA 源码阅读利器,你居然还不会?
java·intellij idea
你的人类朋友2 小时前
什么是OpenSSL
后端·安全·程序员
bobz9653 小时前
mcp 直接操作浏览器
后端
前端小张同学5 小时前
服务器部署 gitlab 占用空间太大怎么办,优化思路。
后端
databook5 小时前
Manim实现闪光轨迹特效
后端·python·动效
武子康6 小时前
大数据-98 Spark 从 DStream 到 Structured Streaming:Spark 实时计算的演进
大数据·后端·spark
该用户已不存在6 小时前
6个值得收藏的.NET ORM 框架
前端·后端·.net