springboot mp mybatis plaus


Mapper vs Service 完整方法对比(含 LambdaWrapper)

一、增删改操作

功能 Mapper 方法 Service 方法
插入单条 insert(T entity) save(T entity)
批量插入 ❌ 无 saveBatch(Collection<T> list)
批量插入(指定批次) ❌ 无 saveBatch(Collection<T> list, int batchSize)
插入或更新 ❌ 无 saveOrUpdate(T entity)
条件插入或更新 ❌ 无 saveOrUpdate(T entity, Wrapper<T> wrapper)
批量插入或更新 ❌ 无 saveOrUpdateBatch(Collection<T> list)
根据ID删除 deleteById(Serializable id) removeById(Serializable id)
根据实体删除 deleteById(T entity) removeById(T entity)
条件删除 delete(Wrapper<T> wrapper) remove(Wrapper<T> wrapper)
批量ID删除 deleteBatchIds(Collection ids) removeByIds(Collection ids)
根据Map删除 ❌ 无 removeByMap(Map<String, Object> map)
根据ID更新 updateById(T entity) updateById(T entity)
条件更新 update(T entity, Wrapper<T> wrapper) update(T entity, Wrapper<T> wrapper)
纯条件更新 ❌ 无 update(Wrapper<T> wrapper)

二、查询操作

功能 Mapper 方法 Service 方法
根据ID查询 selectById(Serializable id) getById(Serializable id)
根据实体查询 selectById(T entity) getById(T entity)
查询单条 selectOne(Wrapper<T> wrapper) getOne(Wrapper<T> wrapper)
查询单条(抛异常) ❌ 无 getOne(Wrapper<T> wrapper, boolean throwEx)
查询全部 selectList(null) list()
条件查询列表 selectList(Wrapper<T> wrapper) list(Wrapper<T> wrapper)
查询总数 selectCount(Wrapper<T> wrapper) count(Wrapper<T> wrapper)
查询全部总数 selectCount(null) count()
分页查询 selectPage(Page<T> page, Wrapper<T> wrapper) page(Page<T> page, Wrapper<T> wrapper)
全部分页 selectPage(page, null) page(page)
批量ID查询 selectBatchIds(Collection ids) listByIds(Collection ids)
根据Map查询 selectByMap(Map<String, Object> map) listByMap(Map<String, Object> map)
返回Map单条 ❌ 无 getMap(Wrapper<T> wrapper)
返回Map列表 selectMaps(Wrapper<T> wrapper) listMaps(Wrapper<T> wrapper)
返回全部Map列表 ❌ 无 listMaps()
返回Map分页 selectMapsPage(Page<T> page, Wrapper<T> wrapper) pageMaps(Page<T> page, Wrapper<T> wrapper)
返回第一列单条 ❌ 无 getObj(Wrapper<T> wrapper)
返回第一列列表 selectObjs(Wrapper<T> wrapper) listObjs(Wrapper<T> wrapper)
返回全部第一列 ❌ 无 listObjs()
返回第一列并转换 ❌ 无 listObjs(Wrapper<T> wrapper, Function mapper)

三、LambdaQueryWrapper 完整方法

service 和mapper 上的方法都能直接传递 LambdaQueryWrapper

java 复制代码
LambdaQueryWrapper<SysMenu> wrapper = new LambdaQueryWrapper<>();
bash 复制代码
@Service
public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> 
                                 implements SysMenuService {

    public List<SysMenu> search(String keyword) {
        LambdaQueryWrapper<SysMenu> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(SysMenu::getLabel, keyword);
        
        // ✅ Service 方法直接传
        return this.list(wrapper);
    }
}
bash 复制代码
@Mapper
public interface SysMenuMapper extends BaseMapper<SysMenu> {
    // 不需要额外定义,直接用继承的方法
}

// 在 Service 里也可以这样
public List<SysMenu> search(String keyword) {
    LambdaQueryWrapper<SysMenu> wrapper = new LambdaQueryWrapper<>();
    wrapper.like(SysMenu::getLabel, keyword);
    
    // ✅ Mapper 方法直接传
    return this.baseMapper.selectList(wrapper);
}

比较操作

