1. MyBatis-Plus 介绍
MyBatis-Plus (简称MP) 是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
特性:
- 润物无声:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑。
- 效率至上:只需简单配置,即可快速进行单表 CRUD 操作,从而节省大量时间。
- 丰富功能:代码生成、自动分页、逻辑删除、自动填充、拦截器等功能一应俱全。
- 广泛认可:连续5年获得开源中国年度最佳开源项目殊荣,Github 累计 16K Star。
支持数据库:
PostgreSQL, MySQL, MariaDB, Oracle, SQL Server, OceanBase, H2, DB2... (任何能使用 MyBatis 进行增删改查,并且支持标准 SQL 的数据库应该都在 MyBatis-Plus 的支持范围内)
2. 使用
2.1 准备工作
2.1.1 数据准备
创建用户表,并创建对应的实体类 User
-- 创建数据库
DROP DATABASE IF EXISTS mybatis_test;
CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4;
-- 使用数据数据
USE mybatis_test;
-- 创建表[用户表]
DROP TABLE IF EXISTS user_info;
CREATE TABLE `user_info` (
`id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
`username` VARCHAR ( 127 ) NOT NULL,
`password` VARCHAR ( 127 ) NOT NULL,
`age` TINYINT ( 4 ) NOT NULL,
`gender` TINYINT ( 4 ) DEFAULT '0' COMMENT '1-男 2-女 0-默认',
`phone` VARCHAR ( 15 ) DEFAULT NULL,
`delete_flag` TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除',
`create_time` DATETIME DEFAULT now(),
`update_time` DATETIME DEFAULT now() ON UPDATE now(),
PRIMARY KEY ( `id` )
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;
-- 添加用户信息
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'admin', 'admin', 18, 1, '18612340001' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'zhangsan', 'zhangsan', 18, 1, '18612340002' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'lisi', 'lisi', 18, 1, '18612340003' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'wangwu', 'wangwu', 18, 1, '18612340004' );
2.1.2 项目准备
创建 SpringBoot 工程
添加 MyBatis-Plus 和 MySQL 依赖,配置数据库连接信息
Spring Boot 2.x 版本
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.9</version>
</dependency>
Spring Boot 3.x 版本
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.9</version>
</dependency>
MySQL 驱动
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
配置数据库
application.yml 文件
# 数据库连接配置
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false
username: root
password: "111111"
driver-class-name: com.mysql.cj.jdbc.Driver
或者 application.properties文件
# 驱动类名称
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据库连接的 url
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mybatis_testcharacterEncoding=utf8&useSSL=false
# 连接数据库的⽤⼾名
spring.datasource.username=root
# 连接数据库的密码
spring.datasource.password=root
2.2 编码
创建实体类 UserInfo
实体类的属性名与表中的字段名一一对应
import lombok.Data;
import java.util.Date;
@Data
public class UserInfo {
private Integer id;
private String username;
private String password;
private Integer age;
private Integer gender;
private String phone;
private Integer deleteFlag;
private Date createTime;
private Date updateTime;
}
Mapper 接口类
MybatisPlus 提供了一个基础的 BaseMapper 接口,已经实现了单表的 CRUD,我们自定义 Mapper只需要继承这个 BaseMapper,就无需自己实现单表 CRUD 了。
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mybatisplusdemo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserInfoMapper extends BaseMapper<UserInfo> {
}
也可以在启动类上添加 @MapperScan,扫描 Mapper 文件夹,二选一即可
2.3 CRUD 单元测试
使用 Generate 生成 test 单元测试
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
// 查询
@Test
void testSelect() {
System.out.println(userInfoMapper.selectById(1));
}
}
运行结果:
3. MyBatis-Plus 复杂操作
3.1 常见注解
在上面的程序中,MyBatis 是如何知道我们要操作的是哪张表,表里有哪些字段呢?
看以下 Mapper 代码
@Mapper
public interface UserInfoMapper extends BaseMapper<UserInfo> {
}
UserInfoMapper 在继承 BaseMapper 时,指定了一个泛型,这个 UserInfo 就是与数据库表相对应的实体类。
MyBatis - Plus 会根据这个实体类来推断表的信息。
默认情况下:
1. 表名:实体类的驼峰表示法转换成蛇形表示法(下划线分割),作为表名。比如 UserInfo -> user_info
2. 字段:根据实体类的属性名 转换为蛇形表示法作为字段名。比如 deleteFlag -> delete_flag
3. 主键:默认为 id
那如果实体类和数据库不是按照上述规则定义的呢?MyBatis - Plus 也给我们提供了一下注解,让我们标识表的信息。
3.1.1 @TableName
修改实体类名 UserInfo 为 Userinfo,重新执行测试方法
运行结果
从日志可以看到默认查找的表名为 userinfo
我们可以通过 @TableName 来标识实体类对应的表
@Data
@TableName("user_info")
public class Userinfo {
private Integer id;
private String username;
private String password;
private Integer age;
private Integer gender;
private String phone;
private Integer deleteFlag;
private Date createTime;
private Date updateTime;
}
再次运行程序,结果正常
3.1.2 @TableField
修改属性名 deleteFlag 为 deleteflag,重新执行测试方法
运行结果:
从日志可以看到,根据属性名转换后的字段名为 deleteflag
我们可以通过 @TableField 来标识对应的字段名
@Data
@TableName("user_info")
public class Userinfo {
private Integer id;
private String username;
private String password;
private Integer age;
private Integer gender;
private String phone;
@TableField("delete_flag")
private Integer deleteflag;
private Date createTime;
private Date updateTime;
}
再次运行程序,结果正常
3.1.3 @Tableld
在 test 中写一个新增操作:
// 新增
@Test
void testInsert() {
Userinfo userinfo = new Userinfo();
userinfo.setUsername("zhaoliu");
userinfo.setPassword("11111");
userInfoMapper.insert(userinfo);
}
运行之后:
这是因为它不知道 id 是自增的,需要在 id 上加上注解 @TableId(type = IdType.AUTO)
还需修改计数器:
修改属性名 id 为 userId,重新执行测试方法
运行结果:
通过 @TableId 来指定对应的主键
@Data
@TableName("user_info")
public class Userinfo {
@TableId(value = "id", type = IdType.AUTO)
private Integer userId;
private String username;
private String password;
private Integer age;
private Integer gender;
private String phone;
@TableField("delete_flag")
private Integer deleteflag;
private Date createTime;
private Date updateTime;
}
如果属性名和字段名不一致,需要在 @TableId 指明对应的字段名。
属性名和字段一致的情况下,直接加 @TableId 注解就可以。
再次运行程序,结果正常
tip:修改和删除测试
// 改
@Test
void testUpdate() {
Userinfo userinfo = new Userinfo();
userinfo.setUserId(5);
userinfo.setPassword("1234");
userInfoMapper.insert(userinfo);
}
// 删除
@Test
void testDelete() {
userInfoMapper.deleteById(5);
}
3.2 打印日志
为了方便观察,我们可以借助日志,查看 Mybatis - Plus 执行的 SQL 语句,参数和执行结果 Mybatis - Plus 配置日志如下:
mybatis-plus:
configuration: # 配置打印 MyBatis日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
3.3 条件构造器
上面程序里的使用,都是简单的 CRUD,在实际的应用场景中,我们还需要使用更复杂的操作,MyBatis - Plus 也给我们提供了相应的支持。
MyBatis - Plus 提供了一套强大的条件构造器 (Wrapper),用于构建复杂的数据库查询条件。Wrapper 类允许开发者以链式调用的方式构造查询条件,无需编写繁琐的 SQL 语句,从而提高开发效率并减少 SQL 注入的风险。
以下是主要的 Wrapper 类及其功能:
- AbstractWrapper:这是一个抽象基类,提供了所有 Wrapper 类共有的方法和属性。详细参考官网介绍:条件构造器
- QueryWrapper:用于构造查询条件,在 AbstractWrapper 的基础上拓展了一个 select 方法,允许指定查询字段。
- UpdateWrapper:用于构造更新条件,可以在更新数据时指定条件。
- LambdaQueryWrapper:基于 Lambda 表达式的查询条件构造器,它通过 Lambda 表达式来引用实体类的属性,从而避免了硬编码字段名。
- LambdaUpdateWrapper:基于 Lambda 表达式的更新条件构造器,它允许你使用 Lambda 表达式来指定更新字段和条件,同样避免了硬编码字段名的问题。
3.3.1 QueryWrapper
QueryWrapper 并不只用于查询语句,无论是修改、删除、查询,都可以使用 QueryWrapper 来构建查询条件
查询
完成下述 SQL 查询
SELECT id,username,password,age FROM user_info WHERE age = 18 AND username like "%min%"
测试代码
@Test
void testQueryWrapper() {
QueryWrapper<Userinfo> queryWrapper = new QueryWrapper<>();
queryWrapper.select("id", "username", "password", "age")
.eq("age", 18)
.like("username", "min");
userInfoMapper.selectList(queryWrapper).forEach(System.out::println); //返回值是一个 List,使用 lambda 表达式打印出来
}
更新
完成下述 SQL 查询
UPDATE user_info SET delete_flag=? WHERE age < 20
测试代码
@Test
void testQueryWrapper2() {
QueryWrapper<Userinfo> queryWrapper = new QueryWrapper<>();
queryWrapper.lt("age", 20);
Userinfo userinfo = new Userinfo();
userinfo.setDeleteflag(1);
userInfoMapper.update(userinfo, queryWrapper);
}
这样写还需要创建一个类,很麻烦,可以直接使用 UpdateWrapper 来实现
@Test
void testUpdateWrapper() {
UpdateWrapper<Userinfo> updateWrapper = new UpdateWrapper<>();
updateWrapper.set("delete_flag", 2)
.lt("age", 20);
userInfoMapper.update(updateWrapper);
}
lt : "less than"的缩写,表示小于。
le : "less than or equal to"的缩写,表示小于等于。
ge : "greater than or equal to"的缩写,表示大于等于。
gt : "greater than"的缩写,表示大于。
eq : "equals"的缩写,表示等于。
ne : "not equals"的缩写,表示不等于。
删除
完成下述 SQL 查询
DELETE FROM user_info WHERE age = 18
测试代码
@Test
void testQueryWrapper3() {
QueryWrapper<Userinfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("age", 18);
userInfoMapper.delete(queryWrapper);
}
3.3.2 UpdateWrapper
对于更新,可以直接使用 UpdateWrapper,在不创建实体对象的情况下,直接设置更新字段和条件
基础更新:
完成下述 SQL 查询
UPDATE user_info SET delete_flag=0, age=5 WHERE id IN (1,2,3)
测试代码
@Test
void testUpdateWrapper2() {
UpdateWrapper<Userinfo> updateWrapper = new UpdateWrapper<>();
updateWrapper.set("delete_flag", 0)
.set("age", 5)
.in("id", List.of(1, 2, 3));
userInfoMapper.update(updateWrapper);
}
基于 SQL 更新:
完成下述 SQL 查询
UPDATE user_info SET age = age+10 WHERE id IN (1,2,3)
测试代码
@Test
void testUpdateWrapper3() {
UpdateWrapper<Userinfo> updateWrapper = new UpdateWrapper<>();
updateWrapper.setSql("age = age + 10")
.in("id", List.of(1, 2, 3));
userInfoMapper.update(updateWrapper);
}
3.3.3 LambdaQueryWrapper
QueryWrapper 和 UpdateWrapper 存在一个问题,就是需要写死字段名,如果字段名发生变更,可能会因为测试不到位酿成事故。
MyBatis - Plus 给我们提供了一种基于 Lambda 表达式的条件构造器,它通过 Lambda 表达式来引用实体类的属性,从而避免了硬编码字段名,也提高了代码的可读性和可维护性。
- LambdaQueryWrapper
- LambdaUpdateWrapper
分别对应上述的 QueryWrapper 和 UpdateWrapper。
具体使用:
@Test
void testLambdaQueryWrapper() {
QueryWrapper<Userinfo> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda()
.select(Userinfo::getUserId, Userinfo::getUsername, Userinfo::getPassword)
.eq(Userinfo::getAge, 18)
.like(Userinfo::getUsername, "min");
userInfoMapper.selectList(queryWrapper).forEach(System.out::println);
}
3.3.4 LambdaUpdateWrapper
@Test
void testLambdaWrapper() {
UpdateWrapper<Userinfo> updateWrapper = new UpdateWrapper<>();
// 普通版
// updateWrapper.setSql("age = age + 10")
// .in("id", List.of(1, 2, 3));
// lambda 版
updateWrapper.lambda()
.set(Userinfo::getDeleteflag, 0)
.set(Userinfo::getAge, 6)
.in(Userinfo::getUserId, List.of(1, 2, 3));
userInfoMapper.update(updateWrapper);
}
3.4 自定义 SQL
在实际的开发中,有时候 MyBatis - Plus 提供的操作依然不能满足我们的实际需求,因此 MyBatis - Plus 也提供了自定义 SQL 的功能,我们可以利用 Wrapper 构造查询条件,再结合 Mapper 编写SQL
为了使用这一功能,MyBatis-Plus 版本不能低于 3.0.7
代码示例 1
完成下述 SQL 查询
select id,username,password,age FROM user_info WHERE username = "admin"
Mapper
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.example.mybatisplusdemo.model.Userinfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface UserInfoMapper extends BaseMapper<Userinfo> {
// @Select("select id,username,password,age FROM user_info WHERE username = #{name}")
@Select("select id,username,password,age FROM user_info ${ew.customSqlSegment}")
List<Userinfo> selectByCustom(@Param(Constants.WRAPPER)Wrapper<Userinfo> wrapper);
}
测试代码
@Test
void selectByCustom() {
// userInfoMapper.selectByCustom("admin").forEach(System.out::println);
QueryWrapper<Userinfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username", "admin");
userInfoMapper.selectByCustom(queryWrapper).forEach(System.out::println);
}
注意事项:
- 参数命名:在自定义 SQL 时,传递 Wrapper 对象作为参数时,参数名必须为 ew ,或者使用注解 @Param(Constants.WRAPPER) 明确指定参数为 Wrapper 对象。(注意:此处的 Constanta 和 Wrapper 都是 import com.baomidou.mybatisplus.core.* 下面的)
- 使用 ${ew.customSqlSegment} :在 SQL 语句中,使用 ${ew.customSqlSegment} 来引用 Wrapper 对象生成的 SQL 片段。
- 不支持基于 entity 的 where 语句:自定义 SQL 时,Wrapper 对象不会基于实体类自动生成 where子句,你需要手动编写完整的 SQL 语句。
代码示例 2
MyBatis-Plus 在 MyBatis 的基础上只做增强,不做改变,所以也支持 XML 的实现方式
示例 1 中功能通过 XML 方式实现:
配置 mapper 路径
mybatis-plus:
mapper-locations: "classpath*:/mapper/**.xml" # Mapper.xml
定义方法
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.example.mybatisplusdemo.model.Userinfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface UserInfoMapper extends BaseMapper<Userinfo> {
List<Userinfo> selectByCustom2(@Param(Constants.WRAPPER)Wrapper<Userinfo> wrapper);
}
编写 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.example.mybatisplusdemo.mapper.UserInfoMapper">
<select id="selectByCustom2" resultType="com.example.mybatisplusdemo.model.Userinfo">
select id,username,password,age FROM user_info ${ew.customSqlSegment}
</select>
</mapper>
测试
@Test
void selectByCustom2() {
// userInfoMapper.selectByCustom("admin").forEach(System.out::println);
QueryWrapper<Userinfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username", "admin");
userInfoMapper.selectByCustom2(queryWrapper).forEach(System.out::println);
}
代码示例 3
完成下述 SQL 查询
UPDATE user_info SET age = age+10 WHERE id IN (1,2,3)
Mapper
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.example.mybatisplusdemo.model.Userinfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List;
@Mapper
public interface UserInfoMapper extends BaseMapper<Userinfo> {
@Update("update user_info set age = age + #{age} ${ew.customSqlSegment}")
Integer updateByCustom(@Param("age")Integer age, @Param(Constants.WRAPPER) Wrapper wrapper);
}
测试代码
@Test
void updateByCustom() {
QueryWrapper<Userinfo> queryWrapper = new QueryWrapper<>();
queryWrapper.in("id", List.of(1, 2, 3));
userInfoMapper.updateByCustom(10, queryWrapper);
}