简介

MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。


特性

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
  • 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
  • 内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

快速开始

通过以下几个简单的步骤,我们就可以实现 User 表的 CRUD 功能,甚至连 XML 文件都不用编写!

  1. 创建数据表
  2. 添加依赖
  3. 配置数据源(DataSource)
  4. 在启动类添加 @MapperScan 注解
  5. 编写实体类(类名一定要与表名一致)
  6. 编写接口继承 BaseMapper 类
  7. 编写测试类进行测试

创建数据表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
DROP TABLE if EXISTS user;

CREATE TABLE user
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id)
);

DELETE FROM user;

INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');

添加依赖

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

配置数据源

1
2
3
4
5
6
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/daily
username: daily
password: daily

在启动类中添加 MapperScan 注解,扫描 mapper 文件夹

1
2
3
4
5
6
7
8
9
@SpringBootApplication
@MapperScan("com.pushihao.mapper")
public class Springboot06MybatisplusApplication {

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

}

编写实体类(此处使用了lombok)

注意:数据库的表名一定要和实体类的类名保持一致

1
2
3
4
5
6
7
8
9
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}

编写 mapper 接口继承 BaseMapper 类

提示:此处即使不写 @Repository 注解也能正确运行,但在编写代码时 IDE 会报错

1
2
3
@Repository
public interface UserMapper extends BaseMapper<User> {
}

编写测试类测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@SpringBootTest
class Springboot06MybatisplusApplicationTests {

@Autowired
private UserMapper userMapper;

@Test
public void testSelect() {
System.out.println("-----selectAll method test-----");
List<User> userList = userMapper.selectList(null);
userList.forEach(System.out::println);
}

}

常用注解


@TableName

用在实体类上,标识实体类对应的表

1
2
3
4
5
6
7
@TableName("user")
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}

@Tabled

用在实体类的某个属性上,表名该属性为实体类的主键字段

1
2
3
4
5
6
7
8
@TableName("user")
public class User {
@TableId
private Long id;
private String name;
private Integer age;
private String email;
}


常用 CRUD 接口

接口中很大一部分用到了条件构造器,在第五部分


Mapper CRUD 接口

说明:此接口为 dao/mapper 层的接口,创建自己的接口基层 Mybatis-Plus 提供的 BaseMapper 接口即可

实现

UserMapper.java

1
2
3
@Repository("userMapper")
public interface UserMapper extends BaseMapper<User> {
}

测试使用

1
2
3
4
5
6
7
8
9
10
11
@Autowired
@Qualifier("userMapper")
//继承了baseMapper类
private UserMapper userMapper;

@Test
public void testSelect() {
System.out.println("-----selectAll method test-----");
List<User> userList = userMapper.selectList(null);
userList.forEach(System.out::println);
}

常用 API


Insert
1
2
// 插入一条记录
int insert(T entity);

Delete
1
2
3
4
5
6
7
8
// 根据 entity 条件,删除记录
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
// 删除(根据ID 批量删除)
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 ID 删除
int deleteById(Serializable id);
// 根据 columnMap 条件,删除记录
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

Update
1
2
3
4
// 根据 whereWrapper 条件,更新记录
int update(@Param(Constants.ENTITY) T updateEntity, @Param(Constants.WRAPPER) Wrapper<T> whereWrapper);
// 根据 ID 修改
int updateById(@Param(Constants.ENTITY) T entity);

Select
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 根据 ID 查询
T selectById(Serializable id);
// 根据 entity 条件,查询一条记录
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

// 查询(根据ID 批量查询)
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 entity 条件,查询全部记录
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 查询(根据 columnMap 条件)
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
// 根据 Wrapper 条件,查询全部记录
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

// 根据 entity 条件,查询全部记录(并翻页)
IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录(并翻页)
IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询总记录数
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

四种操作:增删改查


Service CRUD 接口

说明:此接口为 Service 层的接口,创建自己的接口继承 Mybatis-Plus 提供的 IService 接口,并需要实现类

实现

UserService.java

1
2
3
4
5
6
7
package com.pushihao.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.pushihao.pojo.User;

public interface UserService extends IService<User> {
}

UserServiceImpl.java

1
2
3
4
5
6
7
8
9
10
package com.pushihao.service;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.pushihao.mapper.UserMapper;
import com.pushihao.pojo.User;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

测试使用

1
2
3
4
5
6
7
8
9
10
@Autowired
//继承了IService类
private UserService userService;

@Test
public void testIServiceMethod() {
System.out.println("-----IService selectAll method test-----");
List<User> userList = userService.list();
userList.forEach(System.out::println);
}


常用 API


Save
1
2
3
4
5
6
// 插入一条记录(选择字段,策略插入)
boolean save(T entity);
// 插入(批量)
boolean saveBatch(Collection<T> entityList);
// 插入(批量)
boolean saveBatch(Collection<T> entityList, int batchSize);

其中,batchSize 指插入批次数量,也就是一次插入多少条数据

演示

插入数据为2条,saveBatch为1

1
2
3
4
User user2 = new User(7L, "张三", 26, "zhangsan@qq.com");
User user3 = new User(8L, "李四", 27, "lisi@qq.com");
List<User> users = Arrays.asList(user2, user3);
userService.saveBatch(users, 1);
1
2
3
4
==>  Preparing: INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
==> Parameters: 7(Long), 张三(String), 26(Integer), zhangsan@qq.com(String)
==> Preparing: INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
==> Parameters: 8(Long), 李四(String), 27(Integer), lisi@qq.com(String)

插入数据为2条,saveBatch为2

