MyBatis-Plus

前言

数据库访问的发展

  1. 最初始,在 Java 项目中直接使用 jdbc 来访问数据库,创建 ConnectionResultSet等;
  2. 后来,对 jdbc 的操作进行了封装,创建了很多的工具类,如 DBUtil;
  3. 再后面,就使用一些持久层的框架:
    1. hibernate:全自动的 ORM 框架,实现 Java 对象到表的映射,可以通过 Java 对象的方法,操作表中的数据,开发人员可以不了解或少了解 SQl 语言;
    2. jpa 规范:定义了访问数据库的各种操作,定义了一致的方法操作数据库;jpa 有各种实现,如 hibernate、open-jpa、link 等;
    3. mybatis:需要编写 xml 配置文件,在 xml 文件中编写 SQL 语句,访问数据库;任何的操作都需要使用 xml 文件,对开发人员要求比较高,需要熟悉 SQL 语言,单表的 CRUD 也需要 xml 文件,编写 SQL 语句;
    4. mybatis-plus:简称 MP,是对 mybatis 的增强,在 mybatis 基础上加入了一层,通过 mybatis-plus 直接可实现单表的 CURD,不使用 xml 文件,其还能支持分页、性能统计、逻辑删除等等。

mybatis 和 mybatis-plus

  • 在持久层框架中,mybatis 应用比较多,而且比重在逐渐的上升,通常项目的组合是 SSM。mybatis 之所以火,是因为他的灵活,使用方便,优化比较容易。mybatis 直接执行 sql 语句,sql 语句是写在 xml 文件中,使用 mybatis 需要多个 xml 配置文件,在一定程度上比较繁琐。一般数据库的操作都要涉及到 CURD。

  • mybatis-plus 是在 mybatis 上的增强,减少了 xml 的配置,几乎不用编写 xml 就可以做到单表的 CURD,很是方便,极大提供了开发的效率。 我们写程序目的就是让生活更加简单。

一、什么是 mybatis-plus

  • MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
  • MyBatis-Plus 在 MyBatis 之上套了一层外衣,单表 CURD 的操作几乎都可以由 MyBatis-Plus 代替执行。而且提供了各种查询方式,分页行为。作为使用者无需编写 xml,直接调用 MyBatis-Plus 提供的 API 就可以了。
  • 详细介绍参见官网:http://mp.baomidou.com/

二、快速开始

1. 准备数据库表

准备一张表(此处库名:springdb),下面是建表 SQL

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(80) DEFAULT NULL,
  `email` varchar(80) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

2. 创建 Spring Boot 应用

使用 IDEA 创建一个 Spring Boot 应用,此处定义的项目名为 plus,选择 MySQL 依赖

3. pom.xml 中添加 mybatis-plus 依赖

pom.xml中手动添加 mybatis-plus 的依赖

<!--mybatis-plus-->
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.2</version>
</dependency>

4. 配置数据库连接信息

resources 目录下新建 application.yml,配置数据库连接信息

# 配置数据库连接信息
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/springdb?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8
    username: root
    password: luis

5. 准备实体类

创建实体类:新建 entity 文件夹,其下创建 User 实体类,注意这里使用 MP 中的注解指定主键的类型

package com.luis.plus.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;

public class User {

    /**
     * 使用MP中的注解@TableId来指定主键的方式
     * value:主键字段的名称,如果是id,可以省略不写
     * type:指定主键的类型,也就是主键值如何生成。IdType.AUTO表示自增
     */
    @TableId(
            value = "id",
            type = IdType.AUTO
    )
    private Integer id;
    private String name;
    private String email;
    private Integer age;

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Integer getAge() {
        return age;
    }

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

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

6. 准备 Mapper 接口

创建 Mapper 接口:新建 mapper 目录,其下创建 UserMapper 接口,继承 MP 中 BaseMapper 接口,并指定泛型类型

package com.luis.plus.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.luis.plus.entity.User;

/**
 * 自定义Mapper,即Dao接口
 *  1.需要继承MP中的BaseMapper接口
 *  2.指定泛型,指定实体类
 *
 *  BaseMapper是MP中的对象,定义了17个方法操作数据(CURD)
 */
public interface UserMapper extends BaseMapper<User> {
}

7. 主启动类上添加包扫描器

主启动类上添加包扫描器注解,扫描 Mapper 类所在的包

package com.luis.plus;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan(value = "com.luis.plus.mapper") //包扫描器,指定Mapper类所在的包
public class PlusApplication {

    public static void main(String[] args) {
        SpringApplication.run(PlusApplication.class, args);
    }

}

8. 测试

测试类中使用自动注入方式注入 Mapper 接口,调用其方法(继承了父接口),初步实现添加操作

package com.luis.plus;

import com.luis.plus.entity.User;
import com.luis.plus.mapper.UserMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
@SuppressWarnings("all") //抑制警告
public class PlusApplicationTests {

    @Autowired
    private UserMapper userDao; //使用自动注入,注入Mapper对象(Dao)

