MyBatis Plus 增强工具包 | dahaoshen.com
- 约定优于配置的动态条件查询:通过字段命名约定自动生成查询条件,无需手写
LambdaQueryWrapper - 自动方言感知:自动探测数据库类型,内置 MySQL / PostgreSQL 方言扩展(全文检索、JSON、加解密等)
- IMaxService / MaxServiceImpl:一站式 Service 基类,内置分页、批量、幂等等常用方法
- 开箱即用幂等:
IdempotencyRule配合 max-lock 分布式锁,一行代码实现幂等新建/更新
| 字段命名规则 | 生成条件 | 示例 |
|---|---|---|
以 Like 结尾 |
LIKE '%?%' |
nameLike → name LIKE '%value%' |
以 Start 结尾 |
>= value |
createTimeStart → create_time >= value |
以 End 结尾 |
<= value |
createTimeEnd → create_time <= value |
字段类型为 Collection<?> |
IN (...) |
List<Long> ids → id IN (...) |
字段类型为 Direction + 以 Order 结尾 |
ORDER BY ... ASC/DESC |
nameOrder = ASC → ORDER BY name ASC |
以 Prefix 结尾 |
LIKE '?%' |
codePrefix → code LIKE 'ABC%' |
以 Suffix 结尾 |
LIKE '%?' |
codeSuffix → code LIKE '%XYZ' |
以 Not 开头或结尾 |
!= 或 NOT IN |
statusNot → status != value |
@Priority 注解 |
控制排序字段优先级 | 数字越小优先级越高 |
@AnyColumns({"a","b"}) 注解 |
(cond(a) OR cond(b)) |
@AnyColumns({"name","phone"}) String keywordAnyLike → (name LIKE ? OR phone LIKE ?) |
方言扩展(需对应数据库):
| 字段命名规则 | 方言 | 生成条件 |
|---|---|---|
以 Fulltext 结尾 |
MySQL | MATCH(col) AGAINST(?) |
以 JsonContains 结尾 |
MySQL | JSON_CONTAINS(col, ?) |
以 FindInSet 结尾 |
MySQL | FIND_IN_SET(?, col) |
以 Decrypt 结尾 |
MySQL/PostgreSQL | 解密字段查询 |
<!-- Spring Boot 3.x:唯一需要引入的依赖 -->
<dependency>
<groupId>com.dahaoshen</groupId>
<artifactId>mybatis-max-spring-boot3-starter</artifactId>
<version>3.0.0</version>
</dependency>Spring Boot 2.x 将 starter 换为 mybatis-max-boot-starter。
⚠️ 3.0.0 不兼容变更(从 2.x 升级必读):删除了list(query, orQuery...)等方法的orQueryArray可变参数(QueryResolver.resolve同步删除)。原语义「每个 orQuery 内部 OR、 orQuery 之间 AND」严重反直觉,靠参数位置编码逻辑运算符,连本库旧文档示例都写错过。 迁移:一值多列搜索改用@AnyColumns注解(见下);字段维/组间 OR 用getWrapper(query)追加 MyBatis-Plus 原生条件(注意用.and(w -> ...)包括号,防止击穿其他条件)。
✅ 2.0.1 新增(纯新增,向后兼容,无迁移成本):
IMaxService补上toMap(list, keyMapper)/toGroup(list, keyMapper)两个 List 版重载, 用于把已查出的列表(如list()/listByIds(ids)的返回值)就地转 Map / 分组, 与原有 query 版toMap(keyMapper, query)/toGroup(keyMapper, query)互补—— 后者负责「查 + 转」,前者只负责「转」,避免为了转 Map 而重复查库。
⚠️ 2.0.0 不兼容变更(从 1.x 升级必读):核心类QueryWrapperBuilder更名为QueryResolver, 业务方法build(query)更名为resolve(query),IMaxService.queryBuilder()更名为queryResolver(), 自动装配的 Bean 名由queryWrapperBuilder改为queryResolver。 改名原因:旧名与「Builder 模式」语义冲突——它实际是把声明式 Query 对象解析为QueryWrapper的解析器,而非 Builder。 内部构建器(QueryResolver.builder()…build())保持不变。升级只需按上述映射改名即可,行为完全一致。
幂等功能开箱即用:自 1.1.0 起,分布式锁 max-lock 及其 Redis 实现 redisson 已作为非可选传递依赖随 starter 引入,无需再额外声明 max-lock / redisson 依赖,也无需自己注册
RedissonClientBean。 是否启用 Redis 分布式锁由 max-lock 按配置自动探测决策(约定优于配置):
- 配置了
max.lock.redis.*(或spring.data.redis.*)且能连通 → 启用 Redis 分布式锁;- 连不通 → 启动日志醒目告警并自动回退本地 JVM 锁(应用不崩溃);
- 完全不配置 → 直接使用本地 JVM 锁;
- 已有自定义
RedissonClientBean(如用了 redisson-spring-boot-starter)→ 自动复用,互不冲突。
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserQuery {
/** 精确匹配用户 ID 集合 → IN 查询 */
@TableField("id")
private List<Long> ids;
/** 用户名模糊查询 → LIKE '%?%' */
@TableField("username")
private String usernameLike;
/** 注册时间起始 → >= */
@TableField("create_time")
private LocalDateTime createTimeStart;
/** 注册时间截止 → <= */
@TableField("create_time")
private LocalDateTime createTimeEnd;
/** 按创建时间排序 */
@TableField("create_time")
@Priority(1)
private Direction createTimeOrder;
}// Service 接口
public interface UserService extends IMaxService<User> {
// 自定义业务方法
}
// Service 实现
@Service
public class UserServiceImpl extends MaxServiceImpl<UserMapper, User>
implements UserService {
// 无需重复实现通用 CRUD 方法
}@Service
@RequiredArgsConstructor
public class UserServiceImpl extends MaxServiceImpl<UserMapper, User>
implements UserService {
public Page<User> pageUsers(Page<User> page, UserQuery query) {
// 自动根据 query 字段命名约定构建查询条件
return page(page, query);
}
public List<User> listActiveUsers(UserQuery query) {
return list(query);
}
/** query 版:查库 + 转 Map(一步到位) */
public Map<Long, User> getIdMapByQuery(UserQuery query) {
return toMap(User::getId, query);
}
/** List 版(2.0.1 新增):对已查出的列表就地转 Map / 分组,不再重复查库 */
public Map<Long, User> getIdMap() {
return toMap(list(), User::getId);
}
public Map<Long, List<User>> getDeptGroup(List<Long> ids) {
return toGroup(listByIds(ids), User::getDeptId);
}
}// 幂等新建:username 唯一,重复调用不会插入两条
userService.saveIdempotency(user,
IdempotencyRule.of(User::getUsername, user.getUsername())
.message("用户名已存在"));
// 幂等更新:多条件联合幂等
userService.updateByIdIdempotency(user,
IdempotencyRule.of(User::getEmail, user.getEmail())
.message("邮箱已被占用")
.when(() -> user.getEmail() != null));mybatis-max:
# 是否开启自动填充(create_time / update_time / create_by / update_by)
fill:
enabled: true
# 数据库方言(auto 自动探测;可手动指定 mysql / postgresql)
dialect: auto
# Query 对象 null 值处理策略:ignore(默认,忽略 null)/ include
null-strategy: ignore
# 分布式锁(幂等所用):约定优于配置,默认 auto。
# 仅在需要 Redis 分布式锁时配置以下任一来源即可,无需引入任何额外依赖。
max:
lock:
# provider: auto # auto(默认) / redisson(强制,缺连接配置或连不通则启动报错) / local / none
redis:
address: redis://127.0.0.1:6379 # 或用 host/port;也可直接复用 spring.data.redis.*
# probe-timeout: 1000 # 连通探测超时(ms),连不通则醒目告警并回退本地锁| 模块 | 说明 |
|---|---|
| mybatis-max-annotation | Direction 枚举 + @Priority 注解(零依赖) |
| mybatis-max-core | QueryResolver、方言 SPI、条件策略 SPI、StringKit、JsonbTypeHandler |
| mybatis-max-extension | IMaxService、MaxServiceImpl、IdempotencyRule、自动填充 |
| mybatis-max-boot-starter | Spring Boot 2.x 自动装配 |
| mybatis-max-spring-boot3-starter | Spring Boot 3.x 自动装配 |
| mybatis-max-bom | 依赖版本清单 |
@AnyColumns(一值多列 OR):一个字段的值命中任一列即匹配——搜索框场景的正确建模 (值只有一个,是列在 OR)。策略仍由命名约定后缀决定,组内 OR、组外 AND:
public class UserQuery {
/** → status = ? AND (name LIKE '%?%' OR phone LIKE '%?%' OR email LIKE '%?%') */
@AnyColumns({"name", "phone", "email"})
private String keywordAnyLike;
@TableField("status")
private Integer status;
}语义规则:值为 null/blank 时整组不生成;集合值每列 IN;单列声明退化为普通条件。
非法组合启动即报错:与 @TableField 同字段、Direction 排序字段、否定策略字段
(「任一列不满足」几乎恒真,需要「所有列都不满足」请用多个普通 Not 字段)、空列数组。
逃生舱:Query 对象只表达「顶层 AND + @AnyColumns 括号内 OR」。更复杂的 OR
(不同字段不同值、条件组之间 OR、嵌套)用 getWrapper(query) 追加原生条件:
// age >= 18 或 gender = 'F',其余条件仍走 Query 声明式
List<User> users = list(getWrapper(query)
.and(w -> w.ge("age", 18).or().eq("gender", "F")));.and(w -> ...) 嵌套括号内;顶层裸 .or() 会击穿其他过滤条件
(典型事故:击穿租户隔离)。OR 超过一层嵌套或含子查询时直接手写 Wrapper 或 XML SQL。
not子串陷阱:字段名中含not(如annotation、notable)会被识别为否定查询。若实体字段本身包含not子串,建议通过@TableField显式标注列名,或在 Query 对象中重命名该字段以规避歧义。- 带约定后缀的实体字段:Query 对象中的字段与实体字段映射依赖
@TableField(value = "column_name")注解。凡字段名携带命名约定后缀(如xxxLike、xxxStart),建议始终标注@TableField,避免列名推断错误。
mvn test不需要 Docker,也不需要外部数据库。
mvn verifymybatis-max-extension 和 starter 中的集成测试使用内嵌 H2 数据库,无需额外环境。
注意:mybatis-max 依赖 max-lock 的
com.dahaoshen:max-lock-core(当前 1.1.0,已发布至 Maven Central,正常构建会自动拉取)。若在本地同时修改 max-lock,需先在 max-lock 仓库执行mvn install。