MyBatisPlus


MyBatisPlus

学习目标

  • 基于MyBatisPlus完成标准Dao开发

一、MyBatisPlus简介

1.1 入门案例

(1)创建数据库及表

create database if not exists mybatisplus_db character set utf8;
    use mybatisplus_db;
    CREATE TABLE user (
    id bigint(20) primary key auto_increment,
    name varchar(32) not null,
    password varchar(32) not null,
    age int(3) not null ,
    tel varchar(32) not null
    );
    insert into user values(1,'Tom','tom',3,'18866668888');
    insert into user values(2,'Jerry','jerry',4,'16688886666');
    insert into user values(3,'Jock','123456',41,'18812345678');
    insert into user values(4,'li','itcast',15,'4006184000');

(2)创建SpringBoot工程 - 配置使用技术

(3)pom.xml(添加druid数据源)

  • druid数据源可以加也可以不加,SpringBoot有内置的数据源,可以配置成使用Druid数据源。
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.16</version>
</dependency>

(4)application.yml(配置数据库连接)

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3307/mybatisplus_db?serverTimezone=UTC
    username: root
    password: 1234

(5)创建实体类

package com.li.domain;

public class User {

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getTel() {
        return tel;
    }

    public void setTel(String tel) {
        this.tel = tel;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                ", age=" + age +
                ", tel='" + tel + '\'' +
                '}';
    }

    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;
}

(6)创建Dao接口

  • 继承BaseMapper<User>
@Mapper
//继承“extends BaseMapper<User>”
public interface UserDao extends BaseMapper<User> {
}

(7)编写测试类

@SpringBootTest
class MpDemo1ApplicationTests {

    @Autowired
    private UserDao userDao;

    @Test
    public void testGetAll(){
        List<User> userList = userDao.selectList(null);
        System.out.println(userList);
    }
}

(8)运行测试类

1.2 MyBatisPlus特性

  • 无侵入:只做增强不做改变,不会对现有工程产生影响
  • 强大的 CRUD 操作:内置通用 Mapper,少量配置即可实现单表CRUD操作
  • 支持 Lambda:编写查询条件无需担心字段写错
  • 支持主键自动生成
  • 内置分页插件

二、标准数据层开发

2.1 标准数据层CRUD功能

2.1.1 标准的CRUD功能(mybatisplus接口)

2.1.2 实例

(1)新增

@Test
// 新增
void testSave(){
    User user = new User();
    user.setName("wangwu");
    user.setPassword("99999999");
    user.setAge(820);
    user.setTel("189");
    userDao.insert(user);
}

(2)删除

//    1567064852546011137
//刚刚新增的ID

    @Test
    //删除
    void tsetDelete(){
        //提示数值过大,则在数后加“L”
        userDao.deleteById(1567064852546011137L);
    }

(3)修改

  • 修改的时候,只修改实体对象中有值的字段。
@Test
//修改
void testUpadte(){
    User user = new User();
    user.setId(1L);
    user.setName("gogogo");
    user.setPassword("9993434343999");
    user.setAge(991);
    userDao.updateById(user);
}

(4)根据ID查询

@Test
//根据ID查询
void testGetById(){
    User user = userDao.selectById(2L);
    System.out.println(user);
}

(5)查询所有

  • 我们所调用的方法都是来自于DAO接口继承的BaseMapper类中。
@Test
//查询所有
public void testGetAll(){
    List<User> userList = userDao.selectList(null);
    System.out.println(userList);
}

2.1.3 Lombok

  • 简写模型类的编写。

    • 私有属性
    • setter...getter...方法
    • toString方法
    • 构造函数
  • 利用Lombok就可以不用编写了。
  • Lombok,一个Java类库,提供了一组注解,简化POJO实体类开发。

(1)添加lombok依赖

  • 版本可以不用写,因为SpringBoot中已经管理了lombok的版本。
  • 如果删除setter和getter方法程序有报红,则需要安装Lombok插件。
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <!--<version>1.18.12</version>-->
</dependency>