    //测试添加操作
    @Test
    public void testUserInsert() {

        User user = new User();
        user.setName("luis");
        user.setEmail("luis@163.com");
        user.setAge(25);

        int rows = userDao.insert(user);
        System.out.println("insert ========> " + rows);
    }

}

三、配置 mybatis 日志

application.yml中配置日志打印

# 配置日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

四、CRUD 基本用法

1. insert 操作

//测试添加操作 insert
@Test
public void testUserInsert() {

    User user = new User();
    user.setName("luis");
    user.setEmail("luis@163.com");
    user.setAge(25);

    int rows = userDao.insert(user); //返回数据改变的行数
    Integer id = user.getId(); //获取主键字段的值(数据添加后的主键字段id值)
    System.out.println("insert rows ========> " + rows + " id = " + id);
}

2. update 操作

//测试修改操作 updata
@Test
public void testUserUpdata() {

    User user = new User();
    user.setId(2); //修改id为2的数据
    user.setName("jack");
    user.setEmail("jack@163.com");
    user.setAge(22);

    /*
            小坑提点:
            注意,这里会被修改的是所有非空的字段!!!(也就是新数据只要非空,对应的旧数据就会被修改!)

            例如:新数据中不修改name值,它默认为null,就不会被修改!
            而,如果不修改age值,但如果实体类中不以Integer包装类型定义,而是以int基本类型定义,
            它默认就会是0,不是null,此时,它就会被按照默认值0被修改!

            总结:定义实体类字段时,建议使用包装类型,不要用基本类型!
         */
    int i = userDao.updateById(user); //根据主键修改数据
    System.out.println("updata rows ========> " + i);
}

3. delete 操作

/**
     * deleteById:根据主键删一条数据
     */
@Test
public void testDeleteById() {
    int i = userDao.deleteById(3);
    System.out.println("delete " + i);
}

/**
     * deleteByMap:根据条件删数据(可以有多个条件)
     * 使用Map集合封装条件,key是字段,value是对应的值
     */
@Test
public void testDeleteByMap() {
    HashMap<String, Object> map = new HashMap();
    map.put("name", "luis");
    map.put("age", 23);
    //DELETE FROM user WHERE name = ? AND age = ?
    int i = userDao.deleteByMap(map);
    System.out.println("deleteByMap " + i);
}

/**
     * deleteBatchIds:根据多id,批量删除数据
     */
@Test
public void testDeleteBatchIds() {

    // 方式一:普通方式创建list集合
    // ArrayList<Integer> list = new ArrayList<>();
    // list.add(6);
    // list.add(7);
    // list.add(8);
    // list.add(9);

    // 方式二:使用lambda表达式创建list集合
    List<Integer> list = Stream.of(6, 7, 8, 9).collect(Collectors.toList());

    //DELETE FROM user WHERE id IN ( ? , ? , ? , ? )
    int i = userDao.deleteBatchIds(list);
    System.out.println("deleteBatchIds " + i);
}

4. select 操作

/**
     * 根据一个主键查单条数据
     */
@Test
public void testSelectById() {
    User user = userDao.selectById(8);
    System.out.println(user);
}

/**
     * 批处理查询:根据多id查多条数据
     */
@Test
public void testSelectBatchIds() {
    //将流中的数据转为集合类型
    List<Integer> ids = Stream.of(10, 11, 12).collect(Collectors.toList());
    //SELECT id,name,email,age FROM user WHERE id IN ( ? , ? , ? )
    List<User> users = userDao.selectBatchIds(ids);
    // for (User user : users) {
    //     System.out.println(user);
    // }
    //使用lambda表达式
    users.forEach(user -> {
        System.out.println(user);
    });
}

/**
     * (多)条件查询
     * 使用map集合封装集合数据,返回List
     */
@Test
public void testSelectByMap() {

    Map<String, Object> map = new HashMap<>();
    map.put("name", "luis");
    map.put("age", 20);

    //SELECT id,name,email,age FROM user WHERE name = ? AND age = ?
    List<User> users = userDao.selectByMap(map);
    users.forEach(user -> {
        System.out.println(user);
    });
}

五、ActiveRecord(AR)

ActiveRecord 是什么

  • 每一个数据库表对应创建的一个类,类的每一个对象实例对应于数据库中表的一行记录;通常表的每个字段在类中都有相应的 Field 属性;
  • ActiveRecord 负责把自己持久化,在 ActiveRecord 中封装了对数据库的访问,通过对象自己实现 CRUD,实现优雅的数据库操作;
  • ActiveRecord 也封装了部分业务逻辑,可以作为业务对象使用。

环境准备

  1. 准备一张数据库表

