恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
MyFlex代码生成器实战:从手写CRUD到自动生成全套工程骨架
首页
资讯中心
/
MyFlex代码生成器实战:从手写CRUD到自动生成全套工程骨架
MyFlex代码生成器实战:从手写CRUD到自动生成全套工程骨架
发布时间:2026/10/20 20:57:13
1. 为什么团队需要一个自动生成代码的底座而不是每次手撸一套做后端服务的人应该都有这种体验每来一个业务模块先建表再写实体类再写 Mapper 接口再写 XML 里那些resultMap、select、insert、update的样板 SQL最后还要补 Service 和 Controller。这一套流程熟练的话一个单表 CRUD 大概也要折腾半小时到一小时而且绝大多数代码是雷同的改来改去就是表名和字段名不一样。我最初接触 MyFlex 代码生成器是接手一个模拟项目 X 的时候那个项目有三十多张表如果按老办法手工敲光实体类和 Mapper 就得写一整天而且我敢保证写到后面一定会有字段拼错、类型对不上、XML 里漏了列名这类低级问题。后来我用 MyFlex 提供的自动生成能力把建表脚本吃进去几分钟就把三十多张表的实体、Mapper、Service、Controller 全部生成出来剩下的工作只是对着业务需求做增量调整。这个体验让我确定了一件事代码生成器不是可有可无的效率工具而是后端工程里应该早点落地的“基础设施”。这篇内容不是给你抄一段官方文档就完事而是从“我为什么要这么配置”“生成出来的代码怎么融入现有项目”“哪些参数容易踩坑”这些角度把整个过程讲透。适合正在用 MyBatis 或者想从手写 Mapper 痛苦里解脱出来的团队参考哪怕你之前没用过任何代码生成工具跟着走一遍也能把整套流程跑通。我要先把结论放前面MyFlex 这套代码生成器的核心价值不是“生成代码”这个动作本身而是生成出来的代码能直接挂到你的业务开发流程上——实体带注解、Mapper 带通用方法、Service 有基础封装、Controller 可以直接对接前端。它解决的不是“省几分钟敲键盘时间”而是“让新模块的起点质量保持一致”。2. 生成器的设计逻辑和我为什么抛弃了手写 XML2.1 从 MyBatis Generator 到 MyFlex到底差在哪很多团队其实早就用过代码生成器最常见的是 MyBatis Generator。我自己也用过但我必须说它有两个让人很不舒服的地方。第一它生成的实体类非常“重”每个字段一长串注解看起来像是在给数据库写自传第二它生成的 Mapper XML 动辄几百行BaseColumnList、BaseResultMap这些基础块占了大量篇幅改一个字段要连带改好几处。如果表结构经常调整这种生成方式反而变成了维护负担。MyFlex 走的是另一条路线。它把 MyBatis 的能力重新封装过内置了BaseMapper和配套的BaseService单表 CRUD 根本不需要你手写 SQL只要实体类映射正确selectById、selectListByQuery、insert、updateById这些方法直接继承可用。这样一来代码生成器的职责就从“生成一大坨重复代码”变成了“生成一套轻量骨架”实体类只需要干净地映射表结构Mapper 接口继承 BaseMapperService 继承 BaseServiceController 提供 RESTful 入口。我用一个对比来帮你理解这个差异。MyBatis Generator 的思路是“你表里有什么我就把你所有能做的 SQL 都预生成出来”MyFlex 生成器的思路是“你表里有什么我把你操作这张表所需的骨架搭好具体的 SQL 交给框架的 QueryWrapper 在运行时拼装”。前者的产物是“一堆半成品文件”后者的产物是“一套可以立即开发的工程结构”。这就是为什么我更倾向于在 MyFlex 生态里做自动生成。2.2 它到底生成了哪些文件每个文件是什么角色首次跑通 MyFlex 代码生成器时你会在目标包里看到这几类产物。实体类Entity负责把表字段映射成 Java 属性MyFlex 会为每个字段加上驼峰转换并在类上标注Table注解指定表名。这就是数据访问层的地基。Mapper 接口只需继承BaseMapperT不需要写任何方法声明就能获得基础 CRUD 能力。如果你需要自定义查询直接在接口里加方法然后用 XML 或注解实现。这个设计很聪明因为只有真正“特别”的查询才需要额外代码通用能力全在基类里。Service 接口和实现类也是自动生成的Service 接口继承IServiceT实现类继承ServiceImplM, T。很多团队可能觉得 Service 层是多余的但 MyFlex 这么设计是有道理的它在基类里封装了批量操作、链式查询、逻辑删除、乐观锁等通用逻辑你写业务代码时可以直接复用而不是每个 Service 都从零开始拼 MyBatis API。Controller 层按需生成默认提供增删改查对应的 RESTful 端点。如果你做的是纯内部服务不需要直接暴露 HTTP 接口也可以关掉这一项后面我会说怎么配。还有一个容易被忽略的产物是FKS开头或名为Tables的表结构常量类。这类类把表名和字段名定义为静态常量写查询时可以用UserTable.COLUMN_NAME这种形式引用字段名避免硬编码字符串。老实说这个类刚生成出来的时候我觉得有点多余但用了几次之后发现改字段名时编译器能帮我找出所有引用点比在字符串里搜索安全得多。3. 动手配置一次完整的生成流程以及每一步为什么这么设3.1 引入依赖与构建工程骨架我假设你的项目是基于 Spring Boot 的 Maven 工程JDK 至少 8 起步实际推荐 11 以上。需要引入的核心依赖是 MyFlex 的 starter以及代码生成器模块还有你用的数据库驱动。以 MySQL 为例pom.xml里的关键部分大致长这样dependency groupIdcom.myflex/groupId artifactIdmyflex-spring-boot-starter/artifactId version1.2.6/version /dependency dependency groupIdcom.myflex/groupId artifactIdmyflex-generator/artifactId version1.2.6/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency这里有个容易踩的细节myflex-generator默认依赖了模板引擎。MyFlex 支持 FreeMarker 和 Velocity 两种模板默认内置 FreeMarker。如果你项目里因为其他原因已经引了 Velocity要注意版本冲突最好统一走 MyFlex 的默认模板引擎。我在某次集成时因为项目里已有旧版 Velocity导致生成器运行时出现模板类加载异常排查了很久才发现是版本冲突。建议遇到这类问题优先检查模板引擎依赖树。3.2 编写生成器启动类核心配置项逐个拆解MyFlex 代码生成器的入口是一个 Java 类里面通过Generator对象设置数据源、包名、策略等信息。下面是我在某跨平台系统里实际用过的配置骨架我直接把关键项都列出来public class MyFlexGenerator { public static void main(String[] args) { FastAutoGenerator generator FastAutoGenerator.create( jdbc:mysql://localhost:3306/demo_db?useUnicodetruecharacterEncodingutf8, root, your_password ); generator.globalConfig(builder - { builder.author(dev-team) .outputDir(src/main/java) .disableOpenDir() .dateType(DateType.TIME_PACK) .commentDate(yyyy-MM-dd); }) .packageConfig(builder - { builder.parent(com.example.demo) .entity(entity) .mapper(mapper) .service(service) .serviceImpl(service.impl) .controller(controller) .pathInfo(Collections.singletonMap(OutputFile.xml, src/main/resources/mapper)); }) .strategyConfig(builder - { builder.addInclude(sys_user, sys_role, sys_menu) .addTablePrefix(sys_) .entityBuilder() .enableLombok() .logicDeleteColumnName(deleted) .versionColumnName(version) .mapperBuilder() .enableBaseColumnList() .serviceBuilder() .formatServiceFileName(%sService) .controllerBuilder() .enableRestStyle() .enableHyphenStyle(); }) .execute(); } }这段配置里我标注几个关键点。globalConfig的outputDir是生成代码的输出根目录。这里有个新手很容易犯的错直接把输出路径写成src/main/java如果你的项目是分模块的多 Maven 工程生成器会傻乎乎地把所有文件堆到当前模块下导致其他模块引用不到。多模块项目里每个模块单独跑一次生成器或者用pathInfo把不同文件类型定向到对应模块的源码目录。packageConfig的pathInfo是 XML 文件输出路径的关键配置。很多人在packageConfig里只配置了 Java 包名结果 XML 文件被生成到 Java 源码包里虽然 Spring Boot 能扫描到 classpath 下的 XML但如果你把 Mapper XML 放在com/example/demo/mapper目录下名字又跟接口一致MyBatis 是能自动找到的可是这会让源码目录很乱。我更倾向于把 XML 统一放src/main/resources/mapper这时就需要用pathInfo指定OutputFile.xml的绝对路径。strategyConfig里的addTablePrefix(sys_)非常实用。它会把表名前缀去掉再生成实体名比如sys_user表生成User实体而不是SysUser。这符合 Java 类命名的直觉也避免把数据库前缀污染到业务层。如果你的项目里不同业务模块有不同的表前缀比如order_和user_可以用addTablePrefix(order_, user_)一次配多个。logicDeleteColumnName和versionColumnName这两个配置容易让人看名字犯迷糊。前者指定逻辑删除字段名生成实体时会给该字段加上LogicDelete注解之后 MyFlex 的删除操作会变成UPDATE set deleted 1而不是物理DELETE后者是乐观锁版本字段实体上会加Version注解更新时会自动带上version version 1的条件。这两个东西在业务系统里太常用了但很多人都是等到项目上线之后才想起来要加那时候老数据已经写乱了。生成器阶段就配好等于给所有新表统一了规范。3.3 表结构的设计会影响生成结果先想清楚这几件事代码生成器只是忠实地把数据库表结构翻译成 Java 代码所以表结构设计得越规范生成结果越省心。先说主键。MyFlex 默认把主键名id识别为自增主键生成实体时加上Id注解并使用KeyType.Auto。如果你的表主键不叫id比如叫user_id需要在strategyConfig里通过entityBuilder().addTableIdAnnotation()之类的方式额外处理或者生成后手动调整实体。我建议新表一律用id作单主键字段名这是 MyFlex 最顺滑的使用方式也是很多团队的实际约定。再说字段类型映射。数据库的int、bigint、varchar这些常见类型MyFlex 都能正确映射到 Java 的Integer、Long、String。但datetime和timestamp的映射比较有意思默认会映射成LocalDateTime前提是你的globalConfig里设置了dateType(DateType.TIME_PACK)。如果没有显式设置可能会生成java.util.Date。在 Spring Boot 项目里我强烈建议用LocalDateTime跟 Jackson 的序列化配合更好也避免Date那套时区陷阱。MyFlex 的默认行为其实已经很现代了但最好还是显式写清楚防止同事复制配置时漏掉。还要注意tinyint(1)这种字段。MySQL 里常用它表示布尔值MyFlex 默认会映射成Boolean这通常符合预期。但如果某个tinyint字段其实是想存 0-9 的状态码那就需要在表设计时就把它定义成tinyint(3)或者int否则生成代码里会出现Boolean类型的状态字段业务逻辑写起来非常别扭。4. 生成后的代码如何无缝接入业务开发我总结了三个落地姿势4.1 实体类带注解但不带“历史包袱”列名变更可以安全重构很多人关心生成代码质量怎么样、要不要大改。以我实际生成后的经验看实体类基本不需要改尤其是用了 Lombok 之后实体类里只有字段声明和注解干净得像一张表结构说明。Data Table(sys_user) public class User { Id(keyType KeyType.Auto) private Long id; Column(user_name) private String userName; private String email; LogicDelete private Integer deleted; Version private Integer version; Column(created_at) private LocalDateTime createdAt; Column(updated_at) private LocalDateTime updatedAt; }这个实体类可以直接作为业务对象传递也可以配合 MyFlex 的 QueryWrapper 做条件查询。如果以后表里的某个字段改名了比如user_name改成nickname只需要同步改实体类里的Column注解和字段名所有通过UserTable.COLUMN_USER_NAME引用它的地方都会在编译期暴露出来不用再靠运行时日志去定位 SQL 错误。这一点在维护老系统时尤其值钱。4.2 Service 层继承了哪些通用能力能省掉多少模板代码MyFlex 的BaseService接口里提供了相当丰富的通用方法。我挑几个实际项目中用得最多的说。getById和removeById是最基础的对应主键查询和删除。list能根据 QueryWrapper 返回列表数据page配合分页插件直接产出分页结果。批量操作方面有saveBatch和updateBatchById内部按批次提交比循环单条操作快不少。链式查询更是深得我心写复杂条件时像在写流式 APIListUser users userService.queryChain() .eq(UserTable.COLUMN_STATUS, 1) .like(UserTable.COLUMN_USER_NAME, 张) .orderByDesc(UserTable.COLUMN_CREATED_AT) .list();这段代码可读性极高。要是手写 XML类似查询得写一大段where、if还要考虑空值判断用 QueryWrapper 表达式条件直接拼在 Java 侧MyBatis 侧就干干净净。Controller 层我建议保留但要根据团队习惯微调。enableRestStyle会让生成的方法使用PostMapping、PutMapping、DeleteMapping这类 RESTful 注解生成的方法名也变成add、update、delete而不是save、updateById。如果你的前端团队约定统一走POST /api/user/save这种路径也可以不用enableRestStyle看各自习惯。4.3 自定义查询放哪儿直接在 Mapper 里加方法不要破坏生成骨架生成器生成的 Mapper 接口是空的只继承BaseMapper。很多人第一次看到这种情况会怀疑啥都能不带那我自定义查询怎么写答案很简单直接在接口里加方法。public interface UserMapper extends BaseMapperUser { Select(SELECT * FROM sys_user WHERE email #{email}) User selectByEmail(String email); }如果是复杂查询可以建一个单独的 XML 文件放在src/main/resources/mapper下文件名跟 Mapper 接口一致namespace 指向接口全限定名。这里有个实用经验不要在生成器生成的 XML 基础上去追加内容因为这个文件在重新生成时会被覆盖。正确做法是把自定义 SQL 写在一个独立命名的 XML 里比如UserCustomMapper.xml或者干脆用注解 SQL。用注解虽然字符串拼接不舒服但对于三五行的小查询比建 XML 文件轻量多了。MyFles 还有一个值得说的点它支持在BaseMapper里定义selectListByQuery这类方法参数是QueryWrapper返回值是实体列表。这种设计避免了“每个自定义条件都要写一条 SQL”的尴尬。比如我要按状态和角色过滤用户不用写 XML直接构造 QueryWrapper 传给通用方法就行。只有那种多表 JOIN、子查询、聚合统计才需要正经写 SQL。5. 那些生成后才发现的问题我把踩过的坑做了个清单5.1 表前缀处理不当导致实体类名对不上业务术语表前缀要是不处理sys_user表生成SysUser实体sys_role表生成SysRole实体小项目看着还行但业务模块一多实体命名各种前缀看着很乱并且跟开会时口头讨论的“用户实体”“角色实体”对不上。处理方式有两种一种是像我前面那样用addTablePrefix在前缀阶段就去掉另一种是用entityBuilder().formatFileName(%sEntity)之类的方式统一加后缀。我更建议第一种因为它让实体名直接对应业务概念。如果你的历史表没有统一前缀也可以考虑第二种至少保证生成文件命名规整。5.2deleted字段的两种状态含义别让逻辑删除变成逻辑负担逻辑删除字段配置好了之后MyFles 会在所有默认查询方法上自动追加deleted 0条件。这听起来很美好但有一个副作用如果你有一张表确实需要物理删除比如日志表、流水表也配了逻辑删除字段那这张表会越积越胖而且没法用 DELETE 清掉只能用update ... set deleted 1这种模拟删除。我的经验是核心业务表适合逻辑删除数据量大或者有清理需求的历史表、日志表不要加逻辑删除字段。代码生成器配置放在strategyConfig里是全局生效的如果你希望只对部分表启用逻辑删除需要在生成后手动把某些表的实体类去掉LogicDelete注解或者在生成前对表清单做区分。用addInclude精确控制生成范围时可以分两批跑一批带逻辑删除配置一批不带。5.3 数据库保留字作为字段名时生成代码立刻崩有一次我生成一张订单扩展表表里有个字段叫order结果生成的实体类一编译就报错。order在 MySQL 里是关键词作为列名必须用反引号括起来MyFles 生成实体时不会自动处理这层。解决方案有两个。一个是建表时避开保留字把字段改成order_no、order_status之类的命名另一个是在 SQL 里用反引号包住列名但这样生成器解析元数据时依然可能出问题。最省心的还是改表结构让字段名跟 Java 关键字、SQL 关键字都保持距离。类似的雷还有desc、user在 MySQL 里 user 算特殊、type在某些数据库里是保留字。建议新表起字段名之前先过一眼数据库保留字清单。5.4 重新生成代码时手工修改全被冲掉怎么办这个问题几乎每个用过生成器的人都遇到过。我的建议是把生成器当作“一次性脚手架”生成完代码提交到版本库之后这些文件就应该进入正常的代码评审和维护流程不要反复跑生成器去覆盖它们。如果表结构确实改了更稳的做法是手动改实体类、Mapper 和 Service 的增量部分而不是重新生成整个文件。如果改动很多、必须重新生成可以在生成前把手工修改的内容提取到独立文件里比如自定义查询放进单独的CustomMapper接口或 XML业务逻辑放进单独的 Util 或者 Service 子类。保持生成器产物和手工代码分离是从根上避免冲突的唯一办法。这算是所有代码生成器的通病MyFles 也不例外但它的骨架设计已经让冲突面小了很多。5.5 生成器跑完项目启动报“Invalid bound statement”的排查链路这是个高频报错症状是启动时提示某个 Mapper 方法找不到对应 SQL。我第一次遇到时慌了一下后来总结出固定的排查顺序。先看 Mapper 接口是否被 Spring Boot 扫描到。如果接口没加Mapper注解且启动类上没有MapperScanMyBatis 根本不会注册这个 Mapper。MyFles 的 starter 通常会自带MapperScan但如果你的项目同时用了别的 MyBatis 增强框架扫描路径可能冲突。建议在启动类上显式加MapperScan(com.example.demo.mapper)省得依赖默认行为。再看 XML 文件是否在 classpath 下。src/main/resources/mapper下的 XML 默认会被打包进 classpath问题往往出在pathInfo配错XML 被生成到了src/main/java下。这时如果你没有配置mybatis.mapper-locationsMyBatis 默认只会扫描classpath*:mapper/*.xml自然找不到。最后看 namespace 和 statement id。XML 的 namespace 必须等于 Mapper 接口的全限定名方法 id 必须等于接口方法名。用生成器时一般不会错但如果 XML 是手写的很容易把 id 拼错。6. 模板定制当默认产物满足不了你的团队规范时6.1 认识 MyFles 的模板机制和三个核心变量MyFles 的生成器是基于模板引擎渲染的默认模板文件在依赖 jar 包里。你可以通过重写模板来改变生成代码的样式。这听起来有点重但实际很值得做尤其是团队有自己统一的代码规范时。模板里最常见的三个变量是entity表示实体类元信息包含表名、类名、字段列表mapper表示 Mapper 接口元信息package表示包名配置。不同类型的文件对应不同的模板文件比如实体模板里会遍历字段列表输出每个字段的注解和属性声明。如果我需要给实体类统一加上“创建人”“创建时间”的审计注解而不是每个 Entity 里手工加就可以定制实体模板在生成时自动追加。这个思路非常现代化等于把团队的代码规范固化到脚手架里。6.2 一个定制示例把 Controller 统一改成“父类 子类”模式假设你们团队的 Controller 规范是所有接口都继承一个BaseController里面统一处理参数校验、日志埋点和统一返回结构。默认生成的 Controller 是独立的类没有继承关系。这时可以定制 Controller 模板让它生成以下结构RestController RequestMapping(/api/user) public class UserController extends BaseControllerUserService, User { Autowired private UserService userService; // 基础 CRUD 端点由 BaseController 统一提供 // 子类只需要补充特有接口 }这个定制的好处很明显新模块的 Controller 代码量极少基础端点完全是统一实现接口风格一致后续维护成本也低。实际做的时候你需要找到 MyFles 生成器 jar 包里的controller.java.ftl模板复制出来按照上述结构改一遍然后在生成器配置里指定自定义模板路径。第一次改模板可能需要一两个小时但改完以后每个新模块都享受这个红利绝对值。6.3 模板定制的边界什么时候该停手模板定制是有边界的。我不建议把太重的业务逻辑写进模板比如生成器自动生成复杂的分页查询、多条件动态筛选这些应该靠 MyFles 的 QueryWrapper 在运行时解决而不是靠生成代码写死。模板里只应该做结构化的事情类名、包名、继承关系、注解、基础方法定义。业务逻辑写进模板等于把代码生成器变成了业务代码库的“上帝模式”改模板就跟改全部业务一样危险后患无穷。我习惯把模板分成两层底层模板负责“整形的骨架”比如实体类、Mapper、Service 接口业务层模板不要动留给开发人员通过继承和组合去扩展。这样刷新代码生成器时影响范围可控团队协作也不会被模板改动牵着走。7. 生成之后的代码审查要注意什么别直接合并代码生成器减少了重复劳动但不代表生成产物可以不经审查直接合入。我团队里有个规矩凡是用生成器产出的代码必须经过一次人工审查重点看三个方面。第一字段类型是否符合业务语义。比如数据库里amount字段被定义成decimal(10,2)MyFles 会映射成BigDecimal这是正确的但如果有人建表时偷懒用了double生成的就是Double金额计算会出现浮点误差这种问题不在生成器而在表结构审查时要能看出来。第二是否有遗漏的索引或唯一约束。代码生成器只会读取表结构不会读索引也不会帮你在 Service 层补唯一性校验。比如用户表要求邮箱唯一表上建了唯一索引但生成代码里的新增方法只是简单 insert如果业务层需要友好的“邮箱已被注册”提示得自己补查询判断。这个逻辑生成器给不了你只能靠审查时识别。第三是否默认启用了逻辑删除却忘记考虑统计需求。如果某张表的业务上需要被count而被逻辑删除的数据很多统计结果会偏小。这种情况要么去掉逻辑删除要么在统计 SQL 里显式忽略删除标记别依赖生成器的全局默认。审查这件事听起来无聊但它是保证后续开发质量的关键环节。很多人觉得生成代码就可以闭眼用结果上线后数据被误删、统计对不上问题反而比手写代码更难排查。花五分钟过一遍生成产物比事后修 bug 划算得多。8. 实际项目里怎么落地从几张表扩展到整个新模块开发流程最后聊点落地的体会。我们团队后来规定凡是新的业务模块必须走“建表 - 跑生成器 - 人工审查 - 提交骨架代码 - 业务增量开发”这个流程。这个流程跑顺以后新模块的第一个提交通常几乎是同一套骨架评审变得很快开发重点自然落到真正的业务规则上。有个细节要注意跑生成器的时机。我见过有同事表结构还没定稿就先生成代码然后数据库改一个字段代码又要重新生成来回好几次。最好等表结构评审通过、建表脚本在测试库执行成功后再跑生成器一次成型。另外代码生成器最好由一个人统一维护配置和模板其他人直接使用统一入口不要每个人都写一套生成器配置那会让产物风格走样又变成一种“规范分裂”。如果你是用多模块工程建议把生成器放在独立的tools模块或者独立的工程目录里它不该属于某个业务模块避免业务模块的构建逻辑被生成器连带影响。我们内部用的是一个独立的 Maven 工程里面只放生成器配置、自定义模板和数据库连接信息跑完生成直接输出到指定模块的源码目录。这样生成器和业务代码的边界非常清楚。如果你还没在项目里引入代码生成器我的建议是不要一开始就想把所有表都塞进去跑一遍。挑两张有代表性的表比如一张用户表、一张订单表先把配置调通把生成产物看一遍再决定要不要全面铺开。毕竟所谓自动生成生成的只是起点真正的设计和实现还是掌握在开发者手里。工具能替我们敲键盘但不能替我们思考把这两者的边界划清楚项目才会既高效又可靠。