MyBatis-Plus


MyBatis-Plus 基础

简介

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

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑。

  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作。

  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求。

  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错。

  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题。

  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作。

  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用。

  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询。

  • 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库。

  • 内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询。

  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作。


入门

创建数据库和表

DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user`  (
  `user_id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `username` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名',
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码',
  `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '0-正常,1-删除',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
  `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建者',
  `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新人',
  `version` int NULL DEFAULT NULL COMMENT '乐观锁',
  PRIMARY KEY (`user_id`) USING BTREE,
  INDEX `user_idx1_username`(`username`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 'admin', '123456', '0', '2021-08-29 00:38:43', '2021-08-29 00:38:45', 'admin', 'admin', 1);
INSERT INTO `sys_user` VALUES (2, 'loner', '123456', '0', '2021-08-29 00:38:43', '2021-08-29 00:38:45', 'admin', 'admin', 1);
INSERT INTO `sys_user` VALUES (3, 'Bob', '123456', '0', '2021-08-29 00:38:43', '2021-08-29 00:38:45', 'admin', 'admin', 1);
INSERT INTO `sys_user` VALUES (4, 'Jack', '123456', '0', '2021-08-29 00:38:43', '2021-08-29 00:38:45', 'admin', 'admin', 1);

导入相关依赖


    org.springframework.boot
    spring-boot-starter-parent
    2.5.3



    
        org.springframework.boot
        spring-boot-starter
    
    
        org.springframework.boot
        spring-boot-starter-test
        test
    
    
    
        com.baomidou
        mybatis-plus-boot-starter
        3.4.3
    
    
        com.baomidou
        mybatis-plus-extension
        3.4.3
    
    
    
        com.alibaba
        druid-spring-boot-starter
        1.2.6
    
    
    
        mysql
        mysql-connector-java
    
    
    
        org.projectlombok
        lombok
    

编写配置文件

# 数据源配置
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.1.11:3306/mybatis-plus?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true
    username: root
    password: MaH00...
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true

使用和测试

// 创建用户实体表
@Data
@TableName("sys_user")
public class User implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 用户ID
     */
    @TableId(value = "user_id", type = IdType.AUTO)
    private Long userId;

    /**
     * 用户账号
     */
    private String username;

    /**
     * 密码
     */
    private String password;

    /**
     * 删除标志(0代表存在 2代表删除)
     */
    @TableLogic
    private String delFlag;

    /**
     * 创建者
     */
    @TableField(fill = FieldFill.INSERT)
    private String createBy;

    /**
     * 创建时间
     */
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;

    /**
     * 更新者
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String updateBy;

    /**
     * 更新时间
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;

    /**
     * 乐观锁
     */
    private Integer version;

}


// 继承BaseMapper接口
public interface UserMapper extends BaseMapper {
}

// 编写测试代码
@SpringBootTest
public class UserTest {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void test() {
        // 直接使用BaseMapper定义好的查询方法
        List userList = userMapper.selectList(null);
        userList.forEach(System.out::println);
    }

}

整个MP(简称)的使用过程,就是如此的简单,当我们继承完 BaseMapper 接口后,我们就完成了 CRUD 操作,剩余的只是针对不同的业务进行封装使用罢了。


MyBatis-Plus 核心

CRUD 接口

Mapper CRUD 接口

通用 CRUD 封装 BaseMapper (opens new window)接口,为 Mybatis-Plus 启动时自动解析实体表关系映射转换为 Mybatis 内部对象注入容器,泛型 T 为任意实体对象,参数 Serializable 为任意类型主键,Mybatis-Plus 不推荐使用复合主键约定每一张表都有自己的唯一 id 主键。

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

// 根据 ID 删除
int deleteById(Serializable id);
// 根据 entity 条件,删除记录
int delete(@Param(Constants.WRAPPER) Wrapper wrapper);
// 删除(根据ID 批量删除)
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 columnMap 条件,删除记录
int deleteByMap(@Param(Constants.COLUMN_MAP) Map columnMap);

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

// 根据 ID 查询
T selectById(Serializable id);
// 根据 entity 条件,查询一条记录
T selectOne(@Param(Constants.WRAPPER) Wrapper queryWrapper);

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



Service CRUD 接口

通用 Service CRUD 封装 IService (opens new window)接口,进一步封装 CRUD,采用 get 查询单行,remove 删除 ,list 查询集合,page 分页,前缀命名方式区分 Mapper 层避免混淆,泛型 T 为任意实体对象。

建议如果存在自定义通用 Service 方法的可能,请创建自己的 IBaseService 继承 Mybatis-Plus 提供的基类
对象 Wrapper 为 条件构造器。

Save

// 插入一条记录(选择字段,策略插入)
boolean save(T entity);
// 插入(批量)
boolean saveBatch(Collection entityList);
// 插入(批量),bathSize指插入批次数量
boolean saveBatch(Collection entityList, int batchSize);

Remove

// 根据 entity 条件,删除记录
boolean remove(Wrapper queryWrapper);
// 根据 ID 删除
boolean removeById(Serializable id);
// 根据 columnMap 条件,删除记录
boolean removeByMap(Map columnMap);
// 删除(根据ID 批量删除)
boolean removeByIds(Collection<? extends Serializable> idList);

Update

// 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
boolean update(Wrapper updateWrapper);
// 根据 whereWrapper 条件,更新记录
boolean update(T updateEntity, Wrapper whereWrapper);
// 根据 ID 选择修改
boolean updateById(T entity);
// 根据ID 批量更新
boolean updateBatchById(Collection entityList);
// 根据ID 批量更新
boolean updateBatchById(Collection entityList, int batchSize);

SaveOrUpdate

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

Get

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

List

// 查询所有
List list();
// 查询列表
List list(Wrapper queryWrapper);
// 查询(根据ID 批量查询)
Collection listByIds(Collection<? extends Serializable> idList);
// 查询(根据 columnMap 条件)
Collection listByMap(Map columnMap);
// 查询所有列表
List> listMaps();
// 查询列表
List> listMaps(Wrapper queryWrapper);
// 查询全部记录
List listObjs();
// 查询全部记录
 List listObjs(Function<? super Object, V> mapper);
// 根据 Wrapper 条件,查询全部记录
List listObjs(Wrapper queryWrapper);
// 根据 Wrapper 条件,查询全部记录
 List listObjs(Wrapper queryWrapper, Function<? super Object, V> mapper);

Page

// 无条件分页查询
IPage page(IPage page);
// 条件分页查询
IPage page(IPage page, Wrapper queryWrapper);
// 无条件分页查询
IPage> pageMaps(IPage page);
// 条件分页查询
IPage> pageMaps(IPage page, Wrapper queryWrapper);

条件构造器

基本条件

// 等于,例如 eq("name", "老王") 等价于 name = '老王'
eq(R column, Object val)
// 不等于,例如 ne("name", "老王") 等价于 name <> '老王'
ne(R column, Object val)

// 大于,例如 gt("age", 18) 等价于 age > 18
gt(R column, Object val)
// 小于,例如 lt("age", 18) 等价于 age < 18
lt(R column, Object val)

// 大于等于,例如 ge("age", 18) 等价于 age >= 18
ge(R column, Object val)
// 小于等于,例如 le("age", 18) 等价于 age <= 18
le(R column, Object val)

// BETWEEN 值1 AND 值2,例如 between("age", 18, 30) 等价于 age between 18 and 30
between(R column, Object val1, Object val2))
// NOT BETWEEN 值1 AND 值2,例如 notBetween("age", 18, 30) 等价于 age not between 18 and 30
notBetween(R column, Object val1, Object val2)

// LIKE '%值%',例如 like("name", "王") 等价于 name like '%王%'
like(R column, Object val)
// NOT LIKE '%值%',例如 notLike("name", "王") 等价于 name not like '%王%'
notLike(R column, Object val)
// LIKE '%值',例如 likeLeft("name", "王") 等价于 name like '%王'
likeLeft(R column, Object val)
// LIKE '值%',例如 likeRight("name", "王") 等价于 name like '王%'
likeRight(R column, Object val)

// 字段 IS NULL,例如 isNull("name") 等价于 name is null
isNull(R column)
// 字段 IS NOT NULL,例如 isNotNull("name") 等价于 name is not null
isNotNull(R column)

拼接嵌套

// 字段 IN (v0, v1, ...)
in(R column, Object... values) // in("age", 1, 2, 3) 等价于 age in (1,2,3)
// 字段 IN (value.get(0), value.get(1), ...)
in(R column, Collection<?> value) // 例如 in("age",{1,2,3}) 等价于 age in (1,2,3)

// 字段 NOT IN (v0, v1, ...)
notIn(R column, Object... values) // 例如 notIn("age", 1, 2, 3) 等价于 age not in (1,2,3)
// 字段 NOT IN (value.get(0), value.get(1), ...)
notIn(R column, Collection<?> value) // 例如 notIn("age",{1,2,3}) 等价于 age not in (1,2,3)

// 字段 IN ( sql语句 ),同理还有 字段 NOT IN (SQL 语句)
inSql(R column, String inValue) // // 例如:inSql("id", "select id from table where id < 3") 等价于 id in (id < 3)

// 拼接 OR,注意:主动调用or表示紧接着下一个方法不是用and连接!(不调用or则默认为使用and连接)
or(boolean condition) // 例如:eq("id",1).or().eq("name","老王") 等价于 id = 1 or name = '老王'
// OR 嵌套
or(Consumer consumer) // 例如:or(i -> i.eq("name", "李白").ne("status", "活着"))  等价于 or (name = '李白' and status <> '活着')
// AND 嵌套
and(Consumer consumer) // 例如:and(i -> i.eq("name", "李白").ne("status", "活着")) 等价于 and (name = '李白' and status <> '活着')
// 正常嵌套 不带 AND 或者 OR
nested(Consumer consumer) // 例如:nested(i -> i.eq("name", "李白").ne("status", "活着")) 等价于 (name = '李白' and status <> '活着')

// 无视优化规则直接拼接到 sql 的最后,只能调用一次,多次调用以最后一次为准,有sql注入的风险,请谨慎使用
last(String lastSql) // 例如:last("limit 1"),这是唯一的可以在SQL中使用limit语句的,并且只能是QueryWrapper构造器。
last(boolean condition, String lastSql)

// 拼接 EXISTS ( sql语句 )
exists(String existsSql) // 例如:exists("select id from table where age = 1") 等价于 exists (select id from table where age = 1)
// 拼接 NOT EXISTS ( sql语句 )
notExists(String notExistsSql) // 例如:notExists("select id from table where age = 1") 等价于 not exists (select id from table where age = 1)

// 拼接 SQL,该方法可用于数据库函数,动态入参的params对应前面applySql内部的{index}部分,这样是不会有sql注入风险的,反之会有!
apply(String applySql, Object... params)
// 例如:apply("id = 1") 等价于 id = 1
// 例如:apply("date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")等价于 date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")
// 例如:apply("date_format(dateColumn,'%Y-%m-%d') = {0}", "2008-08-08") 等价于 date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")

分组排序

// 分组:GROUP BY 字段, ...
groupBy(R... columns) // 例如:groupBy("id", "name") 等价于 group by id,name

// 排序:ORDER BY 字段, ... [规则]
orderByAsc(R... columns) // 例如:orderByAsc("id", "name") 等价于 order by id ASC,name ASC
orderByDesc(R... columns) // 例如:orderByDesc("id", "name") 等价于 order by id DESC,name DESC
orderBy(boolean condition, boolean isAsc, R... columns) // 例如:orderBy(true, true, "id", "name") 等价于 order by id ASC,name ASC

// HAVING ( sql语句 )
having(String sqlHaving, Object... params) // 例如:having("sum(age) > 10") 等价于 having sum(age) > 10

// 函数方法,主要方便在出现if...else下调用不同方法能不断链
func(Consumer consumer) // 例如: func(i -> if(true) {i.eq("id", 1)} else {i.ne("id", 1)})

常用注解

@TableName:表名注解

属性 类型 描述 默认值 是否必须指定
value String 表名 ""
schema String 模式 ""
resultMap String xml 中 resultMap 的 id ""
autoResultMap Boolean 是否自动构建 resultMap 并使用,与 resultMap 互斥 false
excludeProperty String[] 需要排除的属性名(@since 3.3.1) {}
keepGlobalPrefix Boolean 是否保持使用全局的 tablePrefix 的值 false

关于autoResultMap的说明:

MP 会自动构建一个 ResultMap 并注入到 MyBatis 里,因为 MP 底层是 MyBatis,只会注入常用的 CRUD 到 MyBatis 里,注入之前可以说是动态的(根据你entity的字段以及注解变化而变化),但是注入之后是静态的(等于你写在xml的东西), 而对于直接指定 typeHandler,MyBatis 只支持你写在2个地方:

  • 定义在 resultMap 里,只作用于 select 查询的返回结果封装。

  • 定义在 insert 和 update SQL 中 #{property} 里的 property 后面(例:#{property,typehandler=xxx.xxx.xxx}),只作用于设置值。而除了这两种直接指定 typeHandler,MyBatis 有一个全局的扫描你自己的 typeHandler 包的配置,这是根据你的 property 的类型去找 typeHandler 并使用。


@TableId:主键注解

属性 类型 描述 默认值 是否必须指定
value String 主键字段名 ""
type Enum 主键类型 IdType.NON

IdType

描述
AUTO 数据库自增ID
NONE 无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
INPUT insert 前自行 set 主键值
ASSIGN_ID 雪花ID,主键类型 Number 或 String,使用接口 IdentifierGenerator 的方法 nextId 实现
ASSIGN_UUID UUID,主键类型为 String,使用接口 IdentifierGenerator 的默认方法 nextUUID 实现

@TableField:非主键字段注解

属性 类型 描述 默认值 是否必须指定
value String 数据库字段名 ""
exist boolean 是否为数据库表字段 true
fill Enum 字段自动填充策略 FieldFill.DEFAULT
select boolean 是否进行 select 查询 true

FieldFill

描述
DEFAULT 默认不处理
INSERT 插入时填充字段
UPDATE 更新时填充字段
INSERT_UPDATE 插入和更新时填充字段

@TableLogic:表字段逻辑删除,@Version:乐观锁注解

注解示例

@TableName("sys_table")
public class Entity {

    /**
     * 实体主键
     */
    @TableId(value = "table_id", type = IdType.AUTO)
    private Long configId;


    /**
     * 创建时间
     */
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;

    /**
     * 更新时间
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;

    /**
     * 乐观锁
     */
    @Version
    private Integer version;

    /**
     * 逻辑删除
     */
    @TableLogic
    private Long delFlag;

}