news 2026/10/27 8:59:41

JDBC元数据驱动批量生成MyBatis Mapper XML

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JDBC元数据驱动批量生成MyBatis Mapper XML

简介:面向Java开发与数据集成场景的XML自动生成工具包,适合需要把数据库查询结果快速转成结构化XML的开发者,用于减少手写标签、嵌套关系和格式校验中的重复劳动。压缩包共4个文件,由exe主程序、ini与properties配置以及xml配置示例组成,整体约144KB,体积轻便,便于随项目携带和快速试用。资源围绕JDBC数据源展开:配置数据库连接参数与SQL后,工具可执行查询并将结果集映射为XML元素,同时预留根元素、命名空间、编码格式等自定义入口,也可作为生成XSD验证文件的辅助参考。已有2354人学习下载,说明其在数据库导出、接口报文准备和配置文档生成等场景中具有一定参考价值。对于希望梳理JDBC取数到XML落盘流程、减少手工拼装出错的中级Java开发者,这份工具包提供了可直接运行和按需调整的实践样本。

1. 接手老库时,最该自动化的就是那批 xml

上周帮一个朋友看他们后台的活:库是七八年前建的,四十多张表,产品临时要加一套管理端,晚上就得把映射层的 xml 铺出来。他打开第一张表开始复制粘贴改字段名,改到第三张手就开始抖了——字段名一个字母写错,启动不报错,跑起来查询全是 null,这种 bug 查起来最费时间。

xml 文件自动生成工具要解决的正是这段纯体力活:你给它一个 JDBC DataSource,它连上去把表名、字段、类型、主键、注释、可空性全读出来,按模板批量生成能直接用的映射 xml,顺带把实体属性名和 jdbcType 一起对齐。它适合两类人:手上有一堆存量表要补持久层代码的,以及想给团队做一套统一代码脚手架、不想每个人手写风格各异的 sql 的。下面按我拆这套东西的顺序讲,从生成物长什么样,一路讲到怎么改造成你们自己的。

2. 先看清生成物:一份 xml 的字段从哪来,工具边界在哪

很多人生成器用不起来,不是工具不好,是没先想明白「它该产出什么」。这套工具的定位很窄:读库结构,产出映射层那批机械重复的文件。它不生成业务 SQL,不猜你的查询条件,也不做分页——那部分永远是人写的。把边界先划清之后再谈技术选型。

2.1 生成物长什么样:拿一张订单表当样本

假设库里有一张t_order,字段是order_id(bigint 自增主键)、order_no(varchar(32))、amount(decimal(12,2))、created_at(timestamp)。跑完生成器,落到src/main/resources/mapper/OrderMapper.xml的东西大概是这样:

<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="com.example.mapper.OrderMapper"> <!-- 字段与属性的映射,由 COLUMN_NAME 转驼峰得到 --> <resultMap id="BaseResultMap" type="com.example.entity.Order"> <id column="order_id" property="orderId" jdbcType="BIGINT"/> <result column="order_no" property="orderNo" jdbcType="VARCHAR"/> <result column="amount" property="amount" jdbcType="DECIMAL"/> <result column="created_at" property="createdAt" jdbcType="TIMESTAMP"/> </resultMap> <!-- 基础列清单,供 include 复用 --> <sql id="Base_Column_List"> order_id, order_no, amount, created_at </sql> <!-- 主键查询:条件字段来自 getPrimaryKeys 的结果 --> <select id="selectByPrimaryKey" resultMap="BaseResultMap"> select <include refid="Base_Column_List"/> from t_order where order_id = #{orderId,jdbcType=BIGINT} </select> <!-- keep:custom:start --> <!-- 手写 SQL 写在这两行标记之间,重新生成时原样保留 --> <!-- keep:custom:end --> </mapper>

这份产物里每个信息都有出处,不是拍脑袋写的:column来自COLUMN_NAME,property是列名转驼峰,jdbcType来自DATA_TYPE对上java.sql.Types常量,type和namespace来自命名策略配置,<id>标签的位置由主键决定,末尾那对keep标记是给二次生成留的保留区。看清楚这层对应关系,后面改模板、改命名规则你就知道该动哪一行。反过来,如果生成器产出里出现你解释不了的内容,那基本就是模板里写死了不该写死的东西。

2.2 为什么走 JDBC DataSource 而不是解析建表 SQL

