数据访问

MyBatis-Flex 代码编写指南

按 Mapper、实体、转换与查询约定接入 MyBatis-Flex。

浏览全部手册
本页目录
文档来源与 Markdown 原文

权威正文:ineed-core/docs/project-development/mybatis-flex-code-guide.md。网站按工作区快照同步,原文中的历史日期和验证范围保留。

开始编码前核对同版本源码;跨仓文件引用可在源码定位目录查找。

下载 Markdown 原文 ↓

本文约定业务模块中 *-mybatis-flex 子模块的标准写法。目标是让 MyBatis-Flex 和 JPA / QueryDSL 一样,使用编译期生成的结构化元数据表达表、列、关联和排序,避免业务代码散落表名、列名字符串。

基本原则

  • mybatis-flex-processor 生成的 entity.table.*TableDef 是 MyBatis-Flex 查询元数据的唯一业务来源。
  • 业务 Mapper 优先组合 core 标准能力接口,例如 CreateMapperUpdateMapperFindByIdMapper;这些能力接口已经统一继承 MybatisFlexOperation<E>
  • 业务 Mapper 必须实现 getTableDef();没有任何标准能力接口可继承的特殊 mapper,才直接继承 MybatisFlexOperation<E>
  • 业务 Manager 只提供 getOperation() 作为 MyBatis-Flex 持久化入口,不再提供 getMapper()
  • 业务 Manager 不实现 getEntityClass();core 默认通过 getOperation().getEntityClass() 获取实体类型,Mapper 再由 getTableDef() 解析实体类型。
  • 查询、删除、计数、join、orderBy 优先使用 QueryWrapper + generated *TableDef
  • 关系表、子表、从表的 findBy... / countBy... / deleteBy... 放在具体 MapperManager 只做业务编排。
  • 业务代码不要使用 QueryMethods.column("...")、表名字符串或列名字符串拼查询。
  • 如果生成类缺失,先修 pom.xml 的 processor 配置,不退回手写字符串 SQL。

Processor 配置

每个 *-mybatis-flex 模块必须独立配置 mybatis-flex-processor,类似 JPA 模块接入 QueryDSL APT。

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <configuration>
        <annotationProcessorPaths combine.children="append">
          <path>
            <groupId>com.mybatis-flex</groupId>
            <artifactId>mybatis-flex-processor</artifactId>
            <version>${mybatis-flex.version}</version>
          </path>
        </annotationProcessorPaths>
      </configuration>
    </plugin>
  </plugins>
</build>

生成类路径应为:

{basePackage}.mybatisflex.entity.table.{Entity}TableDef

业务代码直接 import 生成类,例如:

import com.ineed.config.parameter.mybatisflex.entity.table.ParameterTableDef;

Mapper 写法

业务 Mapper 负责持久化操作入口和明确的表级查询方法。标准能力接口已经继承 MybatisFlexOperation<E>,所以业务 mapper 通常只组合需要的能力接口并实现 getTableDef()

@Mapper
public interface ParameterMapper extends
//@formatter:off
  CreateMapper<Parameter>,
  UpdateMapper<Parameter>,
  PhysicsDeleteMapper<Parameter>,
  LastModifiedMapper<Parameter>,
  DataStatusMapper<Parameter>,
  FindByIdMapper<Parameter>,
  LogicDeleteMapper<Parameter> {
//@formatter:on

  @Override
  default ParameterTableDef getTableDef() {
    return ParameterTableDef.PARAMETER;
  }
}

如果某个 mapper 不需要任何标准能力接口,但仍需要作为 MyBatis-Flex 持久化入口使用,可以直接继承 MybatisFlexOperation<E>

@Mapper
public interface ParameterSnapshotMapper extends MybatisFlexOperation<ParameterSnapshot> {

  @Override
  default ParameterSnapshotTableDef getTableDef() {
    return ParameterSnapshotTableDef.PARAMETER_SNAPSHOT;
  }
}

不要在业务 Mapper 中再写:

default Class<Parameter> getEntityClass() {
  return Parameter.class;
}

MybatisFlexOperation 通过 GetTableDefOperation 承接实体类型解析,getTableDef() 默认解析实体类型。如果实体包名和生成类包名不符合 entity / entity.table 约定,应先修包结构或生成配置,不在业务代码里补第二套实体类型声明。

Mapper 查询方法

子表、关系表、从表的直接外键访问放在具体 Mapper

default long countByGroupIdAndDeleted(final String groupId, final Byte deleted) {
  final QueryWrapper queryWrapper = QueryWrapper.create();
  queryWrapper.where(getTableDef().GROUP_ID.eq(groupId));
  queryWrapper.and(getTableDef().DELETED.eq(deleted));
  final Long count = selectCountByQuery(queryWrapper);
  return count == null ? 0L : count;
}

