从单表到多模块:MyBatis-Plus-Generator 3.5.2 在微服务项目中的高级玩法与避坑指南
当你的项目从单体架构演进为微服务体系时,那些曾经得心应手的工具链往往会暴露出新的挑战。MyBatis-Plus-Generator作为持久层开发的利器,在单体项目中可能只需简单配置就能快速生成CRUD代码,但在多模块、多数据源的微服务环境下,它的使用姿势需要全面升级。
1. 微服务架构下的代码生成困境
在典型的Spring Cloud微服务项目中,我们通常会遇到以下几个核心问题:
- 数据源分散:订单服务连接order_db,用户服务连接user_db,而生成器配置需要动态适配不同数据库
- 模块化拆分:生成的Entity应该放在哪个模块?API模块还是Service模块?
- 表前缀处理:各业务线表名可能带有
order_、payment_等前缀,需要智能过滤 - DDD分层适配:如何让生成的代码符合领域驱动设计的分层规范
我曾在一个电商平台重构项目中,因为未处理好这些问题,导致生成的代码需要大量手工调整。比如支付服务的实体类被错误地生成到了API网关模块,又比如物流服务的Mapper XML路径配置错误导致运行时找不到SQL映射。
2. 多数据源环境下的生成器配置
对于多数据源场景,我们需要改造基础的生成器配置类:
public class MultiDataSourceGenerator { // 数据源配置映射 private static final Map<String, DataSourceConfig> DS_CONFIGS = Map.of( "order", buildDataSourceConfig("jdbc:mysql://127.0.0.1:3306/order_db"), "user", buildDataSourceConfig("jdbc:mysql://127.0.0.1:3306/user_db") ); public static void generate(String moduleName) { DataSourceConfig dataSourceConfig = DS_CONFIGS.get(moduleName); if (dataSourceConfig == null) { throw new IllegalArgumentException("无效的模块名称"); } FastAutoGenerator.create(dataSourceConfig) .globalConfig(builder -> { builder.outputDir(getModulePath(moduleName)); }) // 其他配置... .execute(); } private static String getModulePath(String moduleName) { return System.getProperty("user.dir") + "/" + moduleName + "-service/src/main/java"; } }关键配置项对比:
| 配置项 | 单体项目 | 微服务项目 |
|---|---|---|
| 数据源 | 固定单个数据源 | 根据模块动态切换 |
| 输出路径 | 直接指定src/main/java | 需要计算子模块的相对路径 |
| 表过滤 | 简单前缀过滤 | 需要结合业务线定制过滤策略 |
3. 多模块代码分布策略
在DDD分层架构中,代码应该按照以下原则分布:
order-service ├── order-api // 接口定义 ├── order-domain // 领域模型 └── order-infra // 基础设施对应的生成器配置需要细化:
.packageConfig(builder -> { builder.parent("com.example.order") .entity("domain.model") // 实体类放在domain层 .mapper("infra.persistence") // Mapper放在基础设施层 .service("domain.service") // 服务接口放在domain层 .controller("interfaces.rest") // 控制器放在接口层 .pathInfo(Collections.singletonMap( OutputFile.xml, getModulePath("order") + "/src/main/resources/mapper" )); })注意:在实际项目中,建议将Entity和DTO分开生成,Entity放在domain模块,DTO放在api模块
4. 高级策略配置技巧
4.1 智能表前缀处理
对于不同业务线的表前缀,可以采用动态配置:
.strategyConfig(builder -> { // 根据模块自动匹配表前缀 String tablePrefix = moduleName + "_"; builder.addTablePrefix(tablePrefix) .addInclude(getTablesByModule(moduleName)); })4.2 Lombok与Swagger集成
.entityBuilder() .enableLombok() .enableTableFieldAnnotation() .addSuperClass(BaseEntity.class) .versionColumnName("version") .logicDeleteColumnName("deleted"); .controllerBuilder() .enableHyphenStyle() .enableRestStyle() .formatFileName("%sController");4.3 自定义模板覆盖
在resources/templates目录下放置自定义模板:
templates/ ├── entity.java.ftl # 自定义实体模板 ├── mapper.java.ftl # 自定义Mapper接口模板 └── serviceImpl.java.ftl # 自定义Service实现模板配置模板引擎:
.templateEngine(new VelocityTemplateEngine() { @Override public void init(ConfigBuilder configBuilder) { // 指定自定义模板位置 configBuilder.getTemplateConfig().setCustomTemplatePaths( Collections.singletonList("classpath:/templates/") ); } })5. 常见问题解决方案
问题1:生成的代码存在循环依赖
- 现象:OrderService引用了OrderMapper,而OrderMapper又需要Order实体类
- 解决:使用
@Lazy注解或重构包结构
问题2:多模块间的类型引用
- 现象:API模块需要引用Domain模块的实体类
- 解决:配置生成器时将DTO生成到API模块:
.injectionConfig(builder -> { builder.customMap(Collections.singletonMap( "dtoPackage", "com.example.order.api.dto" )); })问题3:生成代码不符合团队规范
- 现象:生成的代码风格与团队规范不一致
- 解决:定制模板文件,或使用后置处理器:
.entityBuilder() .formatFileName("%sEntity") .addSuperClass(BaseEntity.class) .addIgnoreColumns("create_time", "update_time");在最近的一个金融项目中,我们通过定制模板实现了以下规范:
- 所有实体类继承BaseEntity
- Service接口添加@Transactional注解
- Controller统一返回Result包装类
6. 性能优化建议
当需要生成大量表时,可以考虑以下优化手段:
- 并行生成:将不同模块的生成任务并行化
List<String> modules = Arrays.asList("order", "payment", "user"); modules.parallelStream().forEach(MultiDataSourceGenerator::generate);- 增量生成:记录已生成表的MD5签名,避免重复生成
.fileOverride(fileExists -> { if (fileExists) { return checkFileChanged(fileExists); } return true; })- 缓存模板:避免每次生成都重新编译Velocity模板
.templateEngine(new CachedVelocityTemplateEngine())经过这些优化,在一个包含200多张表的项目中,生成时间从原来的8分钟缩短到2分钟以内。