- 后端
【免费下载链接】mikro-orm
TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, MariaDB, MS SQL Server, PostgreSQL and SQLite/libSQL databases.
本文基于 mikro-orm 官方文档《Naming Strategy》展开,系统讲解 mikro-orm 如何将实体类、属性名映射为数据库表名与列名:内置的三种命名策略(UnderscoreNamingStrategy、MongoNamingStrategy、EntityCaseNamingStrategy)各自的适用驱动与转换规则、NamingStrategy接口的完整 API 参考、在MikroORM.init中覆盖命名策略的方法,以及如何通过继承AbstractNamingStrategy编写符合团队规范的自定义策略。读完本文,你可以准确预判任意实体类最终生成的建表语句,并在 SchemaGenerator、Migrations 与 EntityGenerator 等场景中统一掌控命名规则。
一、命名策略的定位与三大内置实现
在 mikro-orm 中,实体类到数据库表、属性到列的命名并非随意决定,而是由**命名策略(Naming Strategy)**统一裁决。文档明确指出,可选择的三种基本命名策略为:
| 策略类 | 默认适用场景 | 命名行为 |
|---|---|---|
UnderscoreNamingStrategy | 所有 SQL 驱动的默认策略 | 表名/列名统一转为小写 snake_case |
MongoNamingStrategy | MongoDriver的默认策略 | 字段名原样保留,集合名转为小写连字符形式 |
EntityCaseNamingStrategy | 需手动指定 | 表名/列名直接使用实体与属性原名,不做转换 |
从源码可以确认默认值的选择逻辑:Platform 基类的getNamingStrategy()返回UnderscoreNamingStrategy(见 Platform.ts#L93-L95),而 MongoPlatform 通过override getNamingStrategy()返回MongoNamingStrategy。运行时,Configuration 的最终取值逻辑为(Configuration.ts#L388-L390):
getNamingStrategy(): NamingStrategy { return this.getCachedService(this.#options.namingStrategy || this.#platform.getNamingStrategy()); }也就是说:初始化时传入namingStrategy选项则优先使用,否则回落到当前平台的默认策略,且实例通过getCachedService缓存复用。namingStrategy选项的签名是构造函数形式namingStrategy?: { new (): NamingStrategy }(Configuration.ts#L959)。
二、在初始化时覆盖命名策略与自定义策略
文档给出的覆盖方式是在初始化 ORM 时传入namingStrategy配置项:
class MyCustomNamingStrategy implements NamingStrategy { ... } const orm = await MikroORM.init({ ... namingStrategy: MyCustomNamingStrategy, ... });如果你希望复用默认行为、只改个别规则,可以直接继承AbstractNamingStrategy。文档中特别提示:
你也可以扩展
AbstractNamingStrategy,它替你实现了getClassName()方法——该方法用于将实体文件名映射为类名。
实际上 AbstractNamingStrategy 的默认实现远不止getClassName()一个方法,它是自定义策略的最佳起点。下文 API 参考一节会逐一说明这些默认实现。
三、Mongo 驱动下的命名规则
MongoNamingStrategy的字段名原样使用(propertyToColumnName直接返回入参),只有集合名会被转换:驼峰转为小写连字符形式。例如MyCoolEntity会被翻译为my-cool-entity集合名。
对应源码(MongoNamingStrategy.ts):
classToTableName(entityName: string, tableName?: string): string { return tableName ?? entityName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); } propertyToColumnName(propertyName: string): string { return propertyName; // 字段名保持不变 } referenceColumnName(): string { return '_id'; // 主键字段名为 _id }可以注意两个细节:
- 转换正则
([a-z])([A-Z])只在小写字母紧跟大写字母的边界处插入分隔符,因此MyCoolEntity→my-cool-entity; referenceColumnName()返回_id,与 SQL 策略的id不同,这直接影响 Mongo 集合中主键与引用字段的命名。
四、SQL 驱动下的命名规则(以 MySQL 为例)
MySqlDriver默认使用UnderscoreNamingStrategy,所有表和列名都会被转为小写、以下划线分词。文档中给出的author表建表语句示例如下:
CREATE TABLE `author` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `created_at` datetime(3) DEFAULT NULL, `updated_at` datetime(3) DEFAULT NULL, `terms_accepted` tinyint(1) DEFAULT NULL, `name` varchar(255) DEFAULT NULL, `email` varchar(255) DEFAULT NULL, `born` datetime DEFAULT NULL, `favourite_book_id` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;其中createdAt→created_at、favouriteBook(m:1 关系)→favourite_book_id正是 UnderscoreNamingStrategy 各方法协同的结果:
// 核心转换:小写字母后紧跟大写字母时插入下划线,再整体转小写 private underscore(name: string): string { return name.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase(); } classToTableName(entityName: string, tableName?: string): string { return tableName ?? this.underscore(entityName); } joinColumnName(propertyName: string): string { return this.underscore(propertyName) + '_' + this.referenceColumnName(); // 如 bookTag -> book_tag_id } joinKeyColumnName(entityName, referencedColumnName?, ...): string { return this.classToTableName(entityName, tableName) + '_' + (referencedColumnName || this.referenceColumnName()); } joinTableName(sourceEntity, targetEntity, propertyName, ...): string { return this.classToTableName(sourceEntity, tableName) + '_' + this.classToTableName(propertyName); } referenceColumnName(): string { return 'id'; }单元测试 UnderscoreNamingStrategy.test.ts 精确固化了这些行为,例如:
expect(ns.classToTableName('BookTag')).toBe('book_tag'); expect(ns.joinColumnName('bookTag')).toBe('book_tag_id'); expect(ns.joinKeyColumnName('BookTag')).toBe('book_tag_id'); expect(ns.joinTableName('bookTag', 'foo_bar', 'foo_baz')).toBe('book_tag_foo_baz'); expect(ns.propertyToColumnName('bookTag')).toBe('book_tag'); expect(ns.referenceColumnName()).toBe('id');反向转换(列名转属性名)同样经过测试验证:book_tag、book tag、book-tag乃至Book__-- _- tag这类脏数据都会归一为驼峰属性名(columnNameToProperty,AbstractNamingStrategy.ts#L69-L75)。
EntityCaseNamingStrategy的行为则由 EntityCaseNamingStrategy.test.ts 固化:BookTag类 →BookTag表,bookTag属性 →bookTag列;唯一特殊之处是joinKeyColumnName会将实体类名首字母小写(BookTag→bookTag),且复合主键外键会拼上被引用列名(bookTag_name)。
五、NamingStrategy API 参考
以下为文档列出的接口方法,签名与描述结合 NamingStrategy 接口源码进行了核对。
NamingStrategy.getClassName(file: string, separator?: string): string
根据文件名返回类名。默认实现在AbstractNamingStrategy中:取扩展名前的部分,把分隔符(默认-)后的字符大写,再首字母大写。例如my-cool-entity.ts→MyCoolEntity。
NamingStrategy.classToTableName(entityName: string): string
为实体类返回表名。这是各内置策略差异最大的方法(snake_case / 原样 / 连字符小写)。源码签名带可选tableName参数,若实体已通过@table()/ EntitySchema 显式指定表名,该值会优先返回。
NamingStrategy.getEntityName(tableName: string, schemaName?: string): string
根据数据库表名返回实体类名(EntityGenerator使用)。默认实现忽略 schema 名;当发现重名时,生成的名称会自动加前缀。源码中还可看到防御性处理:表名若以数字等非标识符字符开头,会先加E_前缀(AbstractNamingStrategy.ts#L56-L67),保证结果是合法且可排序的类名。
NamingStrategy.propertyToColumnName(propertyName: string): string
为属性返回列名(EntityGenerator亦使用)。
NamingStrategy.getEnumClassName(columnName: string, tableName: string, schemaName?: string): string
为某个列生成枚举类名(EntityGenerator使用)。默认实现基于table_column组合名走getEntityName。
NamingStrategy.getEnumTypeName(columnName: string, tableName: string, schemaName?: string): string
获取枚举类型名,配合实体生成器的enumType: 'dictionary'与enumType: 'union-type'选项使用。默认实现是在枚举类名前加T前缀。
NamingStrategy.enumValueToEnumProperty(enumValue: string, columnName: string, tableName: string, schemaName?: string): string
为枚举值返回枚举属性名(EntityGenerator使用)。默认实现将枚举值转大写。
NamingStrategy.referenceColumnName(): string
返回默认的引用列名。UnderscoreNamingStrategy/EntityCaseNamingStrategy返回id,MongoNamingStrategy返回_id。
NamingStrategy.joinColumnName(propertyName: string): string
为属性返回关联列名(m:1 / 1:1 关系外键列)。UnderscoreNamingStrategy的产物形如book_tag_id(即underscore(propertyName) + '_' + referenceColumnName())。
NamingStrategy.joinTableName(sourceEntity: string, targetEntity: string, propertyName: string): string
返回连接表(pivot 表)名,该返回值同时是@ManyToMany()装饰器pivotTable选项的默认值。UnderscoreNamingStrategy生成源表_属性snake(如book_tag_foo_baz),EntityCaseNamingStrategy生成源表_原属性名(如BookTag_fooBaz)。
NamingStrategy.joinKeyColumnName(entityName: string, referencedColumnName?: string): string
返回给定参数下的外键列名。UnderscoreNamingStrategy生成实体表名_被引用列名(如book_tag_id),Mongo 策略则直接使用(或返回)表名本身。
NamingStrategy.indexName(tableName: string, columns: string[], type: 'primary' | 'foreign' | 'unique' | 'index' | 'sequence'): string
返回指定类型的键/约束名。文档特别提醒:部分驱动并非支持全部类型,例如 MySQL 与 SQLite 会强制主键名(PRIMARY KEY/_pkey约定)。默认实现(AbstractNamingStrategy.ts#L26-L51)的命名规则:
primary→{table}_pkey;sequence→{table}_{cols}_seq;- 其余 →
{table}_{cols}_{type}(无列时退化为{table}_{type}); - 带 schema 的表名会先截掉
.前的部分,列名中的.会被替换为_。
该约束名在 MS SQL 的默认值约束等场景会被真实引用,例如 MsSqlSchemaHelper 调用indexName(..., 'default')为列默认值命名。
NamingStrategy.aliasName(entityName: string, index: number): string
为查询中的实体返回别名。别名必须在整个查询内唯一——默认通过追加递增的index参数保证,只要你自行保证唯一,也可以不使用它。默认实现只取类名首字母小写加序号(如a0、a1),源码注释说明这是为了规避部分数据库引擎的标识符长度限制。SQL 查询构造器 QueryBuilder 即通过config.getNamingStrategy().aliasName(entityName, aliasCounter++)为每个 join 分配别名。
NamingStrategy.inverseSideName(entityName: string, propertyName: string, kind: ReferenceKind): string
返回双向关系中对侧属性名,供EntityGenerator的bidirectionalRelations选项使用。默认实现按关系类型区分(AbstractNamingStrategy.ts#L106-L118):
- M:N关系命名为
${propertyName}Inverse(属性名从 pivot 表名推断); - 其他关系类型使用目标实体类名首字母小写,若是 1:M 集合则追加
Collection后缀(如BookTag→bookTag,1:M 时 →bookTagCollection)。
该行为在 v6.3 中发生变化;在此之前,所有属性(包括非 M:N 关系)都统一加
Inverse后缀。
单测(UnderscoreNamingStrategy.test.ts#L24-L30)固化了上述规则:
expect(ns.inverseSideName('BookTag', 'book', ReferenceKind.MANY_TO_ONE)).toBe('bookTag'); expect(ns.inverseSideName('BookTag', 'book', ReferenceKind.ONE_TO_MANY)).toBe('bookTagCollection'); expect(ns.inverseSideName('User', 'friends', ReferenceKind.MANY_TO_MANY)).toBe('friendsInverse');六、自定义策略实战建议:基于 AbstractNamingStrategy 的默认实现
编写自定义策略前,值得先了解AbstractNamingStrategy已经替实现了哪些方法——你只需覆写与目标风格冲突的少量抽象方法。从源码结构看,默认实现还包括:
getClassName(file, separator = '-'):文件 → 类名,分隔符可自定义(如my_entity.ts传_);classToMigrationName(timestamp, customMigrationName?):生成迁移类名Migration{timestamp},自定义名称部分会把非标识符合法字符替换为_,确保产物可作为合法类名用于迁移文件;indexName(...):如上所述的约束命名规则;getEntityName / columnNameToProperty:反向命名(用于 EntityGenerator),其中还处理了 populate 路径保留字(如$**、$*等PopulatePath成员)的前缀保护;aliasName:首字母 + 序号的短别名;manyToManyPropertyName(...):从 pivot 表名去掉所有方表名前缀后转属性名,例如author_books→books,user_roles→roles(无所有方前缀时返回全名,如booksAuthors);discriminatorColumnName(baseName):多态关系的鉴别器列名,默认为{baseName}Type经列名转换后的结果。
因此一个典型的自定义策略(如「全小写无分隔符」风格)可以写成:
import { AbstractNamingStrategy } from '@mikro-orm/core'; class LowerNamingStrategy extends AbstractNamingStrategy { classToTableName(entityName: string, tableName?: string): string { return (tableName ?? entityName).toLowerCase(); } propertyToColumnName(propertyName: string): string { return propertyName.toLowerCase(); } joinColumnName(propertyName: string): string { return propertyName.toLowerCase() + '_' + this.referenceColumnName(); } joinTableName(sourceEntity: string, targetEntity: string, propertyName: string, tableName?: string): string { return this.classToTableName(sourceEntity, tableName) + '_' + propertyName.toLowerCase(); } joinKeyColumnName(entityName: string, referencedColumnName?: string, composite?: boolean, tableName?: string): string { return this.classToTableName(entityName, tableName).toLowerCase() + '_' + (referencedColumnName ?? this.referenceColumnName()); } referenceColumnName(): string { return 'id'; } }七、命名策略在整个系统中的生效位置
从源码的调用点可以确认,命名策略不只是「建表时」的规则,它贯穿了 mikro-orm 的多个子系统:
- 元数据发现:MetadataDiscovery 在解析实体元数据时就取用命名策略,把属性映射为列;
- 查询构造:SQL 的 QueryBuilder 用
aliasName生成 join 别名;Dataloader 场景(DataloaderUtils)也用aliasName为 pivot 实体分配别名; - Schema 工具:各平台 SchemaHelper 用
indexName为约束、默认值命名(如 SqliteSchemaHelper 的 check 约束); - 迁移:Migrator 把
config.getNamingStrategy()注入迁移生成器; - 实体生成器:EntityGenerator 用
getEntityName、columnNameToProperty、inverseSideName、manyToManyPropertyName等反向命名方法从现有数据库逆向生成实体代码。
这意味着:切换命名策略会同时影响查询别名、schema 对比与迁移 diff,在已落库的项目中更换策略需谨慎评估对既有表结构比对的影响。
八、小结
| 要点 | 说明 |
|---|---|
| 默认策略 | SQL 驱动 →UnderscoreNamingStrategy;Mongo 驱动 →MongoNamingStrategy;EntityCaseNamingStrategy需显式指定 |
| 覆盖方式 | MikroORM.init({ namingStrategy: MyNamingStrategy }),实例经Configuration.getCachedService缓存 |
| 自定义方式 | 实现NamingStrategy接口,或继承AbstractNamingStrategy只覆写少数抽象方法 |
| 影响面 | 表名、列名、外键/关联列、pivot 表名、索引与约束名、查询别名、迁移名、EntityGenerator 产物 |
结合 NamingStrategy 接口定义、三种内置策略实现 与 配套单测,你可以对任意实体准确推导出其在目标数据库中的最终命名,并在 schema 生成、迁移与实体生成工具链中保持命名规则的一致性。
- 后端
【免费下载链接】mikro-orm
TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, MariaDB, MS SQL Server, PostgreSQL and SQLite/libSQL databases.
相关推荐
GORM命名策略:表名与列名的自定义规则终极指南
GORM命名策略:表名与列名的自定义规则终极指南 GORM作为Go语言中最流行的ORM框架,其强大的命名策略功能让开发者能够灵活控制数据库表名和列名的生成规则。
后端数据库ORMDoctrine ORM NamingStrategy 完全指南:自定义表名与列名生成规则
Doctrine ORM NamingStrategy 完全指南:自定义表名与列名生成规则 本指南围绕 Doctrine ORM 的命名策略(NamingStr
数据库ORM后端Regal 自定义命名规范规则(naming-convention)实战指南:用配置而非代码统一 Rego 项目命名
Regal 自定义命名规范规则(naming convention)实战指南:用配置而非代码统一 Rego 项目命名 Regal 的 custom 类别中提供了
后端认证鉴权云原生
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考