方法 说明 示例
eq(SFunction, value) 等于 = wrapper.eq(SysMenu::getId, 1L)
ne(SFunction, value) 不等于 <> wrapper.ne(SysMenu::getMenuType, 2)
gt(SFunction, value) 大于 > wrapper.gt(SysMenu::getSort, 0)
ge(SFunction, value) 大于等于 >= wrapper.ge(SysMenu::getSort, 1)
lt(SFunction, value) 小于 < wrapper.lt(SysMenu::getSort, 10)
le(SFunction, value) 小于等于 <= wrapper.le(SysMenu::getSort, 5)

模糊查询

方法 说明 示例
like(SFunction, value) LIKE %value% wrapper.like(SysMenu::getLabel, "系统")
notLike(SFunction, value) NOT LIKE %value% wrapper.notLike(SysMenu::getLabel, "测试")
likeLeft(SFunction, value) LIKE %value wrapper.likeLeft(SysMenu::getLabel, "系统")
likeRight(SFunction, value) LIKE value% wrapper.likeRight(SysMenu::getLabel, "系统")

范围查询

方法 说明 示例
in(SFunction, Collection) IN wrapper.in(SysMenu::getId, Arrays.asList(1L,2L))
in(SFunction, Object...) IN wrapper.in(SysMenu::getId, 1L, 2L, 3L)
notIn(SFunction, Collection) NOT IN wrapper.notIn(SysMenu::getId, idList)
notIn(SFunction, Object...) NOT IN wrapper.notIn(SysMenu::getId, 1L, 2L)
between(SFunction, v1, v2) BETWEEN wrapper.between(SysMenu::getSort, 1, 10)
notBetween(SFunction, v1, v2) NOT BETWEEN wrapper.notBetween(SysMenu::getSort, 1, 10)

空值判断

方法 说明 示例
isNull(SFunction) IS NULL wrapper.isNull(SysMenu::getParentId)
isNotNull(SFunction) IS NOT NULL wrapper.isNotNull(SysMenu::getParentId)

排序

方法 说明 示例
orderByAsc(SFunction) 升序 wrapper.orderByAsc(SysMenu::getSort)
orderByAsc(SFunction...) 多字段升序 wrapper.orderByAsc(SysMenu::getSort, SysMenu::getId)
orderByDesc(SFunction) 降序 wrapper.orderByDesc(SysMenu::getCreateTime)
orderByDesc(SFunction...) 多字段降序 wrapper.orderByDesc(SysMenu::getSort, SysMenu::getId)
orderBy(boolean condition, boolean isAsc, SFunction) 条件排序 wrapper.orderBy(true, true, SysMenu::getSort)

逻辑组合

方法 说明 示例
and(Consumer) AND 嵌套 wrapper.and(w -> w.eq(...).or().eq(...))
or() OR wrapper.eq(...).or().eq(...)
or(Consumer) OR 嵌套 wrapper.or(w -> w.eq(...).eq(...))
nested(Consumer) 普通嵌套 wrapper.nested(w -> w.eq(...).or().eq(...))

高级功能

方法 说明 示例
exists(String, Object) EXISTS 子查询 wrapper.exists("SELECT 1 FROM sys_role WHERE id = {0}", roleId)
notExists(String, Object) NOT EXISTS wrapper.notExists("SELECT 1 FROM sys_role WHERE id = {0}", roleId)
apply(String, Object...) 追加SQL wrapper.apply("date_format(create_time,'%Y-%m-%d') = {0}", today)
last(String) 追加到最后 wrapper.last("LIMIT 1")
func(Consumer) 函数式操作 wrapper.func(w -> w.eq(...).or().eq(...))

字段选择

方法 说明 示例
select(SFunction...) 选择字段 wrapper.select(SysMenu::getId, SysMenu::getLabel)
select(Class<T>, Predicate) 条件选择 wrapper.select(SysMenu.class, field -> !field.isAnnotationPresent(TableField.class))
select(Predicate) 条件选择 wrapper.select(field -> field.getColumn().equals("id"))

分组

方法 说明 示例
groupBy(SFunction) 分组 wrapper.groupBy(SysMenu::getMenuType)
groupBy(SFunction...) 多字段分组 wrapper.groupBy(SysMenu::getMenuType, SysMenu::getTopId)
having(String, Object...) HAVING wrapper.having("COUNT(*) > {0}", 1)