(2)模型类上添加注解

  • Lombok常见的注解有:(加粗的是比较常用的)

    • @Setter:为模型类的属性提供setter方法
    • @Getter:为模型类的属性提供getter方法
    • @ToString:为模型类的属性提供toString方法
    • @EqualsAndHashCode:为模型类的属性提供equals和hashcode方法
    • @Data:是个组合注解,包含上面的注解的功能
    • @NoArgsConstructor:提供一个无参构造函数
    • @AllArgsConstructor:提供一个包含所有参数的构造函数

(3)实例

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;

    //@Data是个组合注解,包含上面的注解的功能
    //@NoArgsConstructor:提供一个无参构造函数
    //@AllArgsConstructor:提供一个包含所有参数的构造函数
}

(4)如果我只想要某个字段的构造函数-实例

  • 例如name和 password的构造函数。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;

    public User(String name, String password) {
        this.name = name;
        this.password = password;
    }

}

2.2 分页功能

(1)分页查询使用的方法

(2)调用方法传入参数获取返回值

 @Test
    //分页查询
    void testSelectPage(){
        //创建IPage分页对象,设置分页参数,1为当前页码,2为每页显示的记录数
        IPage<User> page = new Page<>(1,2);
        //2 执行分页查询
        userDao.selectPage(page,null);
        //获取分页结果
        System.out.println("当前页码值" + page.getCurrent());
        System.out.println("每页显示多少数" + page.getSize());
        System.out.println("一共多少页" + page.getPages());
        System.out.println("一共多少数据" + page.getTotal());
        System.out.println("数据" + page.getRecords());
    }

(3)设置分页拦截器

@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
            //1 创建MybatisPlusInterceptor拦截器对象
        MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor();
            //2 添加分页拦截器
        mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mpInterceptor;
    }
}

(4)运行测试程序

(5)如果想查看MP执行的SQL语句

  • 如果想查看MP执行的SQL语句,可以修改application.yml配置文件
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印SQL日志到控制台
  • 打开日志后,就可以在控制台打印出对应的SQL语句,开启日志功能性能就会受到影响,调试完后记得关闭

三、DQL控制

3.1 条件查询方式

3.1.1 条件查询的类

  • MyBatisPlus将书写复杂的SQL查询条件进行了封装,使用编程的形式完成查询条件的组合。

3.1.2 取消初始化spring日志打印(可选)

  • resources目录下新建添加logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
</configuration>

3.1.3 构建条件查询

(1)QueryWrapper

@Test
//查询所有-QueryWrapper
public void testGetAll_two(){
    QueryWrapper qw = new QueryWrapper();
    //lt: 小于(<)
    qw.lt("age",50);
    List<User> userList = userDao.selectList(qw);
    System.out.println(userList);
}
  • lt: 小于(<) 。
  • 最终的sql语句为: SELECT id,name,password,age,tel FROM user WHERE (age < ?)
  • gt: 大于(>) 。
  • 最终的sql语句为:SELECT id,name,password,age,tel FROM user WHERE (age < ? AND age > ?)

(2)QueryWrapper的基础上使用lambda(推荐)

@Test
//QueryWrapper的基础上使用lambda
public void testGetAll_three(){
    QueryWrapper<User> qw = new QueryWrapper<User>();
    qw.lambda().lt(User::getAge,50);
    List<User> userList = userDao.selectList(qw);
    System.out.println(userList);
}
  • User::getAget,为lambda表达式中的,类名::方法名。
  • 最终的sql语句为:SELECT id,name,password,age,tel FROM user WHERE (age < ?)
  • 注意:构建LambdaQueryWrapper的时候泛型不能省。

(3)LambdaQueryWrapper(推荐)

@Test
//LambdaQueryWrapper
public void testGetAll_four(){
    LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
    lqw.lt(User::getAge,50);
    List<User> userList = userDao.selectList(lqw);
    System.out.println(userList);
}

3.1.4 多条件构建

  • 需求:查询数据库表中,年龄在10岁到30岁之间的用户信息。
@Test
// 多条件构建
public void testGetAll_five(){
    LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
    //lt: 小于(<)
    //gt:大于(>)
    lqw.lt(User::getAge,50);
    lqw.gt(User::getAge,20);
    List<User> userList = userDao.selectList(lqw);
    System.out.println(userList);
}
  • 构建多条件的时候,可以支持链式编程