1
2
3
==>  Preparing: INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
==> Parameters: 7(Long), 张三(String), 26(Integer), zhangsan@qq.com(String)
==> Parameters: 8(Long), 李四(String), 27(Integer), lisi@qq.com(String)

SaveOrUpdate
1
2
3
4
5
6
7
8
// TableId 注解存在更新记录,否插入一条记录
boolean saveOrUpdate(T entity);
// 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法
boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);

存在就是修改,不存在就是插入


Remove
1
2
3
4
5
6
7
8
// 根据 entity 条件,删除记录
boolean remove(Wrapper<T> queryWrapper);
// 根据 ID 删除
boolean removeById(Serializable id);
// 根据 columnMap 条件,删除记录
boolean removeByMap(Map<String, Object> columnMap);
// 删除(根据ID 批量删除)
boolean removeByIds(Collection<?> extends Serializable> idList);

MyBatisPlus提供了较多的重载方法:

image-20220328105417211


Update
1
2
3
4
5
6
7
8
9
10
// 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
boolean update(Wrapper<T> updateWrapper);
// 根据 whereWrapper 条件,更新记录
boolean update(T updateEntity, Wrapper<T> whereWrapper);
// 根据 ID 选择修改
boolean updateById(T entity);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList, int batchSize);

Get
1
2
3
4
5
6
7
8
9
10
// 根据 ID 查询
T getById(Serializable id);
// 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")
T getOne(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
T getOne(Wrapper<T> queryWrapper, boolean throwEx);
// 根据 Wrapper,查询一条记录
Map<String, Object> getMap(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
<V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);

List
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 查询所有
List<T> list();
// 查询列表
List<T> list(Wrapper<T> queryWrapper);
// 查询(根据ID 批量查询)
Collection<T> listByIds(Collection<? extends Serializable> idList);
// 查询(根据 columnMap 条件)
Collection<T> listByMap(Map<String, Object> columnMap);
// 查询所有列表
List<Map<String, Object>> listMaps();
// 查询列表
List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);
// 查询全部记录
List<Object> listObjs();
// 查询全部记录
<V> List<V> listObjs(Function<? super Object, V> mapper);
// 根据 Wrapper 条件,查询全部记录
List<Object> listObjs(Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录
<V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);

Page
1
2
3
4
5
6
7
8
// 无条件分页查询
IPage<T> page(IPage<T> page);
// 条件分页查询
IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);
// 无条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page);
// 条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page, Wrapper<T> queryWrapper);

注意:

Ipage是一个接口,继承关系为:

image-20220328111400806

service 中 page 的创建方式由多种:

image-20220328111633285

page 中可以获得很多属性:

image-20220328111920846

演示使用:

1
2
3
4
5
@Test
public void testIPage() {
IPage<User> page = userService.page(new Page<>(1, 2), null);
page.getRecords().forEach(System.out::println);
}

Count
1
2
3
4
// 查询总记录数
int count();
// 根据 Wrapper 条件,查询总记录数
int count(Wrapper<T> queryWrapper);

链式查询

类似于 Stream 流式操作



条件构造器

条件构造器层级关系如下图:

image-20220326174459777




插件使用

所有的插件都实现了此接口:InnerInterceptor

目前已有的功能:

  • 自动分页: PaginationInnerInterceptor
  • 多租户: TenantLineInnerInterceptor
  • 动态表名: DynamicTableNameInnerInterceptor
  • 乐观锁: OptimisticLockerInnerInterceptor
  • sql 性能规范: IllegalSQLInnerInterceptor
  • 防止全表更新与删除: BlockAttackInnerInterceptor

配置(以分页插件为例):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//配置类
@Configuration
@MapperScan("com.pushihao.mapper")
public class MyBatisPlusConfig {

//配置mybatis-plus插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {

//该插件是核心插件,在此基础上可以添加其他插件
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

//创建分页插件,参数为数据库类型
InnerInterceptor pagePlugin = new PaginationInnerInterceptor(DbType.MYSQL);

//将分页插件添加到核心插件中
interceptor.addInnerInterceptor(pagePlugin);

return interceptor;
}
}

分页插件的使用

UserMapper.java

1
2
3
4
5
6
7
8
9
10
11
12
@Repository("userMapper")
public interface UserMapper extends BaseMapper<User> {

/**
* 通过年龄查询用户信息并进行分页操作
* @param page MyBatis-Plus提供的分页对象,必须位于参数的第一个位置
* @param age 筛选条件
* @return Page对象
*/
Page<User> selectPageByAge(@Param("page")Page<User> page, @Param("age")Integer age);

}

UserMapper.xml

1
2
3
4
5
6
7
8
<mapper namespace="com.pushihao.mapper.UserMapper">

<!--通过年龄查询用户信息并进行分页操作-->
<select id="selectPageByAge" resultType="User">
select * from user where age>#{age}
</select>

</mapper>

pluginTestController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RestController
@RequestMapping("/plugin")
public class pluginTestController {
@Autowired
private UserMapper userMapper;

//测试MyBatis-Plus提供的BaseMapper中的方法使用分页插件
@RequestMapping("/page")
public List<User> page() {
Page<User> userPage = new Page<>(1, 2);
userMapper.selectPage(userPage, null);
return userPage.getRecords();
}

//测试自定义的Sql语句使用分页插件
@RequestMapping("/page2")
public List<User> page2() {
Page<User> userPage = new Page<>(1, 2);
userMapper.selectPageByAge(userPage, 20);
return userPage.getRecords();
}
}


注意:

使用多个功能需要注意顺序关系,建议使用如下顺序

  • 多租户,动态表名
  • 分页,乐观锁
  • sql 性能规范,防止全表更新与删除

总结: 对 sql 进行单次改造的优先放入,不对 sql 进行改造的最后放入