    DROP TABLE IF EXISTS `dept`;
    CREATE TABLE `dept` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` varchar(50) DEFAULT NULL,
      `mobile` varchar(11) DEFAULT NULL,
      `manager` int(11) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
    
  2. 创建实体类,继承 MP 中的 Model 对象,指定主键类型

    package com.luis.plus.entity;
    
    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.extension.activerecord.Model;
    
    /**
     * 使用ActiveRecord(AR),要求实体类需要继承MP中的Model对象
     * Model中提供了对数据库的CURD操作
     */
    public class Dept extends Model<Dept> {
    
        @TableId(value = "id", type = IdType.AUTO) //指定主键类型为自增
        private Integer id;
        private String name;
        private String mobile;
        private Integer manager;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getMobile() {
            return mobile;
        }
    
        public void setMobile(String mobile) {
            this.mobile = mobile;
        }
    
        public Integer getManager() {
            return manager;
        }
    
        public void setManager(Integer manager) {
            this.manager = manager;
        }
    
        @Override
        public String toString() {
            return "Dept{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", mobile='" + mobile + '\'' +
                    ", manager=" + manager +
                    '}';
        }
    }
    
  3. 定义 Mapper 接口

    package com.luis.plus.mapper;
    
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.luis.plus.entity.Dept;
    
    /**
     * 说明:DeptMapper我们用不到,但是仍需要定义!
     * MP需要使用DeptMapper获取数据库表的信息,如果不定义,
     * MP会报错,找不到表的定义信息!
     */
    public interface DeptMapper extends BaseMapper<Dept> {
    }
    
  4. 创建测试类,进行测试

    package com.luis.plus;
    
    import com.luis.plus.entity.Dept;
    import org.junit.jupiter.api.Test;
    import org.springframework.boot.test.context.SpringBootTest;
    
    @SpringBootTest
    public class DeptARTests {
        
        //insert
        @Test
        public void testARInsert() {
    
            Dept dept = new Dept();
            dept.setName("研发部");
            dept.setMobile("12344445555");
            dept.setManager(2);
    
            //INSERT INTO dept ( name, mobile, manager ) VALUES ( ?, ?, ? )
            //调用实体对象自己的方法,完成对象自身到数据库的添加操作
            boolean success = dept.insert();
            System.out.println("ar insert result: " + success);
        }
    }
    

1. AR 之 insert

//insert
@Test
public void testARInsert() {

    Dept dept = new Dept();
    dept.setName("研发部");
    dept.setMobile("12344445555");
    dept.setManager(2);

    //INSERT INTO dept ( name, mobile, manager ) VALUES ( ?, ?, ? )
    //调用实体对象自己的方法,完成对象自身到数据库的添加操作
    boolean success = dept.insert();
    System.out.println("ar insert result: " + success);
}

2. AR 之 update

//updateById
@Test
public void testARUpdateById() {

    Dept dept = new Dept();
    dept.setId(1);
    dept.setName("市场部");
    dept.setManager(3);

    //调用实体对象自己的方法,完成对象自身的修改操作
    //UPDATE dept SET name=?, manager=? WHERE id=?
    boolean flag = dept.updateById();
    System.out.println("ar update result: " + flag);
}

3. AR 之 delete

//deleteById 有参
@Test
public void testARDeleteById() {

    Dept dept = new Dept();

    //调用实体对象自己的方法,完成对象自身的删除操作
    boolean ret = dept.deleteById(1); //调用有参方法进行删除
    System.out.println("ar deleteById result: " + ret);
}

//deleteById 无参
@Test
public void testARDeleteById2() {

    Dept dept = new Dept();
    dept.setId(2); //调用无参的方法删除的话,对象本身需要给id赋值

    //调用实体对象自己的方法,完成对象自身的删除操作
    boolean ret = dept.deleteById(); //调用无参方法进行删除
    System.out.println("ar deleteById result: " + ret);
}

4. AR 之 select

//selectById 有参
@Test
public void testARSelectById() {

    Dept dept = new Dept();

    //调用实体对象自己的方法,完成对象自身的查询操作
    Dept dept1 = dept.selectById(3); //调用有参方法进行查询,查到返回对象,查不到返回null
    System.out.println("ar selectById result: " + dept1);
}

//selectById 无参
@Test
public void testARSelectById2() {

    Dept dept = new Dept();
    dept.setId(4); //调用无参的方法查询的话,对象本身需要给id赋值

    //调用实体对象自己的方法,完成对象自身的查询操作
    Dept dept2 = dept.selectById(); //调用无参方法进行查询,查到返回对象,查不到返回null
    System.out.println("ar selectById result: " + dept2);
}

六、表和列

主键、TableName、TableId

1. 主键类型

IdType 枚举类,主键定义如下:

public enum IdType {
    AUTO(0),
    NONE(1),
    INPUT(2),
    ASSIGN_ID(3),
    ASSIGN_UUID(4);
}

------说明:-----------
    
ASSIGN_ID(雪花算法):如果不设置类型值,默认则使用此策略。此策略会使用雪花算法,自动生成主键 ID,实体类中主键类型为 Long 或 String;mysql 中 对应 bigint 和 varchar;分布式系统中应用广泛。
    
ASSIGN_UUID(排除中划线的UUID):自动生成排除中划线的 UUID,主键类型为 String,mysql 中对应 varchar(32)。
    
AUTO(数据库 ID 自增):支持如 mysql 这种支持主键自增的数据库。

INPUT(插入前自行设置主键值):手工输入~~    
    
NONE(无状态):没有主键~~    

2. 指定表名

当定义的实体类名与数据库中默认的表名不一致时,需要在实体类的定义上使用@TableName注解说明表名称。

例如:@TableName(value="数据库表名")

3. 指定列名

当定义的实体类中属性名与数据库表中字段名不一致时,需要在实体类的对应属性定义上使用@TableField注解指定属性和列名的对应关系。

例如:@TableField(value="数据库表中字段名")

4. 驼峰命名

指当数据库表中列名使用下划线,实体类的属性名使用驼峰命名时,MyBatis 自动将两者联系起来,默认支持此种规则。【推荐】

七、自定义 SQL

1. 表定义

student.sql

DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(80) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `email` varchar(80) DEFAULT NULL,
  `status` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

2. 创建实体

public class Student {

    @TableId(value = "id", type = IdType.AUTO) 
    private Integer id;
    private String name;
    private Integer age;
    private String email;
    private Integer status;
    // getter and setter
}    

3. 创建 Mapper

public interface StudentMapper extends BaseMapper<Student> {
    List<Student> selectByName();
}

4. 新建 SQL 映射 xml 文件

PS:此处 xml 文件是在 resources 下新建的 xml 目录下

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.luis.plus.mapper.StudentMapper">
    <select id="selectByName" resultType="com.luis.plus.entity.Student">
        select id,name,age,email,status from student order by id limit 10
  </select>
</mapper>

5. 配置 xml 文件位置

application.yml

# 配置日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath*:xml/*Mapper.xml

6. 测试

@SpringBootTest
@SuppressWarnings("all")
public class StudentTests {

    @Autowired
    private StudentMapper studentDao;

    @Test
    public void testSelect() {
        List<Student> students = studentDao.selectByName();
        students.forEach(stu -> {
            System.out.println(stu);
        });
    }
}

日志:

==>  Preparing: select id,name,age,email,status from student order by id limit 10
==> Parameters: 
<==    Columns: id, name, age, email, status
<==        Row: 1, luis, 22, xx, 3
<==        Row: 2, jack, 20, xxx, 2
<==      Total: 2

八、查询和分页

1. 查询构造器 Wrapper

  • Wrapper:com.baomidou.mybatisplus.core.conditions.Wrapper
  • Wrapper 是一个接口,用来构造查询的条件,也就是 SQL 中 where 子句后面的内容
  • Wrapper 有两个接口实现类,分别是 QueryWrapper 和 UpdateWrapper

QueryWrapper(LambdaQueryWrapper) 和 UpdateWrapper(LambdaUpdateWrapper) 的父类用于生成 sql 的 where 条件, entity 属性也用于生成 sql 的 where 条件. MP3.x 开始支持 lambda 表达式,LambdaQueryWrapper,LambdaUpdateWrapper 支持 lambda 表达式的构造查询条件。

PS:下列条件对应的方法在 Wrapper 的接口实现类可查看到(如 AbstractWrapper)

IDEA 中相关快捷键:Ctrl + H 查看继承结构Ctrl + F12 查看相关方法

条件:

条件 说明
allEq 基于 map 的相等
eq 等于 =
ne 不等于 <>
gt 大于 >
ge 大于等于 >=
lt 小于 <
le 小于等于 <=
between BETWEEN 值 1 AND 值 2
notBetween NOT BETWEEN 值 1 AND 值 2
like LIKE '%值%'
notLike NOT LIKE '%值%'
likeLeft LIKE '%值'
likeRight LIKE '值%'
isNull 字段 IS NULL
isNotNull 字段 IS NOT NULL
in 字段 IN (value1, value2, ...)
notIn 字段 NOT IN (value1, value2, ...)
inSql 字段 IN ( sql 语句 )
例: inSql("age", "1,2,3")--->age in (1,2,3,4,5,6)
例: inSql("id", "select id from table where id < 3")--->id in (select id from table where id < 3)
notInSql 字段 NOT IN ( sql 语句 )
groupBy GROUP BY 字段
orderByAsc 升序 ORDER BY 字段, ... ASC
orderByDesc 降序 ORDER BY 字段, ... DESC
orderBy 自定义字段排序
orderBy(true, true, "id", "name")--->order by id ASC,name ASC
having 条件分组
or OR 语句,拼接 + OR 字段=值
and AND 语句,拼接 + AND 字段=值
apply 拼接 sql
last 在 sql 语句后拼接自定义条件
exists 拼接 EXISTS ( sql 语句 )
例 : exists("select id from table where age =
1")--->exists (select id from table where age = 1)
notExists 拼接 NOT EXISTS ( sql 语句 )
nested 正常嵌套 不带 AND 或者 OR

1)QueryWrapper 查询条件封装类

方法 说明
select 设置查询字段 select 后面的内容

2)UpdateWrapper 更新条件封装类

方法 说明
set 设置要更新的字段,MP 拼接 sql 语句
setSql 参数是 sql 语句,MP 不在处理语句

2. 查询

表数据:student.sql

DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(80) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `email` varchar(80) DEFAULT NULL,
  `status` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of student
-- ----------------------------
INSERT INTO `student` VALUES ('1', 'luis', '22', 'xx', '1');
INSERT INTO `student` VALUES ('2', 'jack', '20', 'xxx', '1');
INSERT INTO `student` VALUES ('3', 'mike', '12', 'xxx', '2');
INSERT INTO `student` VALUES ('4', 'look', '33', null, '2');
INSERT INTO `student` VALUES ('5', 'at', '34', null, '2');

1. allEq

**以 Map 为参数条件 **

a. 查询条件没有 null
@Test
public void testAllEq() {
    // 新建查询条件封装类
    QueryWrapper<Student> qw = new QueryWrapper<>();
    // 封装条件
    Map<String, Object> param = new HashMap<>();
    param.put("name", "luis");
    param.put("age", 22);
    // 构造查询条件(基于map的相等)
    qw.allEq(param);
    // 调用MP自己的查询方法
    // SELECT id,name,age,email,status FROM student WHERE (name = ? AND age = ?)
    List<Student> students = studentDao.selectList(qw);
    students.forEach(stu -> System.out.println(stu));
}
b. 查询条件有 null

当查询条件中有 null 时,调用 MP 的方法时可添加布尔参数,选择是否忽略 null 的情况。

@Test
public void testAllEq2() {
    // 新建查询条件封装类
    QueryWrapper<Student> qw = new QueryWrapper<>();
    // 封装条件
    Map<String, Object> param = new HashMap<>();
    param.put("name", "luis");
    param.put("age", null); // 此时的value为null
    // 构造查询条件(基于map的相等)
    // qw.allEq(param, true); // 不忽略value值为null的情况
    qw.allEq(param, false); // 忽略value值为null的情况
    // 调用MP自己的查询方法
    // 不忽略value值为null的情况【true】:WHERE (name = ? AND age IS NULL)
    // 忽略value值为null的情况【false】:WHERE (name = ?)
    List<Student> students = studentDao.selectList(qw);
    students.forEach(stu -> System.out.println(stu));
}

2. eq

等于 =

@Test
public void testEq() {
    QueryWrapper<Student> qw = new QueryWrapper<>();

    // 组装条件(多条件使用多个eq,使用and连接)
    qw.eq("name", "luis");
    qw.eq("age", 20);
    // WHERE (name = ?) 单条件
    // WHERE (name = ? AND age = ?) 多条件
    List<Student> students = studentDao.selectList(qw);
    students.forEach(stu -> System.out.println(stu));
}

3. ne

不等于 <>

@Test
public void testNe() {
    QueryWrapper<Student> qw = new QueryWrapper<>();

    // 组装条件
    qw.ne("name", "mike");
    // WHERE (name <> ?)
    List<Student> students = studentDao.selectList(qw);
    students.forEach(stu -> System.out.println(stu));
}

4. gt

大于 >

@Test
public void testGt() {
    QueryWrapper<Student> qw = new QueryWrapper<>();

    // 组装条件
    qw.gt("age", 30);
    // WHERE (age > ?)
    List<Student> students = studentDao.selectList(qw);
    students.forEach(stu -> System.out.println(stu));
}

5. ge

大于等于 >=

@Test
public void testGe() {
    QueryWrapper<Student> qw = new QueryWrapper<>();

    // 组装条件
    qw.ge("age", 34);
    // WHERE (age >= ?)
    List<Student> students = studentDao.selectList(qw);
    students.forEach(stu -> System.out.println(stu));
}

6. lt

小于 <

@Test
public void testLt() {
    QueryWrapper<Student> qw = new QueryWrapper<>();

    // 组装条件
    qw.lt("age", 20);
    // WHERE (age < ?)
    List<Student> students = studentDao.selectList(qw);
    students.forEach(stu -> System.out.println(stu));
}

7. le

小于等于 <=

@Test
public void testLe() {
    QueryWrapper<Student> qw = new QueryWrapper<>();

    // 组装条件
    qw.le("age", 12);
    // WHERE (age <= ?)
    List<Student> students = studentDao.selectList(qw);
    students.forEach(stu -> System.out.println(stu));
}

8. between

在两个值范围之间(包含这两个值,即属于闭区间)

@Test
public void testBetween() {
    QueryWrapper<Student> qw = new QueryWrapper<>();

    // 组装条件
    qw.between("age", 20, 30);
    // WHERE (age BETWEEN ? AND ?)
    List<Student> students = studentDao.selectList(qw);
    students.forEach(stu -> System.out.println(stu));
}

9. notBetween

不在两个值范围之间(不考虑这两个值)

@Test
public void testNotBetween() {
    QueryWrapper<Student> qw = new QueryWrapper<>();

    // 组装条件
    qw.notBetween("age", 20, 33);
    // WHERE (age not BETWEEN ? AND ?)
    List<Student> students = studentDao.selectList(qw);
    students.forEach(stu -> System.out.println(stu));
}

10. like、notLike

like 匹配值 "%值%"

notLike 不匹配值 "%值%"

@Test
public void testLike() {
    QueryWrapper<Student> qw = new QueryWrapper<>();

    // 组装条件
    qw.like("name", "a");
    // WHERE (name LIKE ?)
    // %a%
    List<Student> students = studentDao.selectList(qw);
    students.forEach(stu -> System.out.println(stu));
}

@Test
public void testNotLike() {
    QueryWrapper<Student> qw = new QueryWrapper<>();

    // 组装条件
    qw.notLike("name", "a");
    // WHERE (name NOT LIKE ?)
    // %a%
    List<Student> students = studentDao.selectList(qw);
    students.forEach(stu -> System.out.println(stu));
}

11. likeLeft、likeRight

匹配值的部分

likeLeft 匹配 like %值

likeRight 匹配 like 值%

@Test
public void testLikeLeft() {
    QueryWrapper<Student> qw = new QueryWrapper<>();

    // 组装条件
    qw.likeLeft("name", "k");
    // WHERE (name LIKE ?)
    // %k 匹配所有以k结尾的
    List<Student> students = studentDao.selectList(qw);
    students.forEach(stu -> System.out.println(stu));
}

@Test
public void testLikeRight() {
    QueryWrapper<Student> qw = new QueryWrapper<>();

    // 组装条件
    qw.likeRight("name", "l");
    // WHERE (name LIKE ?)
    // l% 匹配所有以l开头的
    List<Student> students = studentDao.selectList(qw);
    students.forEach(stu -> System.out.println(stu));
}

12. isNull、isNotNull

判断字段值是否为 null

@Test
public void testIsNull() {
    QueryWrapper<Student> qw = new QueryWrapper<>();

    // 组装条件
    qw.isNull("email");
    // WHERE (email IS NULL)
    List<Student> students = studentDao.selectList(qw);
    students.forEach(stu -> System.out.println(stu));
}

@Test
public void testIsNotNull() {
    QueryWrapper<Student> qw = new QueryWrapper<>();

    // 组装条件
    qw.isNotNull("email");
    // WHERE (email IS NOT NULL)
    List<Student> students = studentDao.selectList(qw);
    students.forEach(stu -> System.out.println(stu));
}

13. in、notIn

是否符合在 in 后列表中的值

@Test
public void testIn() {
    QueryWrapper<Student> qw = new QueryWrapper<>();

    // 组装条件
    qw.in("name", "luis", "jack", "king");
    // WHERE (name IN (?,?,?))
    List<Student> students = studentDao.selectList(qw);
    students.forEach(stu -> System.out.println(stu));
}

@Test
public void testNotIn() {
    QueryWrapper<Student> qw = new QueryWrapper<>();

    // 组装条件
    qw.notIn("name", "luis", "jack", "king");
    // WHERE (name NOT IN (?,?,?))
    List<Student> students = studentDao.selectList(qw);
    students.forEach(stu -> System.out.println(stu));
}

14. inSql、notInSql

常用来做子查询

inSql 类似 in(子查询语句)

notInSql 类似 notIn(子查询语句)

@Test
public void testInSql() {
    QueryWrapper<Student> qw = new QueryWrapper<>();

    // 组装条件
    qw.inSql("age", "select age from student where id = 1");
    // WHERE (age IN (select age from student where id = 1))
    List<Student> students = studentDao.selectList(qw);
    students.forEach(stu -> System.out.println(stu));
}

@Test
public void testNotInSql() {
    QueryWrapper<Student> qw = new QueryWrapper<>();

    // 组装条件
    qw.notInSql("age", "select age from student where id = 1");
    // WHERE (age NOT IN (select age from student where id = 1))
    List<Student> students = studentDao.selectList(qw);
    students.forEach(stu -> System.out.println(stu));
}

15. groupBy

基于多个字段分组

@Test
public void testGroupBy() {
    QueryWrapper<Student> qw = new QueryWrapper<>();

    // 显示查询完后要显示的字段,即 select name,count(*) personNumbers from...
    // 如果不指定要显示的字段,则默认显示全部字段,此时就会出现问题
    qw.select("name, count(*) personNumbers");
    qw.groupBy("name"); // 要分组的字段

    // SELECT name, count(*) personNumbers FROM student GROUP BY name
    List<Student> students = studentDao.selectList(qw);
    students.forEach(stu -> System.out.println(stu));
}

16. orderByAsc、orderByDesc、orderBy

orderByAsc 按字段升序

orderByDesc 按字段降序

orderBy 每个字段指定排序方向(是否启用条件;指定字段和排序方式)

@Test
public void testOrderByAsc() {
    QueryWrapper<Student> qw = new QueryWrapper<>();
    qw.orderByAsc("name", "age"); // 指定按什么字段升序排序

    // SELECT id,name,age,email,status FROM student ORDER BY name ASC,age ASC
    List<Student> students = studentDao.selectList(qw);
    students.forEach(stu -> System.out.println(stu));
}

@Test
public void testOrderByDesc() {
    QueryWrapper<Student> qw = new QueryWrapper<>();
    qw.orderByDesc("name", "age"); // 指定按什么字段降序排序

    // SELECT id,name,age,email,status FROM student ORDER BY name DESC,age DESC
    List<Student> students = studentDao.selectList(qw);
    students.forEach(stu -> System.out.println(stu));
}

@Test
public void testOrderBy() {
    QueryWrapper<Student> qw = new QueryWrapper<>();

    // qw.orderBy(true, true, "name"); // ORDER BY name ASC 升序

    // qw.orderBy(true, false, "name"); // ORDER BY name DESC 降序

    // SELECT id,name,age,email,status FROM student 即不启用条件,使用默认排序方式
    // qw.orderBy(false, false, "name");

    // 多字段混合排序 (依次按照name、age、email进行排序)
    // ORDER BY name ASC,age DESC,email DESC
    qw.orderBy(true, true, "name")
        .orderBy(true, false, "age")
        .orderBy(true, false, "email");

    List<Student> students = studentDao.selectList(qw);
    students.forEach(stu -> System.out.println(stu));
}

17. or、and

or:连接条件用 or(条件满足一个即可)

and:连接条件用 and(需要所有条件都满足)

@Test
public void testOr() {
    QueryWrapper<Student> qw = new QueryWrapper<>();

    // 使用or连接多个条件(不使用or,则默认是and)
    qw.eq("name", "luis")
        .or()
        .eq("age", 22);

    // WHERE (name = ? OR age = ?)
    List<Student> students = studentDao.selectList(qw);
    students.forEach(stu -> System.out.println(stu));
}

@Test
public void testAnd() {
    QueryWrapper<Student> qw = new QueryWrapper<>();

    // 连接多个条件,默认是and
    qw.eq("name", "luis")
        .eq("age", 22);

    // WHERE (name = ? AND age = ?)
    List<Student> students = studentDao.selectList(qw);
    students.forEach(stu -> System.out.println(stu));
}

18. last

拼接 SQL 语句到 MP 的SQL 语句的最后

@Test
public void testLast() {
    QueryWrapper<Student> qw = new QueryWrapper<>();

    qw.eq("name", "luis")
        .or()
        .eq("age", 22)
        .last("limit 1"); // 在最后部分拼接语句

    // WHERE (name = ? OR age = ?) limit 1
    List<Student> students = studentDao.selectList(qw);
    students.forEach(stu -> System.out.println(stu));
}

19. exists、notExists

常用在子查询之中,作为判断条件

exists:拼接Exists(sql语句)

notExists:是 exists 的相反操作

@Test
public void testExists() {
    QueryWrapper<Student> qw = new QueryWrapper<>();

    qw.exists("select id from student where age > 20");
    // SELECT id,name,age,email,status FROM student WHERE (EXISTS (select id from student where age > 20))
    List<Student> students = studentDao.selectList(qw);
    students.forEach(stu -> System.out.println(stu));
}

@Test
public void testNotExists() {
    QueryWrapper<Student> qw = new QueryWrapper<>();

    qw.notExists("select id from student where age > 90");
    // SELECT id,name,age,email,status FROM student WHERE (NOT EXISTS (select id from student where age > 90))
    List<Student> students = studentDao.selectList(qw);
    students.forEach(stu -> System.out.println(stu));
}

3. 分页

前提:在配置类中配置分页插件,实现物理分页。(默认是内存分页)

PS:更详细的分页插件配置自行百度

@Configuration // 作为配置类使用
public class Config {

    // 配置分页插件(新版和旧版配置有区别,注意区分)
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 数据库类型是MySQL
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

分页查询测试:

/**
     * 分页操作步骤:
     * 1.统计记录数
     *  SELECT COUNT(*) AS total FROM student WHERE (age > ?)
     * 2.实现分页(分页插件),在SQL的末尾加上limit语句
     *  SELECT id,name,age,email,status FROM student WHERE (age > ?) LIMIT ?
     *
     * 查询结果示例:
     * students.size = 3
     * 总页数:2
     * 总记录数:4
     * 当前页:1
     * 每页记录数:3
     */
@Test
public void testPage() {

    // 创建查询条件封装类
    QueryWrapper<Student> qw = new QueryWrapper<>();
    qw.gt("age", 22); // 组合查询条件 age>22

    IPage<Student> page = new Page<>();
    // 设置分页数据
    page.setCurrent(1); // 设置当前为第1页
    page.setSize(3); // 设置每页的记录数为3

    IPage<Student> result = studentDao.selectPage(page, qw);

    // 获取分页记录
    List<Student> students = result.getRecords();
    System.out.println("students.size = " + students.size());
    // 分页信息
    System.out.println("总页数:" + result.getPages());
    System.out.println("总记录数:" + result.getTotal());
    System.out.println("当前页:" + result.getCurrent());
    System.out.println("每页记录数:" + result.getSize());
}

九、MP 生成器

注意:下面仅提供其中一种方式,其他方式自行百度或参考官方文档

官方文档参考:https://baomidou.com/pages/779a6e/#快速入门

1. 添加依赖

<!-- MyBatis-Plus代码生成器 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.3</version>
</dependency>

<!-- 模板引擎依赖 Freemarker -->
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.31</version>
</dependency>

2. 新建并配置自动生成器类

示例:(下面代码也是官网文档复制修改的)

/**
 * @Author: Luis
 * @date: 2022/9/1 17:37
 * @description: 代码生成器(新版),快速生成
 */
public class AutoMapper {

    public static void main(String[] args) {

        // 获取当前工程在磁盘中的目录路径
        String path = System.getProperty("user.dir");
        // 数据源配置
        String url = "jdbc:mysql://127.0.0.1:3306/springdb?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8";
        String username = "root";
        String password = "luis";

        FastAutoGenerator.create(url, username, password)
            // 全局设置
            .globalConfig(builder -> {
                builder.author("luis") // 设置作者
                    // .enableSwagger() // 开启 swagger 模式
                    .fileOverride() // 覆盖已生成文件
                    .outputDir(path + "/plus/src/main/java"); // 指定输出目录
            })
            // 包设置
            .packageConfig(builder -> {
                builder.parent("com.luis") // 设置父包名
                    .moduleName("order") // 设置父包模块名
                    .pathInfo(Collections.singletonMap(OutputFile.xml, path + "/plus/src/main/java")); // 设置mapperXml生成路径
            })
            // 策略设置
            .strategyConfig(builder -> {
                builder.addInclude("dept", "student"); // 设置需要生成的表名
            })
            // 模板引擎设置
            .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
            .execute();
    }
}

3. 运行程序并生成

4. 主启动类上添加包扫描器

@MapperScan(value = "com.luis.order.mapper") //包扫描器,指定Mapper类所在的包
public class PlusApplication {

    public static void main(String[] args) {
        SpringApplication.run(PlusApplication.class, args);
    }
}

5. 新建测试类,测试

@SpringBootTest
@SuppressWarnings("all")
public class AutoMapperTests {

    @Autowired
    private StudentMapper studentMapper;

    @Test
    public void test01() {

        Student student = new Student();
        student.setName("Dad");
        student.setAge(49);
        int row = studentMapper.insert(student);
        System.out.println("========> " + row);
    }

    @Test
    public void test02() {

        Student stu = studentMapper.selectById(9);
        System.out.println(stu);
    }

    @Test
    public void test03() {

        QueryWrapper<Student> qw = new QueryWrapper<>();
        qw.eq("age", 49);

        List<Student> students = studentMapper.selectList(qw);
        students.forEach(stu -> System.out.println(stu));
    }
}

十、MP 生成器(2)

1. 添加相关依赖

<!--mp mybatis-plus依赖-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.2.0</version>
</dependency>
<!--页面模板引擎-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!--mp代码生成器-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.2.0</version>
</dependency>

2. 编写配置文件

PS:需配置数据源和 mybatis plus 中 mapper 的 xml 文件扫描路径

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/vueblog?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root
    password: luis
mybatis-plus:
  mapper-locations: classpath*:/mapper/**Mapper.xml

3. 配置类中开启 mapper 接口扫描,添加分页插件

@Configuration //配置类
@EnableTransactionManagement //开启事务支持
@MapperScan("com.markerhub.mapper") //配置包扫描器
public class MybatisPlusConfig {

    //分页拦截器
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        return paginationInterceptor;
    }
}

4. 编写代码生成器类

PS:其实在官网复制后稍作修改的,主要需要修改数据源等相关配置即可

使用:运行程序,控制台输入需生成的表名即可(生成多个表,则多表之间用英文逗号隔开)

// 演示例子,执行 main 方法控制台输入模块表名回车自动生成对应项目目录中
public class CodeGenerator {

    /**
     * <p>
     * 读取控制台内容
     * </p>
     */
    public static String scanner(String tip) {
        Scanner scanner = new Scanner(System.in);
        StringBuilder help = new StringBuilder();
        help.append("请输入" + tip + ":");
        System.out.println(help.toString());
        if (scanner.hasNext()) {
            String ipt = scanner.next();
            if (StringUtils.isNotEmpty(ipt)) {
                return ipt;
            }
        }
        throw new MybatisPlusException("请输入正确的" + tip + "!");
    }

    public static void main(String[] args) {
        // 代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir(projectPath + "/src/main/java");
//        gc.setOutputDir("D:\\test");
        gc.setAuthor("luis");
        gc.setOpen(false);
        // gc.setSwagger2(true); 实体属性 Swagger2 注解
        gc.setServiceName("%sService");
        mpg.setGlobalConfig(gc);

        // 数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/vueblog?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=UTC");
        // dsc.setSchemaName("public");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("luis");
        mpg.setDataSource(dsc);

        // 包配置
        PackageConfig pc = new PackageConfig();
        pc.setModuleName(null);
        pc.setParent("com.markerhub");
        mpg.setPackageInfo(pc);

        // 自定义配置
        InjectionConfig cfg = new InjectionConfig() {
            @Override
            public void initMap() {
                // to do nothing
            }
        };

        // 如果模板引擎是 freemarker
        String templatePath = "/templates/mapper.xml.ftl";
        // 如果模板引擎是 velocity
        // String templatePath = "/templates/mapper.xml.vm";

        // 自定义输出配置
        List<FileOutConfig> focList = new ArrayList<>();
        // 自定义配置会被优先输出
        focList.add(new FileOutConfig(templatePath) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
                return projectPath + "/src/main/resources/mapper/"
                        + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
            }
        });

        cfg.setFileOutConfigList(focList);
        mpg.setCfg(cfg);

        // 配置模板
        TemplateConfig templateConfig = new TemplateConfig();

        templateConfig.setXml(null);
        mpg.setTemplate(templateConfig);

        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        strategy.setEntityLombokModel(true);
        strategy.setRestControllerStyle(true);
        strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
        strategy.setControllerMappingHyphenStyle(true);
        strategy.setTablePrefix("m_");
        mpg.setStrategy(strategy);
        mpg.setTemplateEngine(new FreemarkerTemplateEngine());
        mpg.execute();
    }
}

5. 代码生成并测试

示例:

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    UserService userService;

    @GetMapping("/index")
    public Object index() {
        return userService.getById(1L);
    }
}
posted @ 2022-11-01 23:37  luis林  阅读(226)  评论(0编辑  收藏  举报