@Test
// 多条件构建 - 链式编程
public void testGetAll_six(){
    LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
    //lt: 小于(<)
    //gt:大于(>)
    lqw.lt(User::getAge,50).gt(User::getAge,20);
    List<User> userList = userDao.selectList(lqw);
    System.out.println(userList);
}
  • 需求:查询数据库表中,年龄小于10或年龄大于30的数据。
@Test
// 多条件构建 - 链式编程-“或者”条件
public void testGetAll_seven(){
    LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
    //lt: 小于(<)
    //gt:大于(>)
    lqw.lt(User::getAge,50).or().gt(User::getAge,20);
    List<User> userList = userDao.selectList(lqw);
    System.out.println(userList);
}
  • or()就相当于我们sql语句中的or关键字,不加默认是and。
  • 最终的sql语句为:SELECT id,name,password,age,tel FROM user WHERE (age < ? OR age > ?)

3.1.5 null判定

(1)案例

  • 后台如果想接收前端的两个数据,该如何接收?
  • 新建一个模型类,让其继承User类,并在其中添加age2属性,UserQuery在拥有User属性后同时添加了age2属性。
@Data
public class UserQuery extends User {
    private Integer age2;
}

(2)实例

  • lt()方法
  • condition为boolean类型,返回true,则添加条件,返回false则不添加条件。
@Test
//null判定
public void testGetAll_eight(){
    //模拟页面传递过来的查询数据
    UserQuery uq = new UserQuery();
    uq.setAge(10);
    uq.setAge2(30);
    LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
    lqw.lt(null!=uq.getAge2(),User::getAge, uq.getAge2());
    lqw.gt(null!=uq.getAge(),User::getAge, uq.getAge());
    List<User> userList = userDao.selectList(lqw);
    System.out.println(userList);
}

3.2 查询投影

3.2.1 查询指定字段

  • 目前我们在查询数据的时候,什么都没有做默认就是查询表中所有字段的内容,我们所说的查询投影,即不查询所有字段,只查询出指定内容的数据。

实例(1)

  • select(...)方法用来设置查询的字段列,可以设置多个
  • 最终的sql语句为:SELECT id,name,age FROM user
@Test
    //查询指定字段
    public void testGetAll_nine(){
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
        //select(...)方法用来设置查询的字段列,可以设置多个
        lqw.select(User::getAge,User::getName,User::getAge);
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);
    }

实例(2)

  • 如果使用的不是lambda,就需要手动指定字段。
  • QueryWrapper
@Test
//查询指定字段-非lambda(QueryWrapper)
//需要手动指定字段
public void testGetAll_ten(){
    QueryWrapper<User> lqw = new QueryWrapper<User>();
    lqw.select("id","name","age","tel");
    List<User> userList = userDao.selectList(lqw);
    System.out.println(userList);
}

3.2.2 聚合查询

  • 聚合函数查询
  • count、max、min、avg、sum的使用

    • count:总记录数
    • max:最大值
    • min:最小值
    • avg:平均值
    • sum:求和
  • count:SELECT count(*) as count FROM user
  • max:SELECT max(age) as maxAge FROM user
  • min:SELECT min(age) as minAge FROM user
  • avg:SELECT avg(age) as avgAge FROM user
  • sum:SELECT sum(age) as sumAge FROM user
@Test
    //聚合查询
    //count、max、min、avg、sum
    public void testGetAll_eleven(){
        QueryWrapper<User> lqw = new QueryWrapper<User>();
//        lqw.select("count(*) as count");
//        lqw.select("max(age) as maxAge");
//        lqw.select("min(age) as minAge");
//        lqw.select("sum(age) as sumAge");
        lqw.select("avg(age) as avgAge");
        List<Map<String, Object>> userList = userDao.selectMaps(lqw);
        System.out.println(userList);
    }

3.2.3 分组查询

  • 需求:分组查询,完成group by的查询使用。
  • groupBy为分组。
  • 最终的sql语句为:SELECT count(*) as count,tel FROM user GROUP BY tel
@Test
//分组查询
//group by
public void testGetAll_twelve(){
    QueryWrapper<User> lqw = new QueryWrapper<User>();
    lqw.select("count(*) as count,tel");
    lqw.groupBy("tel");
    List<Map<String, Object>> userList = userDao.selectMaps(lqw);
    System.out.println(userList);
}
  • 注意:
  • 聚合与分组查询,无法使用lambda表达式来完成。
  • MP只是对MyBatis的增强,如果MP实现不了,我们可以直接在DAO接口中使用MyBatis的方式实现。

