MyBatis-Plus
前言
数据库访问的发展
- 最初始,在 Java 项目中直接使用 jdbc 来访问数据库,创建
Connection
、ResultSet
等; - 后来,对 jdbc 的操作进行了封装,创建了很多的工具类,如 DBUtil;
- 再后面,就使用一些持久层的框架:
- hibernate:全自动的 ORM 框架,实现 Java 对象到表的映射,可以通过 Java 对象的方法,操作表中的数据,开发人员可以不了解或少了解 SQl 语言;
- jpa 规范:定义了访问数据库的各种操作,定义了一致的方法操作数据库;jpa 有各种实现,如 hibernate、open-jpa、link 等;
- mybatis:需要编写 xml 配置文件,在 xml 文件中编写 SQL 语句,访问数据库;任何的操作都需要使用 xml 文件,对开发人员要求比较高,需要熟悉 SQL 语言,单表的 CRUD 也需要 xml 文件,编写 SQL 语句;
- 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 也封装了部分业务逻辑,可以作为业务对象使用。
环境准备
-
准备一张数据库表
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;
-
创建实体类,继承 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 + '}'; } }
-
定义 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> { }
-
创建测试类,进行测试
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);
}
}
都看到最后了,右下角来个赞鸭!-.- 欢迎评论留言~