default void deleteByGroupId(final String groupId) {
  final QueryWrapper queryWrapper = QueryWrapper.create();
  queryWrapper.where(getTableDef().GROUP_ID.eq(groupId));
  deleteByQuery(queryWrapper);
}

不要把这类逻辑写成 Manager 中的通用 helper:

// 禁止
countActiveValue(BaseMapper<?> mapper, String columnName, String columnValue)
deleteValue(BaseMapper<?> mapper, String columnName, String columnValue)

也不要写:

// 禁止
QueryMethods.column("group_id").eq(groupId)

正确写法是:

getTableDef().GROUP_ID.eq(groupId)

Manager 写法

业务 Manager 组合 core 默认能力,只提供 getOperation() / converter 入口,不再提供 getMapper(),也不提供 getEntityClass()。需要实体类型时走 core 默认实现:getOperation().getEntityClass()

@Component
@RequiredArgsConstructor
@Slf4j
public class ParameterManager extends AbstractMybatisFlexManager implements
//@formatter:off
  IParameterManager,
  CreateOrRefreshManager<Parameter, ParameterAO, ParameterVO, ParameterMapper>,
  EnableManager<Parameter, ParameterMapper>,
  LockManager<Parameter, ParameterMapper>,
  PublishManager<Parameter, ParameterMapper>,
  FindByIdManager<Parameter, ParameterVO, ParameterMapper>,
  LogicDeleteManager<Parameter, ParameterMapper>,
  PhysicsDeleteManager<Parameter, ParameterMapper>,
  SearchManager<Parameter, ParameterQuery, ParameterVO>,
  SearchRecycleManager<Parameter, ParameterQuery, ParameterVO> {
//@formatter:on

  private static final ParameterTableDef PARAMETER = ParameterTableDef.PARAMETER;

  private final ParameterConverter parameterConverter;

  private final ParameterMapper parameterMapper;

  @Override
  public ParameterMapper getOperation() {
    return parameterMapper;
  }

  @Override
  public AoEntityConverter<Parameter, ParameterAO> getCreateEntityConverter() {
    return parameterConverter;
  }
}

getOperation() 返回当前 mapper,是 Manager 到 MyBatis-Flex 的唯一持久化入口。core 的 search、find、delete、data status 等默认能力都通过这个入口调用 mapper 方法、读取 getTableDef()getEntityClass()。不要为了“语义清晰”再在业务 Manager 中补一个 getMapper() 委托方法,否则同一对象会出现两套入口,后续很容易出现返回对象不一致的问题。

Manager 中可以声明当前查询需要的 TableDef 常量。需要 join 时使用 alias:

private static final ParameterTableDef PARAMETER_TABLE = ParameterTableDef.PARAMETER.as("p");

private static final ParameterGroupTableDef PARAMETER_GROUP_TABLE = ParameterGroupTableDef.PARAMETER_GROUP.as("g");

Search 写法

getKeySearchColumns() 返回 QueryColumn[],必须直接返回 generated 列:

@Override
public QueryColumn[] getKeySearchColumns() {
  //@formatter:off
  return new QueryColumn[] {
      PARAMETER.PARAMETER_KEY,
      PARAMETER.PARAMETER_NAME
  };
  //@formatter:on
}

不要返回字符串:

// 禁止
return new QueryColumn[] {
    "parameter_key",
    "parameter_name"
};

appendWhereOfQuery(...) 使用 generated 列:

@Override
public void appendWhereOfQuery(final QueryWrapper wb, final ParameterQuery query) {
  if (query == null) {
    return;
  }
  if (StringUtils.isNotBlank(query.getGroupId())) {
    wb.and(PARAMETER.GROUP_ID.eq(query.getGroupId()));
  }
  if (StringUtils.isNotBlank(query.getParameterName())) {
    wb.and(PARAMETER.PARAMETER_NAME.like("%" + query.getParameterName() + "%"));
  }
}

如果 query 中有字段,但当前实体表没有对应列,不能为了编译通过回退到 QueryMethods.column("...")。应先判断:

  • 字段是否是契约遗留,应从当前 MyBatis-Flex 查询中移除。
  • 字段是否本应存在于实体,应先补实体 @Column 和数据库字段,再由 processor 生成列。
  • 字段是否来自关联表,应通过 join 引入对应表的 generated TableDef

Join 写法

join 必须使用 generated TableDef 和 alias,不写表名字符串:

//@formatter:off
final QueryWrapper queryWrapper = QueryWrapper.create()
    .select(PARAMETER_TABLE.DEFAULT_COLUMNS)
    .select(
        PARAMETER_GROUP_TABLE.GROUP_KEY.as("groupKey"),
        PARAMETER_GROUP_TABLE.GROUP_NAME.as("groupName"))
    .from(PARAMETER_TABLE)
    .leftJoin(PARAMETER_GROUP_TABLE)
    .on(PARAMETER_GROUP_TABLE.ID.eq(PARAMETER_TABLE.GROUP_ID))
    .where(PARAMETER_TABLE.DELETED.eq(CoreYesNo.NO.getByte()));