3.3 查询条件设定

  • MP的查询条件有很多:

    • 范围匹配(> 、 = 、between)
    • 模糊匹配(like)
    • 空判定(null)
    • 包含性匹配(in)
    • 分组(group)
    • 排序(order)

3.3.1 等值查询

  • 需求:根据用户名和密码查询用户信息。
  • eq()
  • SELECT id,name,password,age,tel FROM user WHERE (name = ? AND password = ?)
  • selectList:查询结果为多个或者单个。
  • selectOne:查询结果为单个。
@Test
//查询条件
//等值查询
//需求:根据用户名和密码查询用户信息
//eq()
public void testGetAll_thirteen(){
    LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
    lqw.eq(User::getName,"cds").eq(User::getPassword,"4123");
    User loginUser = userDao.selectOne(lqw);
    System.out.println(loginUser);
}

3.3.2 范围查询

  • 需求:对年龄进行范围查询,使用lt()、le()、gt()、ge()、between()进行范围查询
  • gt():大于(>)
  • ge():大于等于(>=)
  • lt():小于(<)
  • lte():小于等于(<=)
  • between():between ? and ?
@Test
//查询条件
//范围查询
//需求:对年龄进行范围查询,使用lt()、le()、gt()、ge()、between()进行范围查询
//between():between ? and ?
public void testGetAll_fourteen(){
    LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
    lqw.between(User::getAge,10,50);
    List<User> userList = userDao.selectList(lqw);
    System.out.println(userList);
}

3.3.3 模糊查询

  • 需求:查询表中name属性的值以J开头的用户信息,使用like进行模糊查询
  • like():前后加百分号,如 %A%
  • likeLeft():前面加百分号,如 %J
  • likeRight():后面加百分号,如 J%
@Test
    //查询条件
    //模糊查询
    //需求:查询表中name属性的值以J开头的用户信息,使用like进行模糊查询
//    like():前后加百分号,如 %J%
//    likeLeft():前面加百分号,如 %J
//    likeRight():后面加百分号,如 J%
    public void testGetAll_fifteen(){
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
        lqw.like(User::getName,"4");
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);
    }

3.3.4 排序查询

3.4 字段映射与表名映射(映射匹配兼容性)

3.4.1 问题

  • 表字段与编码属性设计不同步?
  • 编码中添加了数据库中未定义的属性?
  • 采用默认查询开放了更多的字段查看权限?
  • 表名与编码开发设计不同步?

3.4.2 实例

(1)修改数据库表user为tbl_user

解决方案 —— 模型类添加@TableName注解
@Data
@AllArgsConstructor
@NoArgsConstructor
//@TableName注解
@TableName("tbl_user")
public class User {
    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;

    public User(String name, String password) {
        this.name = name;
        this.password = password;
    }
    //@Data是个组合注解,包含上面的注解的功能
    //@NoArgsConstructor:提供一个无参构造函数
    //@AllArgsConstructor:提供一个包含所有参数的构造函数
}

(2)将字段password修改成pwd

解决方案 —— 使用@TableField映射关系
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("tbl_user")
public class User {
    private Long id;
    private String name;
    
    
    //@TableField映射关系
    @TableField(value="pwd")
    private String password;
    private Integer age;
    private String tel;

    public User(String name, String password) {
        this.name = name;
        this.password = password;
    }
}

(3)添加一个数据库表不存在的字段

解决方案 —— 使用@TableField排除字段
@Data
@TableName("tbl_user")
public class User {
    private Long id;
    private String name;
    @TableField(value="pwd")
    private String password;
    private Integer age;
    private String tel;
    
    //使用@TableField排除字段
    @TableField(exist=false)
    
    //添加一个数据库表不存在的字段
    private Integer online;
}

(4)查询时将pwd隐藏

@Data
@TableName("tbl_user")
public class User {
    private Long id;
    private String name;
    
    //查询时将pwd隐藏
    //password=null
    @TableField(value="pwd",select=false)
    
    private String password;
    private Integer age;
    private String tel;
    @TableField(exist=false)
    private Integer online;
}