四、LambdaUpdateWrapper 完整方法

java 复制代码
LambdaUpdateWrapper<SysMenu> wrapper = new LambdaUpdateWrapper<>();

SET 操作

方法 说明 示例
set(SFunction, value) SET 字段 wrapper.set(SysMenu::getLabel, "新名称")
set(SFunction, value, boolean condition) 条件SET wrapper.set(SysMenu::getLabel, "新名称", flag)
setSql(String) SET SQL片段 wrapper.setSql("sort = sort + 1")

WHERE 条件(同 LambdaQueryWrapper)

方法 说明 示例
eq(SFunction, value) 等于 wrapper.eq(SysMenu::getId, 1L)
ne(SFunction, value) 不等于 wrapper.ne(SysMenu::getMenuType, 2)
gt(SFunction, value) 大于 wrapper.gt(SysMenu::getSort, 0)
ge(SFunction, value) 大于等于 wrapper.ge(SysMenu::getSort, 1)
lt(SFunction, value) 小于 wrapper.lt(SysMenu::getSort, 10)
le(SFunction, value) 小于等于 wrapper.le(SysMenu::getSort, 5)
like(SFunction, value) LIKE wrapper.like(SysMenu::getLabel, "系统")
in(SFunction, Collection) IN wrapper.in(SysMenu::getId, Arrays.asList(1L,2L))
between(SFunction, v1, v2) BETWEEN wrapper.between(SysMenu::getSort, 1, 10)
isNull(SFunction) IS NULL wrapper.isNull(SysMenu::getParentId)
isNotNull(SFunction) IS NOT NULL wrapper.isNotNull(SysMenu::getParentId)
and(Consumer) AND 嵌套 wrapper.and(w -> w.eq(...).or().eq(...))
or() OR wrapper.eq(...).or().eq(...)
apply(String, Object...) 追加SQL wrapper.apply("date_format(create_time,'%Y-%m-%d') = {0}", today)

五、链式调用(Service 特有)(不推荐,推荐使用LambdaQueryWrapper )

方法 说明 示例
lambdaQuery() 链式查询 this.lambdaQuery().eq().like().list()
lambdaUpdate() 链式更新 this.lambdaUpdate().eq().set().update()
lambdaUpdate() 链式删除 this.lambdaUpdate().eq().remove()

链式查询示例

java 复制代码
List<SysMenu> list = this.lambdaQuery()
    .eq(SysMenu::getMenuType, 1)
    .like(SysMenu::getLabel, "系统")
    .orderByAsc(SysMenu::getSort)
    .list();

链式更新示例

java 复制代码
boolean success = this.lambdaUpdate()
    .eq(SysMenu::getId, 1L)
    .set(SysMenu::getLabel, "新名称")
    .set(SysMenu::getPath, "/new-path")
    .update();

链式删除示例

java 复制代码
boolean success = this.lambdaUpdate()
    .eq(SysMenu::getParentId, 1L)
    .remove();

六、快速记忆总结

名字对应关系

功能 Mapper Service LambdaWrapper
插入 insert save
删除 delete remove
更新 update update set
查询单条 selectOne getOne
查询列表 selectList list
查询总数 selectCount count
分页 selectPage page
条件构建 eq/like/in...

使用场景

场景 推荐用
简单 CRUD Service (this.save(), this.list())
复杂条件查询 LambdaQueryWrapper (wrapper.eq().like().list())
链式操作 Service (this.lambdaQuery().eq().list())
更新指定字段 LambdaUpdateWrapper (wrapper.set().eq().update())
批量操作 Service (this.saveBatch())
多表关联 Mapper + XML

bash 复制代码
第1优先:Service 方法
    this.save()
    this.list()
    this.page()
    ...

第2优先:LambdaQueryWrapper(更规范,有编译检查)
    LambdaQueryWrapper<SysMenu> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(SysMenu::getMenuType, 1)
           .like(SysMenu::getLabel, "系统")
           .list();

第3优先:Service 链式 lambdaQuery(方便但无编译检查)
    this.lambdaQuery().eq().like().list()