最省事的思路好像是让开发把schema.sql丢过来,正则扫一遍CREATE TABLE就完事了。我一开始也这么想,后来被现实教育了三次:DDL 文件经常跟线上库不同步,改了字段没人更新文件;不同人导出的 DDL 格式千奇百怪,COMMENT有的在行尾有的单独一句;联合主键和索引在 DDL 里能解析,但自增属性、字符集、可空性经常被导出工具吃掉。

走 JDBC 的好处是拿到的永远是「数据库自己认为的事实」。DatabaseMetaData是 JDBC 规范里的一部分,任何驱动都得实现,所以工具天然支持多种库:换库只换 DataSource 和连接串,元数据读取的代码一行不用动。这也是关键词里jdbc和datasource放在一起的原因——数据源是入口,元数据是原料,两者凑齐了整个工具就不需要解析任何文本格式的 DDL。

另一个隐性收益是它天然支持「连到哪个环境就生成哪个环境的结构」。测试库和预发库字段不一致是常态,工具支持直接指库,你就不用去猜哪份 DDL 文件是最新的。

2.3 工程结构与技术栈清单

整个仓库的结构很扁平,不搞多层抽象,因为这类工具一旦抽象过头,改个命名规则要翻五个类,没人愿意维护:

目录/文件职责
src/main/java/.../DataSourceFactory.java从配置构造 DataSource,处理连接池与连接复用
src/main/java/.../MetaReader.java封装DatabaseMetaData的四类读取,产出TableMeta
src/main/java/.../TypeMapper.javaJDBC 类型到 Java 类型、jdbcType 的映射表
src/main/java/.../NamingStrategy.java表名到类名、列名到属性名、包路径的转换规则
src/main/java/.../TemplateEngine.java极简占位符渲染(${}替换 + 片段循环)
src/main/java/.../Generator.java串起读取、渲染、落盘,处理覆盖与保留区
src/main/resources/template/*.tplmapper、entity、mapper 接口三套模板
config.yaml数据源、目标包名、表过滤、覆盖策略

技术栈就是 Java 8 以上加 JDBC,构建用 Maven 的 pom 结构,配置解析用 YAML,模板这块内置了一个三十来行的占位符渲染器,不强依赖任何模板引擎。这个选择是有意的:模板引擎功能强但学习成本高,而这里需要的只是「把字段列表循环出来」,${}加一个#foreach风格的块就够。想换成成熟模板引擎,TemplateEngine是个接口,实现类替换掉即可,这是后面二次开发里最常被动的扩展点之一。

3. 核心链路拆解:一个 DataSource 如何变成一批 xml

中间这条链路只有四步:拿连接、读元数据、转类型、渲染落盘。每一步都有几个能让人卡半天的细节,尤其是第一步和第二步,很多「生成出来是空文件」「表名对不上」的问题都出在这里,而不是模板写错。

3.1 数据源接入:三种传法,选能复用连接的那种

工具支持三种数据源接入方式:直接给 url/username/password 让它自己建、接收外部传进来的DataSource实例、从 JNDI 名字查。日常用得最多的是前两种。第一种方便单人跑一次,第二种适合塞进构建流程或者做成 IDE 插件。

关键点是别在循环里反复DriverManager.getConnection。四十张表就是四十次建连,MySQL 上还算快,Oracle 上足够让你怀疑人生,中间某次网络抖动还会直接中断整批任务。正确做法是先构造一个连接池 DataSource,整个生成过程共用一个池,表级操作各取各的连接并保证归还。

/** * 构造生成器用的 DataSource。 * 表数量多时这里必须是池化的,单连接串会在中途超时断掉。 */ public static DataSource build(GenConfig cfg) { HikariConfig hc = new HikariConfig(); // 换成其他池实现同理 hc.setJdbcUrl(cfg.getUrl()); hc.setUsername(cfg.getUsername()); hc.setPassword(cfg.getPassword()); hc.setMaximumPoolSize(4); // 生成任务并发不高,4 个足够 hc.setConnectionTimeout(10_000); hc.setReadOnly(true); // 生成器只读,顺手防手滑 return new HikariDataSource(hc); }

setReadOnly(true)这一行是血泪经验:工具只该拿元数据,不该有任何写操作的可能,把连接标成只读,就算后面有人手贱在模板里拼了update也会被驱动拦下来。maximumPoolSize不用给大,元数据查询都是毫秒级的,4 个连接跑五十张表也就几秒。

还有一层是配置里的密码处理。别把生产库密码明文提交到仓库,常见的做法是支持${env:DB_PASSWORD}这种占位符,运行时从环境变量注入;如果要做成团队工具,再加一个「只允许连非生产库」的白名单校验,见过太多人心急之下连错库,对生产库执行元数据读取虽然不写数据,但也不该成为习惯。

3.2 元数据抓取:四个 DatabaseMetaData 调用撑起整张表结构

这是整个工具最核心的三十行。DatabaseMetaData提供的方法很多,真正用得上的就四个:getTables拿表清单,getColumns拿字段,getPrimaryKeys拿主键,getImportedKeys拿外键(如果模板要生成关联查询才需要)。

/** * 读取单表结构:字段、主键、可空、自增、注释、精度。 * catalog / schema 的语义在不同数据库上不一样,必须由调用方传准。 */ public TableMeta read(Connection conn, String catalog, String schema, String table) throws SQLException { DatabaseMetaData md = conn.getMetaData(); TableMeta meta = new TableMeta(table); // ① 列信息:类型、长度、精度、可空、自增、注释都在这一个结果集里 try (ResultSet rs = md.getColumns(catalog, schema, table, "%")) { while (rs.next()) { ColumnMeta c = new ColumnMeta(); c.setName(rs.getString("COLUMN_NAME")); c.setJdbcType(rs.getInt("DATA_TYPE")); // 对应 java.sql.Types 的常量 c.setSize(rs.getInt("COLUMN_SIZE")); // varchar 长度、decimal 总位数 c.setScale(rs.getInt("DECIMAL_DIGITS")); // 小数位 c.setNullable(rs.getInt("NULLABLE") == DatabaseMetaData.columnNullable); c.setAutoIncrement("YES".equalsIgnoreCase(rs.getString("IS_AUTOINCREMENT"))); c.setRemark(rs.getString("REMARKS")); // 驱动默认可能返回空串,见第 5 章 meta.addColumn(c); } } // ② 主键:联合主键会有多行,KEY_SEQ 决定字段顺序,不能按结果集顺序拍脑袋 try (ResultSet rs = md.getPrimaryKeys(catalog, schema, table)) { while (rs.next()) { meta.addPk(rs.getString("COLUMN_NAME"), rs.getShort("KEY_SEQ")); } } return meta; }

逐段说明。getColumns的第一个参数catalog、第二个参数schema是最容易翻车的地方,它们在不同库上语义完全不同:MySQL 上catalog是库名、schema传 null;Oracle 上catalog传 null、schema是用户名(且要大写);PostgreSQL 上catalog是库名、schema一般是public。所以配置里我一般让用户显式写catalog和schema两个字段,而不是猜着填。

DATA_TYPE返回的是java.sql.Types里的整型常量,不是字符串类型名。想要数据库自己的类型名(比如bigint unsigned、jsonb),得读TYPE_NAME。两者都要拿:DATA_TYPE用来决定 Java 类型和 jdbcType,TYPE_NAME用来处理那些标准类型覆盖不了的特例。

KEY_SEQ这个细节坑过不少人。复合主键在结果集里不是按建表顺序返回的,必须按KEY_SEQ排一遍再写进<id>标签,否则生成的where条件顺序和索引顺序对不上,某些库上会走不到索引。

3.3 类型映射表:从 JDBC Type 到 Java 类型与 jdbcType

元数据读回来是数字,模板里需要的是String、Long、BigDecimal、TIMESTAMP这些。中间靠一张映射表,这张表是整个工具最该「可配置」的部分,因为各家团队的实体类型习惯差别很大:有人全用Integer,有人必须Long,时间字段有人用Date有人用LocalDateTime。

JDBC Type数据库类型示例Java 类型(默认)xml 里的 jdbcType
Types.BIGINTbigintLongBIGINT
Types.INTEGERintIntegerINTEGER
Types.SMALLINT/TINYINTsmallint / tinyintIntegerSMALLINT/TINYINT
Types.DECIMAL/NUMERICdecimal(12,2)BigDecimalDECIMAL
Types.VARCHAR/CHARvarchar(32)StringVARCHAR
Types.TIMESTAMP/DATETIMEdatetimeLocalDateTimeTIMESTAMP
Types.DATEdateLocalDateDATE
Types.BIT/BOOLEANtinyint(1) / booleanBooleanBIT
Types.LONGVARCHAR/CLOBtext / longtextStringLONGVARCHAR

映射代码本身很短,重点是「允许覆盖」:

/** 查表,查不到就走兜底,兜底一定要打日志,不然新类型出来全是 String 没人发现 */ public TypeMapping resolve(ColumnMeta col) { TypeMapping m = overrides.get(col.getJdbcType()); // 配置文件里的覆盖优先 if (m == null) { m = defaults.get(col.getJdbcType()); } if (m == null) { log.warn("未识别的 JDBC 类型 {} (列 {},库内类型 {}),已回退为 String", col.getJdbcType(), col.getName(), col.getTypeName()); return TypeMapping.STRING; } return m; }

这段里的overrides来自 YAML,让团队能在不改代码的前提下把TINYINT映射成Integer而不是Boolean。那个log.warn是必须留的:数据库版本升级冒出个没见过的类型,没有这条日志,你只会看到实体里一堆莫名其妙的String字段,然后花两小时往回找。

TYPE_NAME在这里派上用场。比如 MySQL 的tinyint(1)和tinyint(4)在DATA_TYPE里都是TINYINT,但业务含义完全不同——前者通常是布尔,后者是数量。判断方法是看COLUMN_SIZE是否为 1,或者直接看TYPE_NAME里有没有带(1)。这类特例就该写在TypeMapper里,别让它扩散到模板层。

3.4 模板渲染与落盘:命名策略决定你能不能直接用

最后一步是把TableMeta塞进模板,渲染成字符串,写到磁盘。这里有一个隐藏的设计决定:生成的文件放哪、叫什么、包名怎么定,全由NamingStrategy决定,跟模板无关。这么做的好处是命名规则可整体替换,比如有些团队 mapper 接口和 xml 同名同包,有些团队 xml 统一放resources/mapper下按模块分目录。

/** 表名 → 类名:去掉常见前缀,下划线转驼峰,首字母大写 */ public String toClassName(String table) { String t = table; for (String p : stripPrefixes) { // 配置项,如 t_、tbl_、biz_ if (t.startsWith(p)) { t = t.substring(p.length()); break; } } return upperFirst(toCamel(t)); // t_order -> Order } /** 列名 → 属性名:order_no -> orderNo */ public String toPropertyName(String column) { return toCamel(column.toLowerCase()); }

stripPrefixes这个配置项看着不起眼,实际决定了生成物能不能直接用。老库里t_、tbl_、biz_前缀混着来,不剥掉,生成的类名就是TOrder、TblUser,谁看了都想重写一遍。配置里给它一个数组,按顺序匹配,命中第一个就停。

落盘环节要处理的就三件事:目录不存在就建、文件已存在按策略处理、写完打一行日志。覆盖策略这块值得单独说,它直接关系到「敢不敢重复跑」:skip存在就跳过,overwrite直接覆盖,merge覆盖时保留保留区内容。默认值我一般设成merge,理由在最后一章的后悔药设计里细说。

4. 配置与命令行:把生成器塞进日常开发流程

工具能不能活下去,取决于它跑起来有多顺手。如果一个生成器需要打开 IDE、改代码、点运行,那它用两次就会被丢在角落。可行的形态是:一份 YAML 配置 + 一条命令行,在项目根目录敲一下,几秒钟出结果。

4.1 配置文件全量字段说明

配置结构照下面这个来,字段不多,但每个都别省:

datasource: url: jdbc:mysql://127.0.0.1:3306/demo?useInformationSchema=true username: dev password: ${env:DB_PASSWORD} # 从环境变量注入,别写明文 catalog: demo # MySQL 库名;Oracle 填 null schema: null # PostgreSQL 填 public;Oracle 填用户名大写 generate: outputDir: ./src/main/resources/mapper entityDir: ./src/main/java/com/example/entity basePackage: com.example templates: mapper: template/mapper.tpl entity: template/entity.tpl stripPrefixes: [t_, tbl_, biz_] overwrite: merge # skip | overwrite | merge tables: include: ["t_order", "t_user", "t_*"] exclude: ["t_log_*", "*_bak"] typeOverrides: - jdbcType: 5 # java.sql.Types.SMALLINT javaType: Integer

逐个说清楚,因为这里每个字段都能引发一类问题:

字段作用常见误填
urlJDBC 连接串忘了加驱动侧参数,导致注释读不到
catalog/schema元数据查询的定位参数按 MySQL 习惯填到 PostgreSQL 上,结果查出 0 张表
basePackage生成类与 namespace 的包前缀与实际工程包名不一致,编译期才发现
stripPrefixes剥表名前缀不配,生成一堆TOrder
overwrite已存在文件的处理策略误设overwrite,手写 SQL 被冲掉
include/exclude表过滤,支持通配只写 include 不写 exclude,日志表、备份表全进来
typeOverrides覆盖默认类型映射用类型名而不是Types常量值,匹配不上

useInformationSchema=true这个参数单独提一句:MySQL 驱动默认不返回表注释和列注释,REMARKS全是空串。加上这个参数,注释才会从information_schema里读出来。Oracle 那边的对应参数是remarksReporting=true。这个参数忘了加,生成的实体里没一个字段有注释,等于白生成。

4.2 命令行参数与批量/增量生成

命令行入口不用引框架,手撸一个args解析就够了:

# 全量生成:读 config.yaml,把 include 命中的表全部铺一遍 java -jar xml-gen.jar -c config.yaml # 只生成指定的几张表,改配置麻烦的时候用这个 java -jar xml-gen.jar -c config.yaml -t t_order,t_user # 试运行:只打印将要写哪些文件,不落盘 java -jar xml-gen.jar -c config.yaml --dry-run # 增量:只处理最近修改过的表(靠 information_schema 的 UPDATE_TIME,部分库不支持) java -jar xml-gen.jar -c config.yaml --since 2024-06-01

--dry-run这个参数看着多余,实际是我用得最多的一个。老库上跑第一次之前,先 dry-run 看一眼「到底要生成多少个文件、落在哪些目录」,确认没问题再真跑。省下的不是时间,是那点「万一它把我 src 目录写脏了」的心理负担。

-t参数解决的是迭代期的高频场景:加了一张表,只想补这一张的 xml。虽然配置里改 include 也行,但改配置文件会污染版本库,命令行临时指定干净得多。

批量生成的并发要克制。我试过开 8 个线程并行读元数据,理论上更快,实际在测试库上直接把连接池打满,几个查询超时重试,最后总耗时比串行还长。表数量在几百张以内的,串行加复用连接完全够用,别过早优化。

4.3 生成物接入工程:目录约定与一次冒烟验证

生成完不等于能用,得跑一遍验证。第一步是看文件落位对不对:xml 在resources/mapper下,实体在entity包下,mapper 接口在mapper包下,三者包名与 namespace 对得上。第二步是启动应用,看持久层框架加载 mapper 时有没有报「xml 解析失败」或者「namespace 重复」。

第三步最关键——跑一个真实的selectByPrimaryKey。新建一张只有几条数据的测试表,跑一遍生成,写个测试用例:

@Test void 生成的主键查询能跑通() { Order o = orderMapper.selectByPrimaryKey(1001L); assertNotNull(o); // 空表建议先插一条 assertNotNull(o.getOrderNo()); // 字段映射对不上时这里会是 null assertNotNull(o.getCreatedAt()); // 时间类型最容易在映射上翻车 }

这三个断言分别验证三件事:主键条件拼对了、字段名与属性名映射对了、类型转换没出问题。见过太多次「启动不报错、查询返回全 null」,就是resultMap里的column写成了数据库里不存在的名字,这种错只有真查一次才会暴露。

提示:把这条测试用例写进项目里,别用临时脚本。以后每次重跑生成器,跑一遍测试就知道有没有把东西改坏,比人眼比对强得多。

5. 避坑与排查:五个真跑起来才会撞上的问题

这套工具的代码量不大,但踩坑密度极高,因为它的输入是「别人家的数据库」。下面五条是我自己撞过、并且每次都还会在别人身上看到的,按现象、原因、解决写清楚。

5.1 生成的实体一个注释都没有

现象:文件生成了,字段、类型都对,就是每个属性上面干干净净,REMARKS全空。

原因:不是所有 JDBC 驱动默认都返回注释。MySQL 要连接串上加useInformationSchema=true,Oracle 要加remarksReporting=true,PostgreSQL 相对友好但要求表上确实有COMMENT ON。驱动层没开这个开关,getColumns返回的REMARKS就是空串,代码再对也读不出东西。

解决:在DataSourceFactory里按 URL 前缀判断数据库类型,自动补上对应参数,别指望使用者每次都记得。同时加一条启动日志,把「本次是否启用了注释读取」打出来,省去后面猜的时间。另外注意REMARKS可能返回null而不是空串,写模板前统一做一次null转空处理,不然模板里会渲染出字面的null四个字母。

5.2 明明有表,却一张都没匹配上

现象:跑完日志显示「匹配到 0 张表,生成 0 个文件」,连上库手动show tables明明有几十张。

原因:九成是catalog/schema填错,或者大小写对不上。同一个参数名在不同库上含义不同,MySQL 里库名对应catalog,Oracle 里用户对应schema且默认全大写,PostgreSQL 里schema通常是public。有的人从 MySQL 迁到 PostgreSQL,配置原样照抄,catalog填了库名但schema传了 null,getTables就返回空。

解决:加一个自检步骤——先调一次getTables(catalog, schema, "%", null)拿全量,拿不到就打印「当前连接看到的 catalog 列表和 schema 列表」,让使用者照着填。同时把getTables的返回结果打进 debug 日志,出问题时能立刻判断是「参数不对」还是「过滤规则写错」。这个自检我一般做成--doctor子命令,专门用来排查连接和定位参数。

5.3 生成的 xml 一加载就报解析错误

现象:生成的文件看着很正常,应用一启动就抛异常,说某行有非法字符,或者标签不匹配。

原因:字段注释里带了特殊字符。业务方在注释里写「金额 > 100 时……」「A&B 类型」,这些<、>、&直接进 XML 就是非法字符。还有一个更隐蔽的:注释里带了--,在 XML 注释里是非法的连续短横线。这两种错在 IDE 里打开文件看不出来,因为编辑器会自动容错显示。

解决:渲染前对所有进入 XML 的文本做转义,&、<、>、"、'五个字符一个不能漏,注释里的--也要替换掉。别只转义字段值不转义注释,注释恰恰是最容易被忽略又最容易带特殊字符的地方。写个escapeXml工具方法统一处理,所有模板变量出口必须走它,包括表名、包名这些「看起来不会出错」的字段。

5.4 tinyint 全变成布尔,bigint 数字溢出

现象:实体里本该是数量的字段变成了Boolean,本该是Long的 ID 在某些地方变成了负数。

原因:两个独立的坑凑一起了。TINYINT在 MySQL 里既能当布尔也能当小整数,DATA_TYPE都是Types.TINYINT,光看类型码分不出来;bigint unsigned超出Long的正数范围时,驱动读出来会变成负数。这两个都属于「标准类型覆盖不了数据库方言」的情况。

解决:布尔判断改成看COLUMN_SIZE == 1并且TYPE_NAME带(1),两个条件同时满足才映射成Boolean,其余按Integer处理。bigint unsigned在TYPE_NAME里能看出来,映射成BigInteger;如果业务上确定不会超过Long最大值,保持Long也行,但要在映射表里显式声明,并让TypeMapper里那条 warn 日志别再触发——日志一直响等于没有日志。

5.5 重跑一次,手写 SQL 没了

现象:生成器第二次跑,之前手动加到 xml 里的复杂查询、批量插入、手写 resultMap 全被覆盖没了。

原因:覆盖策略配的是overwrite,或者虽然是merge,但手写内容不在保留区标记里。覆盖和保留区这两件事必须配套,光有策略没有标记,程序无从判断哪段是人写的、哪段是机器生成的。

解决:默认策略设成merge,模板末尾固定输出一对保留区标记,覆盖时按标记切割——标记内的内容原样搬回新文件,标记外全部用新生成的内容替换。切割逻辑要用精确匹配加锁定的行号范围,别用正则到处找起始标记,因为文件里可能出现多个同名标记(比如生成的 sql 片段里恰好也有类似注释)。改完之后,重跑生成器应该是一个「无脑操作」,不需要先 commit 一次保底。

6. 二次开发:把它改成团队自己的脚手架

接手的团队里,几乎每个人都会在第一次用完之后提一两个改造需求。这很正常,因为生成器本质上是「把团队的约定固化成代码」,约定不一样,改造点就不一样。真正需要动的地方其实只有三个。

6.1 三个稳定扩展点:类型映射、命名策略、模板

TypeMapper的overrides优先于默认表,改类型不用动代码,加一行 YAML 就行。NamingStrategy是接口,想改包路径规则、想给不同模块加不同前缀,实现一个类注册进去即可。模板是三套.tpl文件,改输出格式完全不碰 Java 代码。

我一般会加第四个扩展点:PostProcessor,在落盘前对生成内容做一次加工。典型用途是给每个 mapper 接口补一个@Mapper注解、按模块给 xml 加不同的 namespace 前缀、或者把生成时间戳写进文件头(这个慎用,会让每次生成都产生 diff)。

6.2 改造后的验证:用一份基准库做生成物 diff

改造完最怕的是「格式变了但没人发现」。可行的做法是准备一个基准库,里面刻意放上各种极端字段:带注释的、带特殊字符的、bigint unsigned的、tinyint(1)的、联合主键的、保留字做列名的(order、desc、user)。改造前后各跑一次,用 git diff 对比生成物。

保留字那条特别值得放进去,因为order这种列名不转义,生成的select order from t直接是个语法错误,而这个问题在开发库上大概率试不出来——业务表的列名恰好叫保留字,属于典型的「上线才翻车」。

一个经验:把这份基准库的建表脚本和期望生成物一起提交到仓库,做成一个跑得起来的回归测试。以后谁改了模板,跑一下测试就知道有没有影响别人,比 Code Review 里肉眼扫靠谱得多。

说到最后,我现在的习惯是:任何生成器上线前,必须先在一个「字段类型最脏」的库上跑一遍,并且把--dry-run的输出贴进文档。这条规矩是被坑出来的,之前有一次直接对着一套整洁的测试库调好模板,换到真实库上,特殊字符和保留字一起爆,改了一下午。希望帮到你,少走那段弯路。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/27 8:58:16

Blender制作蜘蛛机器人:建模、骨骼绑定与动画全流程

1. 项目概述&#xff1a;为什么选择Blender制作蜘蛛机器人 蜘蛛机器人一直是科幻作品和工程实践中的经典形象&#xff0c;其复杂的运动机制和多关节结构对建模提出了极高要求。Blender作为一款开源三维创作套件&#xff0c;凭借其完整的建模、骨骼绑定和动画系统&#xff0c;成…

作者头像 李华
网站建设 2026/10/27 8:58:02

Notepad++ 安装配置、插件管理与编码避坑指南

简介&#xff1a;Notepad 安装包面向 Windows 平台的程序员、Web 开发者及需要轻量代码编辑器的用户&#xff0c;提供可直接解压使用的安装包&#xff0c;重点包含插件管理器与 emeet 插件&#xff0c;方便在安装后扩展语法高亮、代码折叠、自动补全等编辑能力&#xff0c;并兼…

作者头像 李华
网站建设 2026/10/27 8:57:59

智能软开关在配电网中的优化配置与灵敏度分析改进

1. 项目背景与核心价值去年参与某工业园区微电网改造时&#xff0c;我们遇到了典型的有源配电网电压波动问题。当分布式光伏发电量突增时&#xff0c;线路末端电压最高飙升到1.08pu&#xff0c;传统机械开关根本无法实现毫秒级调节。这正是智能软开关(SOP)大显身手的场景——它…

作者头像 李华
网站建设 2026/10/27 8:56:59

编程尚未被解决:当代工程师的认知摩擦与生存指南

1. 这个标题不是bug&#xff0c;是当代程序员的集体心电图“编程尚未被解决”——第一次看到这行字&#xff0c;我正蹲在凌晨三点的工位上&#xff0c;咖啡凉透&#xff0c;终端里跑着第十七次失败的CI流水线&#xff0c;IDE右下角弹出新提示&#xff1a;“TypeScript类型推导超…

作者头像 李华
网站建设 2026/10/27 8:56:04

NAS跑AI怎么选?本地部署、云API与托管方案全面对比

NAS圈子里最近讨论度最高的新东西&#xff0c;应该就是 LightVela 了。一句话介绍&#xff1a;这是一个把 NAS 上的 AI 助手整体托管出去的服务&#xff0c;按月收费 45 元。很多人的第一反应都是&#xff1a;我的 NAS 本来就能跑 AI&#xff0c;为什么还要再多花这笔钱&#x…

作者头像 李华
网站建设 2026/10/27 8:55:17

AI算法测试覆盖率提升实战与关键技术解析

1. 项目背景与核心挑战测试覆盖率是衡量软件质量的重要指标之一&#xff0c;但在实际项目中&#xff0c;我们常常遇到覆盖率难以提升的困境。特别是在AI算法领域&#xff0c;由于模型本身的"黑盒"特性&#xff0c;传统的测试方法往往难以全面覆盖各种边界情况。我在最…

作者头像 李华