3.4.3 知识点 —— @TableField@TableName

@TableField

@TableName

四、DML控制

4.1 id生成策略控制(Insert)

4.1.1 知识点:@TableId

4.1.2 初始测试环境

@SpringBootTest
class Mybatisplus03DqlApplicationTests {
        
    @Autowired
    private UserDao userDao;


    @Test
    void testSave(){
        User user = new User();
        user.setName("11L");
        user.setPassword("1111");
        user.setAge(10);
        user.setTel("11110");
        userDao.insert(user);
    }

    @Test
    void testDelete(){
        userDao.deleteById(8888);
    }

    @Test
    void testUpdate(){
        User user = new User();
        user.setId(3L);
        user.setName("safety");
        user.setPassword("468");
        user.setAge(17);
        user.setTel("189");
        userDao.updateById(user);
    }
}

4.1.3 AUTO策略

(1)设置生成策略为AUTO

  • AUTO的作用是使用数据库ID自增。
  • 在使用该策略的时候一定要确保对应的数据库表设置了ID主键自增,否则无效。
@Data
@TableName("tbl_user")
public class User {
    
    //设置生成策略为AUTO
    @TableId(type = IdType.AUTO)
    
    private Long id;
    private String name;
    @TableField(value="pwd",select=false)
    private String password;
    private Integer age;
    private String tel;
    @TableField(exist=false)
    private Integer online;
}

(2)其他生成策略

  • NONE:不设置id生成策略
  • INPUT:用户手工输入id
  • ASSIGN_ID:雪花算法生成id(可兼容数值型与字符串型)。
  • ASSIGN_UUID:以UUID生成算法作为id生成策略。

(3)分布式ID

  • 当数据量足够大的时候,一台数据库服务器存储不下,这个时候就需要多台数据库服务器进行存储。
  • 比如订单表就有可能被存储在不同的服务器上,如果用数据库表的自增主键,因为在两台服务器上所以会出现冲突。
  • 这个时候就需要一个全局唯一ID,这个ID就是分布式ID。

4.1.4 INPUT策略

(1)设置生成策略为INPUT

  • 注意:这种ID生成策略,需要将表的自增策略删除掉。
@Data
@TableName("tbl_user")
public class User {
    
    //设置生成策略为INPUT
    @TableId(type = IdType.INPUT)
    
    private Long id;
    private String name;
    @TableField(value="pwd",select=false)
    private String password;
    private Integer age;
    private String tel;
    @TableField(exist=false)
    private Integer online;
}

(2)添加数据手动设置ID

@Test
    void testSave(){
        User user = new User();
        
        //添加数据手动设置ID
        //设置主键ID的值
        user.setId(9L);
        
        user.setName("11L");
        user.setPassword("1111");
        user.setAge(10);
        user.setTel("11110");
        userDao.insert(user);
    }

4.1.5 ASSIGN_ID策略

(1)设置生成策略为ASSIGN_ID

@Data
@TableName("tbl_user")
public class User {
    
    //设置生成策略为ASSIGN_ID
    @TableId(type = IdType.ASSIGN_ID)
    
    private Long id;
    private String name;
    @TableField(value="pwd",select=false)
    private String password;
    private Integer age;
    private String tel;
    @TableField(exist=false)
    private Integer online;
}

(2)添加数据不设置ID

  • 注意:这种生成策略,不需要手动设置ID,如果手动设置ID,则会使用自己设置的值。
  • 生成的ID就是一个Long类型的数据。
@Test
void testSave(){
    User user = new User();
    user.setName("13L");
    user.setPassword("1111");
    user.setAge(10);
    user.setTel("11110");
    userDao.insert(user);
}

4.1.6 ASSIGN_UUID策略

(1)设置生成策略为ASSIGN_UUID

  • 使用uuid需要注意的是,主键的类型不能是Long,而应该改成String类型。
@Data
@TableName("tbl_user")
public class User {

    //设置生成策略为ASSIGN_UUID
    @TableId(type = IdType.ASSIGN_UUID)

    private Long id;
    private String name;
    @TableField(value="pwd",select=false)
    private String password;
    private Integer age;
    private String tel;
    @TableField(exist=false)
    private Integer online;
}

(2)修改表的主键类型

