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 标准能力接口,例如CreateMapper、UpdateMapper、FindByIdMapper;这些能力接口已经统一继承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...放在具体Mapper,Manager只做业务编排。 - 业务代码不要使用
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),前提是参数列来自 generatedTableDef。
业务代码中如果只是 where / join / orderBy / select 普通列,必须使用 generated TableDef。
删除和关系维护
物理删除前后的关系处理保持分层:
Manager的beforePhysicsDelete/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
仍有疑问?按反馈清单整理复现信息 →