//@formatter:on

跨表删除前检查也要指向被检查表的 TableDef

//@formatter:off
final long count = dataScopeRuleMapper.selectCountByQuery(QueryWrapper.create()
    .from(DATA_SCOPE_RULE_VALUE)
    .where(DATA_SCOPE_RULE_VALUE.RULE_ID.eq(entity.getId()))
    .and(DATA_SCOPE_RULE_VALUE.DELETED.eq(CoreYesNo.NO.getByte())));
//@formatter:on

不要写:

// 禁止
.from("isec_data_scope_rule_value")
.where(QueryMethods.column("rule_id").eq(entity.getId()))

字段命名

generated TableDef 字段名来自实体属性,而不一定等同于数据库列名的直译。

例如实体字段:

@Column("group_id")
protected String workGroupId;

生成列可能是:

WORK_GROUP_ID

业务代码必须使用 generated 字段:

WORK_GROUP_USER.WORK_GROUP_ID.eq(workGroupId)

不要凭数据库列名猜成:

// 禁止
WORK_GROUP_USER.GROUP_ID.eq(workGroupId)

遇到编译错误时,以 target/generated-sources/annotations/.../*TableDef.java 为准。

QueryMethods 使用边界

业务模块禁止使用 QueryMethods.column("...") 生成列。以下场景例外:

  • core 内部工具类为了兼容通用字段名、函数表达式或默认排序做集中转换。
  • MyBatis-Flex 没有 QueryWrapper 等价 API 的函数表达式,例如 QueryMethods.distinct(tableDef.FIELD),前提是参数列来自 generated TableDef

业务代码中如果只是 where / join / orderBy / select 普通列,必须使用 generated TableDef

删除和关系维护

物理删除前后的关系处理保持分层:

  • ManagerbeforePhysicsDelete / afterPhysicsDelete 只编排业务动作。
  • 关系表删除、子表计数、外键查询落到具体 Mapper
  • 查询条件使用关系表自己的 TableDef,不要拿主表 TableDef 查子表字段。

示例:

@Override
public void beforePhysicsDelete(final Parameter beDelete) {
  parameterValueMapper.deleteByParameterId(beDelete.getId());
}

对应 mapper:

default void deleteByParameterId(final String parameterId) {
  final QueryWrapper queryWrapper = QueryWrapper.create();
  queryWrapper.where(getTableDef().PARAMETER_ID.eq(parameterId));
  deleteByQuery(queryWrapper);
}

验证命令

改造或新增 MyBatis-Flex 模块后,至少执行对应模块编译:

mvn -pl :{module}-mybatis-flex -am -DskipTests compile

批量改造后应扫描业务 MyBatis-Flex 源码:

find . -path '*/target/*' -prune -o \
  -path '*mybatis*flex*/src/main/java/*' -type f -name '*.java' -print0 \
  | xargs -0 rg "QueryMethods\\.column\\(|GetEntityClassOperation|default Class<[^>]+> getEntityClass\\(|public Class<[^>]+> getEntityClass\\("

同时扫描 MyBatis-Flex Manager 是否残留 getMapper()

find . -path '*/target/*' -prune -o \
  -path '*mybatis*flex*/src/main/java/*/manager/*.java' -type f -name '*.java' -print0 \
  | xargs -0 rg "public .* getMapper\\("

预期结果:

  • 业务 MyBatis-Flex 模块没有 QueryMethods.column(...)
  • 业务 MyBatis-Flex 模块没有直接继承 GetEntityClassOperation
  • 业务 mapper / manager 没有手写 getEntityClass()
  • 业务 Manager 没有 getMapper(),MyBatis-Flex 持久化入口统一为 getOperation()
  • GetEntityClassOperation 只由 core 的 GetTableDefOperation / MybatisFlexOperation 间接承接。

常见错误

错误 正确处理
找不到 *TableDef 检查 mybatis-flex-processor 是否配置,先编译生成
TableDef 没有某列 以实体字段和生成类为准;不要手写字符串列绕过
Mapper 不知道继承什么 优先继承 core 标准 mapper 能力接口;没有标准能力时再直接继承 MybatisFlexOperation<E>
Manager 需要实体类型 不实现 getEntityClass();通过 getOperation().getEntityClass() 走 core 默认实现
Manager 想拿 mapper 直接使用 getOperation();不要新增 getMapper() 委托
子表计数写在 Manager 下沉到子表 Mapper 的明确方法
join 中写 .from("table") 改为 .from(TABLE_DEF).from(TABLE_DEF.as("alias"))
key search 返回字符串 改为 QueryColumn[] generated 列
generated 字段名与列名不一致 使用 generated 字段名,不按数据库列名猜测

最后更新:2026-06-02

仍有疑问?按反馈清单整理复现信息 →