(3)添加数据不设置ID

@Test
void testSave(){
    User user = new User();
    user.setName("13L");
    user.setPassword("1111");
    user.setAge(10);
    user.setTel("11110");
    userDao.insert(user);
}

4.1.7 雪花算法

4.1.8 ID生成策略对比

  • NONE:不设置id生成策略,MP不自动生成,约等于INPUT。

    • 所以这两种方式都需要用户手动设置,但是手动设置第一个问题是容易出现相同的ID造成主键冲突。
    • 为了保证主键不冲突就需要做很多判定,实现起来比较复杂。
  • AUTO:数据库ID自增,这种策略适合在数据库服务器只有1台的情况下使用,不可作为分布式ID使用。
  • ASSIGN_UUID:可以在分布式的情况下使用,而且能够保证唯一,但是生成的主键是32位的字符串,长度过长占用空间而且还不能排序,查询性能也慢。
  • ASSIGN_ID:可以在分布式的情况下使用,生成的是Long类型的数字,可以排序性能也高,但是生成的策略和服务器时间有关,如果修改了系统时间就有可能导致出现重复主键。

4.1.9 简化配置

  • 如何在项目中的每一个模型类上都使用相同的生成策略?

解决方案

  • application.yml中配置id-type: assign_id
  • 每个模型类的主键ID策略都将成为assign_id
# assign_id
# 每个模型类的主键ID策略都将成为assign_id
mybatis-plus:
  global-config:
    db-config:
      id-type: assign_id

4.1.10 数据库表与模型类的映射关系

  • MP会默认将模型类的类名名首字母小写作为表名使用,假如数据库表的名称都以tbl_开头,那么我们就需要将所有的模型类上添加@TableName
  • 麻烦。
  • 那么该如何简化呢?
  • application.yml中配置table-prefix: tbl_
  • 设置表的前缀内容,这样MP就会拿tbl_加上模型类的首字母小写,就刚好组装成数据库的表名。
# assign_id
# 每个模型类的主键ID策略都将成为assign_id
mybatis-plus:
  global-config:
    db-config:
      id-type: assign_id
      
      # 在application.yml中配置id-type: assign_id
      table-prefix: tbl_

4.2 多记录操作(删除-查询)(Delete-Selete)

4.2.1 删除指定多条数据

(1)对应的API方法

  • 删除(根据ID 批量删除),参数是一个集合,可以存放多个id值。
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

(2)需求:根据传入的id集合将数据库表中的数据删除掉

  • 执行成功后,数据库表中的数据就会按照指定的id进行删除。
    @Test
    void testDelete(){
        //删除指定多条数据
        List<Long> list = new ArrayList<>();
        list.add(1567880975352897538L);
        list.add(9l);
        list.add(8L);
        userDao.deleteBatchIds(list);
    }

4.2.2 查询指定多条数据

(1)对应的API方法

  • 查询(根据ID 批量查询),参数是一个集合,可以存放多个id值。
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

(2)需求:根据传入的ID集合查询用户信息

@Test
void testGetByIds(){
    //查询指定多条数据
    List<Long> list = new ArrayList<>();
    list.add(1L);
    list.add(2l);
    list.add(3L);
    userDao.selectBatchIds(list);
}

4.3 逻辑删除(Delete/Update)

  • 物理删除:业务数据从数据库中丢弃,执行的是delete操作。
  • 逻辑删除:为数据设置是否可用状态字段,删除时设置状态字段为不可用状态,数据保留在数据库中,执行的是update操作。

4.3.1 MP中逻辑删除具体实现

(1)修改数据库表添加deleted列

  • 字段名可以任意,内容也可以自定义,比如0代表正常,1代表删除,可以在添加列的同时设置其默认值为0正常。

(2)实体类添加属性

  • 添加与数据库表的列对应的一个属性名,名称可以任意,如果和数据表列名对不上,可以使用@TableField进行关系映射,如果一致,则会自动对应。
  • 标识新增的字段为逻辑删除字段,使用@TableLogic
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    @TableId(type = IdType.ASSIGN_UUID)
    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;

    //@TableLogic
    @TableLogic(value = "0",delval = "1")
    //value为正常数据的值,delval为删除数据的值
    private Integer deleted;

    public User(String name, String password) {
        this.name = name;
        this.password = password;
    }

}