第4优先:Mapper 自定义方法 + XML
    this.baseMapper.selectMenuTreeByUserId(userId)

为什么 LambdaQueryWrapper 比 lambdaQuery 优先级高?

对比项 LambdaQueryWrapper lambdaQuery

编译检查​ ✅ 有(字段名写错 IDE 报红) ❌ 无(运行时才报错)

代码提示​ ✅ 有 ✅ 有

可读性​ ✅ 清晰 ✅ 简洁

复用性​ ✅ 可以赋值给变量复用 ❌ 每次都要重写

复杂条件​ ✅ 支持嵌套、分组、having ✅ 支持

IDE 重构​ ✅ 字段改名 IDE 自动更新 ❌ 不会自动更新

对,这份笔记已经包含了 MyBatis-Plus 全部核心知识点,日常开发 95% 的场景都够用了。

不过还有几个小点可以补充,让你的知识体系更完整:


可以补充的几个知识点

1. QueryWrapper(非 Lambda 版)

虽然 LambdaQueryWrapper 更推荐,但有时也会见到老代码用 QueryWrapper:

java 复制代码
// 不推荐,字段名写字符串,没编译检查
QueryWrapper<SysMenu> wrapper = new QueryWrapper<>();
wrapper.eq("menu_type", 1)
       .like("label", "系统");

2. 分页插件配置

java 复制代码
@Configuration
public class MyBatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

3. 乐观锁

java 复制代码
@Entity
public class SysMenu {
    @Version
    private Integer version;  // 数据库要有 version 字段
}

// 使用时
menu.setLabel("新名称");
boolean success = this.updateById(menu);  // 自动带上 version 条件

4. 逻辑删除

java 复制代码
@Data
public class SysMenu {
    @TableLogic
    private Integer deleted;  // 0-未删除,1-已删除
}

// 调用 delete 时自动变成 UPDATE set deleted=1
this.removeById(1L);  // 实际执行:UPDATE sys_menu SET deleted=1 WHERE id=1

5. 自动填充(创建时间、更新时间)

java 复制代码
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
        this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }
}

// 实体类
@Data
public class SysMenu {
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

好,我给你补全 逻辑删除的完整配置,包括 yml 和实体类。


逻辑删除完整配置

一、application.yml 配置

yaml 复制代码
mybatis-plus:
  global-config:
    db-config:
      # 逻辑删除配置
      logic-delete-field: deleted    # 全局逻辑删除字段名
      logic-delete-value: 1          # 逻辑已删除值(默认 1)
      logic-not-delete-value: 0      # 逻辑未删除值(默认 0)

简化写法(如果使用默认值 1 和 0)

yaml 复制代码
mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted

默认值就是 logic-delete-value: 1logic-not-delete-value: 0,所以可以省略。


二、实体类配置

方式一:@TableLogic 注解(推荐)

java 复制代码
@Data
@TableName("sys_menu")
public class SysMenu {

    @TableId(type = IdType.AUTO)
    private Long id;

    private String label;

    @TableLogic  // 标记为逻辑删除字段
    private Integer deleted;  // 0-未删除,1-已删除
}

方式二:yml 全局配置 + 实体类字段名匹配

如果 yml 里配了 logic-delete-field: deleted,实体类只要有 deleted 字段就行,不需要加 @TableLogic 注解

java 复制代码
@Data
public class SysMenu {
    private Integer deleted;  // 字段名和 yml 配置一致,自动识别
}

三、逻辑删除后的行为

删除操作

java 复制代码
// 物理删除(真的删)
sysMenuMapper.deleteById(1L);
// SQL: DELETE FROM sys_menu WHERE id=1

// 逻辑删除(假删,变更新)
this.removeById(1L);
// SQL: UPDATE sys_menu SET deleted=1 WHERE id=1 AND deleted=0

查询操作(自动带上条件)

java 复制代码
// 查询所有
this.list();
// SQL: SELECT * FROM sys_menu WHERE deleted=0

// 条件查询
this.lambdaQuery().eq(SysMenu::getLabel, "系统").list();
// SQL: SELECT * FROM sys_menu WHERE label='系统' AND deleted=0

// 根据ID查询
this.getById(1L);
// SQL: SELECT * FROM sys_menu WHERE id=1 AND deleted=0

四、特殊场景处理

1. 查询包含已删除的数据

java 复制代码
// 方式一:用 Mapper 的 selectList(不带逻辑删除过滤)
sysMenuMapper.selectList(null);
// SQL: SELECT * FROM sys_menu(不过滤 deleted)

// 方式二:关闭本次查询的逻辑删除
LambdaQueryWrapper<SysMenu> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysMenu::getId, 1L);
wrapper.last("AND deleted=1");  // 手动加条件

