数据库后端【免费下载链接】objection.jsAn SQL-friendly ORM for Node.js项目地址https://gitcode.com/gh_mirrors/ob/objection.js点击查看免费下载Objection.js 是一个 SQL-friendly 的 Node.js ORM它的全部核心都建立在“模型Model”之上一个Model子类对应一张数据库表该类的实例对应表中的一行。本篇指南以官方 Models 指南 为骨架结合仓库源码与真实示例系统讲解如何定义模型、配置tableName/idColumn/jsonSchema/relationMappings以及为什么 Objection.js 没有全局配置、如何用BaseModel统一共享配置。读完本文你将能独立编写出一个生产可用的模型层并理解校验、复合主键、关系映射背后的源码级实现。一、核心概念表与行的面向对象映射在 Objection.js 中模型的定义遵循一条最简原则一个 Model 子类代表一张数据库表这个类的实例代表表中的一行。创建一个模型就是继承Model类并在子类上通过静态属性声明它与数据库表的映射关系tableName模型对应的表名唯一必选属性idColumn表的主键列名jsonSchema可选的输入校验 JSON SchemarelationMappings模型与其他模型之间的关系关联定义。其中关系映射通过静态的relationMappings属性声明详细的关系类型与用法见 Relations 指南。二、最简模型只有 tableName 就够官方文档给出的“最小可用”模型只有几行代码const { Model } require(objection); class MinimalModel extends Model { static get tableName() { return someTableName; } } module.exports MinimalModel;仓库中的 minimal 示例 与此完全一致只额外导出了具名类use strict; const { Model } require(objection); class Person extends Model { // Table name is the only required property. static get tableName() { return persons; } } module.exports { Person, };从源码看tableName确实是硬性要求。Model.getTableName()会先兼容“静态属性”与“静态 getter”两种写法isFunction(tableName)分支随后校验// lib/model/Model.js#L341-L353 static getTableName() { let tableName this.tableName; if (isFunction(tableName)) { tableName this.tableName(); } if (!isString(tableName)) { throw new Error(Model ${this.name} must have a static property tableName); } return tableName; }而基类Model的默认值是Model.tableName null见 lib/model/Model.js也就是说不声明tableName会在运行时直接抛出异常。从源码结构看getter 与静态属性的等价性Objection.js 允许static get xxx()与static xxx value两种声明方式并存。源码中几乎所有配置读取入口都做了isFunction兼容处理例如getTableName、getIdColumn、getRelationMappings见 lib/model/Model.js#L869-L877因此你可以根据自己的代码风格自由选择。三、idColumn主键列与复合主键每个模型都必须有一个标识列identifier column用于唯一标识一行。标识列名通过静态属性 idColumn 指定默认值为id这一点在基类定义中写得很明确// lib/model/Model.js#L719 Model.idColumn id;只有当表的主键不叫id时才需要显式声明class Person extends Model { static get idColumn() { return some_column_name; } }复合主键是一等公民idColumn可以传入列名数组来表示复合主键Objection.js 把复合键当作一等公民处理class Movie extends Model { static get tableName() { return movies; } // 复合主键由 languageCode 和 id 两列共同标识一行 static get idColumn() { return [languageCode, id]; } }源码中getIdRelationProperty()会把声明的标识列统一加工为“表名限定”的列引用并通过RelationProperty管理// lib/model/Model.js#L839-L846 function getIdRelationProperty(modelClass) { const idColumn asArray(modelClass.getIdColumn()); return new RelationProperty( idColumn.map((idCol) ${modelClass.getTableName()}.${idCol}), () modelClass, ); }getIdColumnArray()/getIdPropertyArray()分别返回列名与属性名数组getIdProperty()则在单列时返回字符串、多列时返回数组lib/model/Model.js#L440-L456。复合主键相关的实战细节$compositeKey查询、关系中的复合键可进一步阅读 复合键配方。注意idColumn也可以返回null用于告诉 Objection.js 该模型没有主键——这在“联接表join table”模型中可能有用见 static-properties.md 中idColumn一节。四、jsonSchema输入校验而非数据库 Schema模型可以可选地定义 jsonSchema 对象用于输入校验。官方文档特别强调了两点这不是数据库 SchemaObjection.js不会根据它生成任何表或列每当一个模型实例被创建无论是显式new还是隐式创建它都会被拿来与jsonSchema比对校验。所谓隐式创建就是调用 insert、insertGraph、patch 等接收模型属性的方法时属性会被转换成模型实例再校验。从数据库读取数据时不做校验。例如class Person extends Model { static get jsonSchema() { return { type: object, required: [firstName, lastName], properties: { id: { type: integer }, parentId: { type: [integer, null] }, firstName: { type: string, minLength: 1, maxLength: 255 }, lastName: { type: string, minLength: 1, maxLength: 255 }, age: { type: number }, // 声明为 object/array 的属性在写入数据库时会被自动 // 转成 JSON 字符串读取时再转回对象/数组。 // 想改变这一行为可以覆写 Model.jsonAttributes。 address: { type: object, properties: { street: { type: string }, city: { type: string }, zipCode: { type: string }, }, }, }, }; } }校验的源码级实现Objection.js 的校验不是简单散落在各个方法里而是收敛到统一的校验流程中见 lib/model/modelValidate.jsfunction validate(model, json, options {}) { json json || model; const inputJson json; const validatingModelInstance inputJson inputJson.$isObjectionModel; if (options.skipValidation) { return json; } // ... 对模型实例做浅拷贝后再校验避免污染原对象 const modelClass model.constructor; const validator modelClass.getValidator(); const args { options, model, json, ctx: Object.create(null) }; validator.beforeValidate(args); json validator.validate(args); validator.afterValidate(args); // ... }默认校验器是 AjvValidator它基于ajv构建使用allErrors: true收集全部错误而不是“报第一个错就停”默认useDefaults: true即 JSON Schema 里的default值会在校验时被写入数据额外维护了一个不设默认值的 Ajv 实例ajvNoDefaults专门用于校验patch对象——patch 允许只提交部分字段所以required约束会被剔除见compilePatchValidator/jsonSchemaWithoutRequired编译后的校验函数会按modelClass.uniqueTag()或序列化的 schema 做缓存避免重复编译。校验失败时抛出的异常是ValidationErrortype: ModelValidationdata为按属性路径聚合的错误哈希错误路径中的/会被转换为.例如firstName上的错误会落在data.firstName上。仓库示例 koa/app.js 展示了典型处理方式if (err instanceof ValidationError) { ctx.status 400; ctx.body { error: ValidationError, errors: err.data, }; }如果你想替换默认的 Ajv 实现例如接入自己的校验库可以覆写Model.createValidator()更完整的定制方案见 自定义校验配方钩子$beforeValidate、$afterValidate的使用见 Hooks 指南。jsonAttributesobject/array 属性的自动 JSON 序列化文档示例中address被声明为object这触发了 Objection.js 的自动 JSON 属性机制。源码 lib/model/modelJsonAttributes.js 中的getJsonAttributes()会扫描jsonSchema.properties凡是type包含object或array含anyOf/oneOf组合的属性都会被加入 JSON 属性列表if (types.indexOf(object) ! -1 || types.indexOf(array) ! -1) { jsonAttributes.push(propName); }写入数据库时formatJsonAttributes把这些属性JSON.stringify成字符串读取时parseJsonAttributes再把字符串JSON.parse回对象/数组解析失败则保留原值见 lib/model/modelJsonAttributes.js#L15-L23。结合 PostgreSQL 的json/jsonb列类型这是“用一行数据库记录表示一份文档”的利器。如果想手动指定而不是依赖自动探测可以覆写jsonAttributesclass Person extends Model { static get jsonAttributes() { return [someProp, someOtherProp]; } }五、完整示例一个带方法、校验与关系的模型官方文档给出了一个“麻雀虽小五脏俱全”的Person模型——它也是 koa 示例 的真实模型。注意其中三个核心设计点自定义方法类方法如fullName()可以直接定义在模型上如果希望方法的返回值出现在输出 JSON 中需要配合virtualAttributesjsonSchema 校验见上一节relationMappings 关系在 getter 内部require关联模型这是避免require循环的常用手法。const { Model } require(objection); class Person extends Model { // Table name is the only required property. static get tableName() { return persons; } // idColumn 默认返回 id主键不是 id 时才需要显式声明。 static get idColumn() { return id; } // 自定义方法。想让它出现在输出 JSON 中见 virtualAttributes。 fullName() { return this.firstName this.lastName; } // 可选的 JSON Schema。这不是数据库 Schema // 不会据此生成任何表或列仅用于输入校验。 static get jsonSchema() { return { type: object, required: [firstName, lastName], properties: { id: { type: integer }, parentId: { type: [integer, null] }, firstName: { type: string, minLength: 1, maxLength: 255 }, lastName: { type: string, minLength: 1, maxLength: 255 }, age: { type: number }, // object/array 属性自动做 JSON 字符串往返转换 address: { type: object, properties: { street: { type: string }, city: { type: string }, zipCode: { type: string }, }, }, }, }; } // 与其他模型的关系定义。 static get relationMappings() { // 在 getter 内部 require 模型是避免 require 循环的方式之一。 const Animal require(./Animal); const Movie require(./Movie); return { pets: { relation: Model.HasManyRelation, // 关联模型可以是Model 子类构造器或导出该类的绝对文件路径。 modelClass: Animal, join: { from: persons.id, to: animals.ownerId, }, }, movies: { relation: Model.ManyToManyRelation, modelClass: Movie, join: { from: persons.id, // ManyToMany 关系需要通过 through 描述联接表。 through: { // 如果联接表有模型类可以这样声明 // modelClass: PersonMovie, from: persons_movies.personId, to: persons_movies.movieId, }, to: movies.id, }, }, children: { relation: Model.HasManyRelation, modelClass: Person, join: { from: persons.id, to: persons.parentId, }, }, parent: { relation: Model.BelongsToOneRelation, modelClass: Person, join: { from: persons.parentId, to: persons.id, }, }, }; } }仓库里的 koa/models/Person.js 还在同一模型上增加了modifiers可复用的查询片段searchByName演示了“模型 表 校验 关系 查询封装”的完整形态。六、relationMappings 与关系类型的深入说明relationMappings是一个对象或返回对象的函数/getter键是关系名值是一个 relation mapping。join对象中的from/to定义了两表关联所经由的列这些列不要求是主键可以是任意列甚至可以是 JSON 列内部的字段配合 ref 辅助函数如ref(animals.json:details.ownerId).castInt()。对于ManyToManyRelation还需要用through对象声明联接表from/to指向联接表两侧的外键也可通过extra声明要随关系读写到联接表的额外列见 static-properties.md。modelClass支持三种取值见 static-properties.md 与 relations 指南模型类构造器如上面的Animal导出模型类的绝对文件路径相对 modelPaths 数组中某个目录的路径。其中后两种“路径”写法很适合规避require循环。Objection.js 内置五种关系类型全部挂在Model静态侧见 lib/model/Model.js#L703-L707静态类型语义典型场景Model.BelongsToOneRelation属于一个persons.parentId - persons.idModel.HasOneRelation拥有一个一对一Model.HasManyRelation拥有多个persons.id - animals.ownerIdModel.ManyToManyRelation多对多需through联接表persons - moviesModel.HasOneThroughRelation经联接表拥有一个如“最爱的电影”源码层面关系映射由getRelationMappings()解析支持 getter/函数写法见 lib/model/Model.js#L869-L877并通过getRelationUnsafe()把 mapping 实例化为具体的 Relation 对象缓存lib/model/Model.js#L603-L620。关系一旦定义好就可以用withGraphFetched/withGraphJoined做嵌套加载用$relatedQuery做关联查询详细 API 见 eager-methods 与 relations 指南。七、没有全局配置Model.knex、多数据库与 BaseModel 模式Objection.js 的一个标志性设计是所有配置都通过Model类完成不存在全局配置或全局状态也没有所谓的 “objection 实例”。这意味着你可以创建彼此隔离的组件比如在同一应用里同时使用多个不同配置的数据库。最常见的做法是让所有模型共享同一份配置——官方推荐模式是创建一个BaseModel让所有模型继承它// models/BaseModel.js const { Model } require(objection); class BaseModel extends Model { // 共享配置集中在这里例如 modelPaths、jsonSchema、columnNameMappers 等。 static get modelPaths() { return [__dirname]; } } module.exports { BaseModel }; // models/Person.js const { BaseModel } require(./BaseModel); class Person extends BaseModel { static get tableName() { return persons; } }该模式同样出现在 static-properties.md 的modelPaths一节。数据库连接的绑定同样“无全局”在启动代码里把 knex 实例绑定到模型类上即可。仓库 koa 示例 正是这样做的const Knex require(knex); const knexConfig require(./knexfile); const { Model } require(objection); // 初始化 knex。 const knex Knex(knexConfig.development); // 把所有模型绑定到同一个 knex 实例。 // 如果服务器只有一个数据库做到这一步就够了。 // 多数据库系统请看 Model.bindKnex() 方法。 Model.knex(knex);源码中Model.knex(...)设置/读取内部$$knexlib/model/Model.js#L515-L521Model.bindKnex(knex)则返回一个绑定到指定 knex 的模型子类lib/model/Model.js#L563-L565供多数据库场景使用。八、数据库 Schema 交给 Migrations官方文档明确了一个工程哲学除了idColumn不要在模型里定义属性、索引或任何数据库 Schema 相关内容。数据库 Schema 在 Objection.js 中被视为独立关注点应通过 knex migrations 管理。理由也很务实任何非平凡项目最终都需要 migrations若同时在“模型”和“migrations”两处维护 Schema长期来看只会让事情更复杂。仓库示例中的表结构正是通过 migration 文件建立的见 koa migrations模型文件里只关心业务映射两者职责清晰分离。安装与初始化步骤npm install objection knex及对应数据库驱动见 安装指南。九、更多可配置的静态属性速查除了上述核心属性static-properties.md 还列出了一系列实用配置均可在BaseModel中统一设置默认值均可在 lib/model/Model.js#L717-L737 中找到virtualAttributes默认null。把 getter/方法的结果序列化进toJSON()输出不写入数据库例如fullName()toJSON({ virtuals: [fullName] })可以只输出子集。modifiers默认{}。可复用的查询片段配合modify()、withGraphFetched([movies(goodMovies)])、modifyGraph()使用详见 modifiers 配方。modelPaths默认[]。供关系按相对路径解析模型类配合modelClass: Animal这类写法使用。concurrency默认4mssql 为1。单个连接上最多并发执行的查询数knex 连接池默认大小为 10因此整体最大并发 ≈concurrency * 池大小见 lib/model/Model.js#L381-L402 与 static-properties.md 说明。cloneObjectAttributes默认true。序列化时是否克隆对象属性如 jsonb 列大 JSON 字段导致性能瓶颈时可设为false。columnNameMappers默认null。列名 ↔ 属性名转换器内置snakeCaseMappers()见 snake_case 转换配方。uidProp/uidRefProp/dbRefProp/propRefRegex图插入insertGraph中用于临时标识、引用、指向已有行的内部属性名默认分别为#id、#ref、#dbRef与对应正则且不能用模型自身的属性名覆盖。pickJsonSchemaProperties默认false。设为true时插入/更新只挑选jsonSchema.properties中声明的属性写入数据库。defaultGraphOptions默认{ minimize: false, separator: :, aliases: {}, maxBatchSize: 10000 }作为withGraphFetched/withGraphJoined的默认选项。useLimitInFirst默认false。为兼容旧行为first()默认不加limit(1)有需要可设为true。QueryBuilder自定义查询构建器子类覆盖所有由query()/$query()/$relatedQuery()创建的查询见 自定义查询构建器配方。十、小结把本篇要点串起来一个 Objection.js 模型 表名必填 主键列默认id 输入校验可选jsonSchema 关系映射可选relationMappings 自定义方法与查询封装可选校验只发生在写入路径上Schema 管理交给 migrations所有配置都挂在Model类上、无全局状态因此多数据库隔离与BaseModel共享配置都顺理成章。在此基础上你可以进一步探索 模型静态方法query、relatedQuery、bindKnex、transaction、实例方法$query、$relatedQuery、$toJson、$clone、校验指南 与 关系指南把模型层真正用活。赞分享数据库后端【免费下载链接】objection.jsAn SQL-friendly ORM for Node.js项目地址https://gitcode.com/gh_mirrors/ob/objection.js点击查看免费下载相关推荐Objection.js 关系映射五种关系类型的完整指南Objection.js 关系映射五种关系类型的完整指南 本文详细介绍了 Objection.js 中的五种核心关系类型BelongsToOneRelati数据库后端sandspiel 元素大全10种基础物质的相互作用原理sandspiel 元素大全10种基础物质的相互作用原理 sandspiel 是一款创意细胞自动机浏览器游戏玩家可以通过组合不同元素创造出丰富的物理效果和动Cosmos-Predict2.5终极指南如何用世界模拟模型预测未来视频状态Cosmos Predict2.5终极指南如何用世界模拟模型预测未来视频状态 Cosmos Predict2.5作为Cosmos世界基础模型WFMs家族的上一篇如何快速优化Windows右键菜单专业管理工具完全指南下一篇百度网盘高速下载解决方案告别龟速3步获取真实下载地址创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考