(3)运行删除方法

  • 逻辑删除最后走的是update操作,会将指定的字段修改成删除状态对应的值。
@Test
void testDelete2(){
    userDao.deleteById(7L);
}

(4)逻辑删除,对查询的影响?

  • 执行查询操作
@Test
void testFind(){
    System.out.println(userDao.selectList(null));
}
  • 打印出来的sql语句中会多一个查询条件。
  • SELECT id,name,password,age,tel,deleted FROM user WHERE deleted=0
  • WHERE deleted=0
  • MP的逻辑删除会将所有的查询都添加一个未被删除的条件,也就是已经被删除的数据是 不应该被查询出来的。

(5)已经删除的数据都查询出来该如何实现(UserDao)

@Mapper
public interface UserDao extends BaseMapper<User> {
    //查询所有数据包含已经被删除的数据
    @Select("select * from user")
    public List<User> selectAll();
}

(6)全局配置

  • 如果每个表都要有逻辑删除,那么就需要在每个模型类的属性上添加@TableLogic注解
  • 麻烦
  • 在配置文件中添加全局配置:
global-config:
  db-config:
      # 逻辑删除字段名
    logic-delete-field: deleted
    # 逻辑删除字面值:未删除为0
    logic-delete-value: 0
    # 逻辑删除字面值:删除为1
    logic-not-delete-value: 1

4.3.2 逻辑删除的本质

  • 逻辑删除的本质其实是修改操作。
  • 如果加了逻辑删除字段,查询数据时也会自动带上逻辑删除字段。
  • 实际的 SQL语句:UPDATE user SET deleted=1 where id = ? AND deleted=0

4.3.3 知识点:@TableLogic

4.4 乐观锁(Update)

  • 业务并发现象带来的问题:秒杀
  • 乐观锁主要解决的问题是当要更新一条记录的时候,希望这条记录没有被别人更新。

4.4.0 乐观锁插件

4.4.1 实现思路(乐观锁的实现方式)

4.4.2 实现步骤

(1)数据库表添加列

  • 列名可以任意,比如使用version,给列设置默认值为1。

(2)在模型类中添加对应的属性

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    @TableId(type = IdType.ASSIGN_UUID)
    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;

    @TableLogic(value = "0",delval = "1")
    //value为正常数据的值,delval为删除数据的值
    private Integer deleted;

    //在模型类中添加对应的属性
    @Version
    private Integer version;

    public User(String name, String password) {
        this.name = name;
        this.password = password;
    }

}

(3)添加乐观锁的拦截器

  • @Configuration一定要注意!
@Configuration
public class MpConfig {
    @Bean
    public MybatisPlusInterceptor mpInterceptor() {
        //1.定义Mp拦截器
        MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
        //2.添加乐观锁拦截器
        mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return mpInterceptor;
    }
}

(4)执行更新操作-添加version数据

  • 传递的是1,MP会将1进行加1,然后,更新回到数据库表中。
@Test
void testUpdate1(){
    User user = new User();
    user.setId(3L);
    user.setName("Jock666");
    //添加version数据
    user.setVersion(1);
    userDao.updateById(user);
}
  • 实现乐观锁,首先第一步应该是拿到表中的version;
  • 然后拿version当条件在将version加1更新回到数据库表中;
  • 所以我们在查询的时候,需要对其进行查询。
@Test
void testUpdate3(){
    //1.先通过要修改的数据id将当前数据查询出来
    User user = userDao.selectById(3L);
    //2.将要修改的属性逐一设置进去
    user.setName("Jock888");
    userDao.updateById(user);
}

(5)模拟加锁情况

  • 实现多个人修改同一个数据的时候,只能有一个人修改成功。
@Test
void testUpdate4(){
    //1.先通过要修改的数据id将当前数据查询出来
    User user = userDao.selectById(3L); //version=3
    User user2 = userDao.selectById(3L); //version=3
    user2.setName("Jock aaa");
    userDao.updateById(user2); //version=>4
    user.setName("Jock bbb");
    userDao.updateById(user); //version=3?条件还成立吗?不成立
}
  • 结果:

五、快速开发(代码生成器)

声明:三二一的一的二|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - MyBatisPlus


三二一的一的二