2. 物理删除(不走逻辑删除)

java 复制代码
// 用 Mapper 的 delete 方法(不走逻辑删除)
sysMenuMapper.deleteById(1L);
// SQL: DELETE FROM sys_menu WHERE id=1

// 或者在 Service 里用 baseMapper
this.baseMapper.deleteById(1L);

3. 恢复已删除的数据

java 复制代码
// 手动设置 deleted=0
LambdaUpdateWrapper<SysMenu> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(SysMenu::getId, 1L)
       .set(SysMenu::getDeleted, 0);
this.update(wrapper);
// SQL: UPDATE sys_menu SET deleted=0 WHERE id=1

五、完整示例

application.yml

yaml 复制代码
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test
    username: root
    password: 123456

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

实体类

java 复制代码
@Data
@TableName("sys_menu")
public class SysMenu {

    @TableId(type = IdType.AUTO)
    private Long id;

    private String label;

    private String path;

    private Integer menuType;

    @TableLogic
    private Integer deleted;

    private LocalDateTime createTime;

    private LocalDateTime updateTime;
}

数据库表

sql 复制代码
CREATE TABLE sys_menu (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    label VARCHAR(50),
    path VARCHAR(100),
    menu_type INT,
    deleted TINYINT DEFAULT 0 COMMENT '0-未删除,1-已删除',
    create_time DATETIME,
    update_time DATETIME
);

六、注意事项

注意点 说明
字段类型 deleted 可以是 IntegerLongBooleanString
默认值 数据库建表时建议设置 DEFAULT 0
唯一索引 如果有唯一索引,逻辑删除可能导致重复数据,建议用 deleted 作为联合索引的一部分
批量操作 removeByIdsremove(wrapper) 都会自动带上逻辑删除
分页查询 page() 也会自动过滤已删除数据

@TableField注解的常用属性

bash 复制代码
@Data
public class SysMenu {

    @TableId(type = IdType.AUTO)
    private Long id;

    @TableField("menu_name")  // 指定数据库列名
    private String label;

    @TableField(exist = false)  // 非数据库字段
    private List<SysMenu> children;

    @TableField(fill = FieldFill.INSERT)  // 插入时自动填充
    private LocalDateTime createTime;

    @TableField(fill = FieldFill.INSERT_UPDATE)  // 插入和更新时自动填充
    private LocalDateTime updateTime;
}
相关推荐
金融支付架构实战指南1 小时前
Milvus 向量检索服务 + SpringBoot 实战:电商商品语义检索与相似商品推荐
spring boot·后端·milvus·向量检索
程序员佳佳2 小时前
四个月长期实测:自建 Milvus、FAISS、原生向量 API 和向量引擎中转方案,到底怎么选?
人工智能·windows·python·gpt·milvus·faiss
_Aaron___2 小时前
MyBatis 动态排序别乱用 ${}:ORDER BY 的安全写法
java·spring·mybatis
吠品2 小时前
.NET 8 单文件发布:把 exe 和一堆 dll 打进一个文件里
服务器·数据库·windows
愛~杦辷个訾2 小时前
Java Springboot使用阿里云oss对图片进行等质量压缩,转换成webp格式的压缩图。
java·spring boot·阿里云·oss
hikktn2 小时前
Excel 日期格式统一治理:从“显示不全“到“自动兼容“的完整方案
windows·python·excel
霸道流氓气质2 小时前
Spring Boot Multipart 表单中文乱码问题全解析
java·spring boot·后端
caimouse3 小时前
Reactos 第 6 章 进程间通信 — 6.8 本地过程调用(LPC)与 6.9 视窗报文(Message)
windows
caimouse3 小时前
Reactos 第 6 章 进程间通信
windows