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
LambdaQueryWrapper<SysMenu> wrapper = new LambdaQueryWrapper<>();
@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);
}
}
@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 完整方法
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() |
链式查询示例
List<SysMenu> list = this.lambdaQuery()
.eq(SysMenu::getMenuType, 1)
.like(SysMenu::getLabel, "系统")
.orderByAsc(SysMenu::getSort)
.list();
链式更新示例
boolean success = this.lambdaUpdate()
.eq(SysMenu::getId, 1L)
.set(SysMenu::getLabel, "新名称")
.set(SysMenu::getPath, "/new-path")
.update();
链式删除示例
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 |
第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:
// 不推荐,字段名写字符串,没编译检查
QueryWrapper<SysMenu> wrapper = new QueryWrapper<>();
wrapper.eq("menu_type", 1)
.like("label", "系统");
2. 分页插件配置
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
3. 乐观锁
@Entity
public class SysMenu {
@Version
private Integer version; // 数据库要有 version 字段
}
// 使用时
menu.setLabel("新名称");
boolean success = this.updateById(menu); // 自动带上 version 条件
4. 逻辑删除
@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. 自动填充(创建时间、更新时间)
@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 配置
mybatis-plus:
global-config:
db-config:
# 逻辑删除配置
logic-delete-field: deleted # 全局逻辑删除字段名
logic-delete-value: 1 # 逻辑已删除值(默认 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认 0)
简化写法(如果使用默认值 1 和 0)
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted
默认值就是 logic-delete-value: 1 和 logic-not-delete-value: 0,所以可以省略。
二、实体类配置
方式一:@TableLogic 注解(推荐)
@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 注解:
@Data
public class SysMenu {
private Integer deleted; // 字段名和 yml 配置一致,自动识别
}
三、逻辑删除后的行为
删除操作
// 物理删除(真的删)
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
查询操作(自动带上条件)
// 查询所有
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. 查询包含已删除的数据
// 方式一:用 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. 物理删除(不走逻辑删除)
// 用 Mapper 的 delete 方法(不走逻辑删除)
sysMenuMapper.deleteById(1L);
// SQL: DELETE FROM sys_menu WHERE id=1
// 或者在 Service 里用 baseMapper
this.baseMapper.deleteById(1L);
3. 恢复已删除的数据
// 手动设置 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
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
实体类
@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;
}
数据库表
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 可以是 Integer、Long、Boolean、String |
| 默认值 |
数据库建表时建议设置 DEFAULT 0 |
| 唯一索引 |
如果有唯一索引,逻辑删除可能导致重复数据,建议用 deleted 作为联合索引的一部分 |
| 批量操作 |
removeByIds、remove(wrapper) 都会自动带上逻辑删除 |
| 分页查询 |
page() 也会自动过滤已删除数据 |
@TableField注解的常用属性
@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;
}