Skip to content

Repository files navigation

mybatis-max

MyBatis Plus 增强工具包 | dahaoshen.com

CI License

特性

  • 约定优于配置的动态条件查询:通过字段命名约定自动生成查询条件,无需手写 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 解密字段查询

快速开始

1. 引入依赖

<!-- 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 依赖,也无需自己注册 RedissonClient Bean。 是否启用 Redis 分布式锁由 max-lock 按配置自动探测决策(约定优于配置):

  • 配置了 max.lock.redis.*(或 spring.data.redis.*)且能连通 → 启用 Redis 分布式锁;
  • 连不通 → 启动日志醒目告警并自动回退本地 JVM 锁(应用不崩溃);
  • 完全不配置 → 直接使用本地 JVM 锁;
  • 已有自定义 RedissonClient Bean(如用了 redisson-spring-boot-starter)→ 自动复用,互不冲突。

2. 定义 Query 对象

@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;
}

3. Service 继承 MaxServiceImpl

// Service 接口
public interface UserService extends IMaxService<User> {
    // 自定义业务方法
}

// Service 实现
@Service
public class UserServiceImpl extends MaxServiceImpl<UserMapper, User>
        implements UserService {
    // 无需重复实现通用 CRUD 方法
}

4. 使用 Query 对象查询

@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);
    }
}

5. 幂等操作(开箱即用,无需额外依赖)

// 幂等新建: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 依赖版本清单

一值多列 OR 与逃生舱

@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")));

⚠️ 追加的 OR 必须包在 .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,也不需要外部数据库。

集成测试(含 H2)

mvn verify

mybatis-max-extension 和 starter 中的集成测试使用内嵌 H2 数据库,无需额外环境。

注意:mybatis-max 依赖 max-lock 的 com.dahaoshen:max-lock-core(当前 1.1.0,已发布至 Maven Central,正常构建会自动拉取)。若在本地同时修改 max-lock,需先在 max-lock 仓库执行 mvn install。

License

Apache License 2.0

About

MyBatis Plus 增强:约定优于配置的动态条件查询 + 自动方言感知 + 一站式 Service 基类 + 开箱即用幂等

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages