
数据库后端【免费下载链接】objection.jsAn SQL-friendly ORM for Node.js项目地址https://gitcode.com/gh_mirrors/ob/objection.js点击查看免费下载本篇技术指南以 objection.js 官方 API 文档中module objection一节为核心系统讲解require(objection)返回的模块所暴露的全部公开属性核心类Model、异步预热函数initialize、事务函数transaction、SQL 引用构建器ref/raw/val/fn、插件组合助手mixin/compose、命名映射工具snakeCaseMappers/knexSnakeCaseMappers/knexIdentifierMapping以及各类数据库错误类。读完本文你将掌握 objection.js 模块层 API 的完整用法能够在实际项目中正确选用这些工具写出类型安全、防注入且结构清晰的查询代码。模块总览require(objection)里有什么objection.js 的模块入口位于 lib/objection.js。当你执行const objection require(objection); const { Model, ref } require(objection);时得到的对象包含以下公开属性。根据 doc/api/README.md 的说明凡未在 API 文档中提及的内容均视为私有实现不应依赖私有实现可能在小版本之间随时变更而本文介绍的公共 API 遵循语义化版本管理。从源码看lib/objection.js 最终导出了三大类成员核心类Model、QueryBuilder、QueryBuilderBase、QueryBuilderOperation、RelationExpression、Validator、AjvValidator、Relation、HasOneRelation、HasManyRelation、BelongsToOneRelation、HasOneThroughRelation、ManyToManyRelation函数与工具transaction、initialize、compose、mixin、ref、val、raw、fn、snakeCaseMappers、knexSnakeCaseMappers、knexIdentifierMapping错误类ValidationError、NotFoundError以及来自db-errors库的DBError、UniqueViolationError、NotNullViolationError、ForeignKeyViolationError、ConstraintViolationError、CheckViolationError、DataError。值得注意的一个实现细节源码中Model、QueryBuilder、Validator、AjvValidator这四个可被继承的类被特意用 ES5 风格的function包裹lib/objection.js注释说明这是为了兼容 Babel 的 ES5 继承转译——如果你的应用还在用 ES5 转译方式编写 Node 代码这种包裹可以保证class X extends Model {}正常工作。Model一切查询的起点const { Model } require(objection);Model是 objection.js 的模型基类所有业务模型都继承自它。模型的完整 API实例方法、实例属性、静态方法、静态属性参见 Model API 文档其下还有 instance-methods.md、instance-properties.md、static-methods.md、static-properties.md 和 overview.md 等详细分册。典型用法const { Model } require(objection); const Knex require(knex); const knex Knex({ client: postgres, connection: { /* ... */ } }); Model.knex(knex); // 将 knex 实例全局绑定到 Model class Person extends Model { static get tableName() { return persons; } } Person.query().where(age, , 18);Model的所有查询操作都经由 QueryBuilder 完成本文不展开模型细节只强调一点Model.knex()的全局绑定是initialize、transaction等模块级函数能够省略参数的前提下文会反复用到。initialize按需预热模型的异步准备const { initialize } require(objection);某些查询在执行前需要异步准备工作例如从数据库获取表元数据table metadata。objection.js 默认在首次执行这类查询时按需on-demand进行准备。但少数方法如toKnexQuery需要这些准备已经完成才能同步地构建查询——这时就可以用initialize来预热模型一次性完成所有必需的异步准备。该函数只需调用一次。调用initialize完全可选。如果有方法要求必须先调用它会抛出带有明确提示的错误信息。这类情况极为罕见该函数主要是为这些场景准备的。你也可以主动调用它来掌控异步准备发生的时机例如在测试中让准备工作提前完成、避免测试结果的不确定性。用法示例当Model已通过Model.knex(knex)全局绑定了 knex 实例时可以省略第一个参数const { initialize } require(objection); await initialize([Person, Movie, Pet, SomeOtherModelClass]);也可以显式传入 knex 实例const { initialize } require(objection); await initialize(knex, [Person, Movie, Pet, SomeOtherModelClass]);源码剖析lib/initialize.js 的实现非常精简async function initialize(knex, modelClasses) { if (!modelClasses) { modelClasses knex; knex modelClasses[0].knex(); } await Promise.all(modelClasses.map((modelClass) modelClass.fetchTableMetadata({ knex }))); }可以推断出它的两个关键行为参数重载如果只传一个数组参数会从数组第一个模型类上取knex()作为数据库连接因此省略参数的前提是所有模型都绑定到了同一个 knex并发预热内部通过Promise.all并行调用每个模型类的fetchTableMetadata({ knex })将各模型的表元数据缓存下来之后需要同步构建查询的方法就能直接使用缓存。transaction多模型事务const { transaction } require(objection);transaction是 objection.js 的事务辅助函数完整指南参见 事务指南。它的核心能力是将多个 Model 类绑定到同一个事务连接上保证回调内所有模型发出的查询都运行在同一事务中全部成功提交、任一失败回滚。基本用法const { transaction, Model } require(objection); class Person extends Model {} class Movie extends Model {} await transaction(Person, Movie, async (person, movie) { await person.query().insert({ name: Jennifer }); await movie.query().insert({ title: Some movie }); });回调的第一个参数依次是被绑定到事务的模型类最后一个参数是 knex 事务对象trx。源码剖析参数约束从 lib/transaction.js 可以看到事务函数的几条硬性约束至少两个参数一个模型类加一个回调否则直接Promise.reject除最后一个参数外都必须是 Model 子类通过maybeModel.isObjectionModelClass判断否则报错all but the last argument should be Model subclasses所有模型必须绑定到同一个数据库逐个比对modelClass.knex()不一致时报错all Model subclasses must be bound to the same database内部通过modelClass.bindTransaction(trx)生成事务绑定副本并调用callback.apply(trx, args)。此外还提供transaction.start(modelClassOrKnex)静态方法lib/transaction.js用于手动启动事务并取出trx对象适合需要自己管理提交/回滚时机的场景const trx await transaction.start(Person); try { await Person.query(trx).insert({ name: Jennifer }); await trx.commit(); } catch (err) { await trx.rollback(); throw err; }如果第一个参数传入的是 knex 实例而非模型类transaction会直接转发给knex.transactionlib/transaction.js此时行为与原生 knex 事务一致。ref安全的列、表与 JSON 字段引用const { ref } require(objection);ref是工厂函数返回一个 ReferenceBuilder 实例用于在查询中引用表、列、JSON 属性等标识符并支持类型转换cast与别名alias。它比手写字符串拼接更安全因为最终会转换为带占位符的 knex raw SQL标识符通过??绑定避免注入。用法示例const { ref } require(objection); await Model.query() .select([ id, ref(Model.jsonColumn:details.name) .castText() .as(name), ref(Model.jsonColumn:details.age) .castInt() .as(age) ]) .join( OtherModel, ref(Model.jsonColumn:details.name).castText(), , ref(OtherModel.name) ) .where(age, , ref(OtherModel.ageLimit));JSON 字段引用的歧义与.from()解法ref使用:作为 JSON 字段分隔符如jsonColumn:details.name而withGraphJoined与joinRelated也使用:作为关系路径分隔符二者组合时会产生歧义。例如jsonColumn:details.name可能有两种含义关系jsonColumn.details的name列jsonColumn列中details对象的name字段。当与withGraphJoined、joinRelated一起使用时可以用ReferenceBuilder的from方法显式指定表这里的关系路径消除歧义const { ref } require(objection); await Person.query() .withGraphJoined(children.children) .where(ref(jsonColumn:details.name).from(children:children), Jennifer);源码剖析ReferenceBuilder 的能力集ReferenceBuilder 提供了以下链式方法类型转换castText()、castInt()、castBigInt()、castFloat()、castDecimal()、castReal()、castBool()、castJson()或通用的castTo(sqlType)。从 源码 可以看到带 cast 的 JSON 字段引用使用#提取文本、不带 cast 时使用#提取 JSON并生成CAST(... AS type)表达式作用域限定from(table)/table(table)指定表名model(modelClass)绑定模型类用于自动解析表引用别名as(alias)。源码中的_shouldAlias()lib/queryBuilder/ReferenceBuilder.js说明若引用本身就是简单列且别名与列名相同则省略as避免生成冗余 SQL克隆clone()用于复制构建器克隆时跳过表达式重新解析。raw不依赖 knex 的裸 SQL 构建器const { raw } require(objection);raw是工厂函数返回一个 RawBuilder 实例。RawBuilder 是 knexraw方法的封装本身不依赖 knex——RawBuilder 实例会在查询执行时被惰性转换为 knex raw 实例。相关配方参见 裸 SQL 查询配方。为什么推荐使用占位符在查询中使用裸 SQL 片段时强烈建议使用占位符而非直接把用户输入拼进 SQL以避免注入攻击。占位符会被交给数据库引擎安全地插值。占位符规则??标识符占位符列名、别名等?值占位符。const { raw } require(objection); const result await Person.query() .select(raw(coalesce(sum(??), 0) as ??, [age, ageSum])) .where(age, , raw(? ?, [50, 25])); console.log(result[0].ageSum);raw同样可用于 insert / updateawait Person.query().patch({ age: raw(age ?, 10) });命名占位符还可以使用命名占位符:someName:用于标识符列名、别名等:someName用于值。await Person.query() .select( raw(coalesce(sum(:sumColumn:), 0) as :alias:, { sumColumn: age, alias: ageSum }) ) .where( age, , raw(:value1 :value2, { value1: 50, value2: 25 }) );嵌套引用ref、raw、val 与查询构建器raw调用中可以嵌套ref、raw、val以及查询构建器knex 与 objection 的均可const { val } require(objection); await Person .query() .select(raw(coalesce(:sumQuery, 0) as :alias:, { sumQuery: Person.query().sum(age), alias: ageSum })) .where(age, , raw(:value1 :value2, { value1: val(50), value2: knex.raw(25) }));源码剖析从 RawBuilder 源码可见其转换逻辑toKnexRaw(builder)会区分单个对象参数走命名占位符分支支持as()别名与数组参数走位置占位符分支并递归调用buildArg处理嵌套的ref、val、其他raw实例与查询构建器。这也解释了为什么可以在raw中混用各类 objection 构建器。val构造不同类型值的构建器const { val } require(objection);val是工厂函数返回一个 ValueBuilder 实例用于构建不同类型的值JSON 对象、数组等并支持类型转换与别名。用法示例const { val, ref } require(objection); // 比较 JSON 对象 await Model.query().where( ref(Model.jsonColumn:details), , val({ name: Jennifer, age: 29 }) ); // 插入数组 await Model.query().insert({ numbers: val([1, 2, 3]) .asArray() .castTo(real[]) });源码剖析ValueBuilder 的默认行为从 ValueBuilder 源码看构造函数中this._toJson isObject(value)——对象与数组默认会序列化为 JSONJSON.stringify后作为绑定值传入这是它和普通字面量最大的区别。链式能力包括castText()/castInt()/castBigInt()/castFloat()/castDecimal()/castReal()/castBool()/castJson()以及通用castTo(sqlType)最终生成CAST(... AS type)asArray()将值渲染为ARRAY[...]字面量lib/queryBuilder/ValueBuilder.js每个元素作为独立绑定值as(alias)为表达式起别名。fn调用 SQL 函数的构建器const { fn } require(objection);fn是工厂函数返回一个 FunctionBuilder 实例用于调用 SQL 函数。签名如下const functionBuilder fn(functionName, ...args);例如fn(coalesce, ref(age), 0);fn还内置了最常用函数的快捷方式fn.now(); fn.now(precision); fn.coalesce(...args); fn.concat(...args); fn.sum(...args); fn.avg(...args); fn.min(...args); fn.max(...args); fn.count(...args); fn.upper(...args); fn.lower(...args);所有参数默认按值解释引用列时请用ref。也可以传入raw实例、其他fn实例、QueryBuilder、knex 构建器、knex raw 等——与其他 objection 方法一样灵活。用法示例const { fn, ref } require(objection); // 比较可空数值 await Model.query().where(fn(coalesce, ref(age), 0), , 30); // 使用 fn.coalesce 快捷方式的等价写法 await Model.query().where(fn.coalesce(ref(age), 0), , 30);需要注意在很多场景下直接使用raw或whereRaw更简洁await Model.query().whereRaw(coalesce(age, 0) ?, 30);源码剖析FunctionBuilder 直接继承自RawBuilder。fn(...)会把函数名与参数个数拼成函数名(?, ?, ...)的 SQL 模板lib/queryBuilder/FunctionBuilder.js参数通过绑定值安全传入。快捷函数遍历[coalesce, concat, sum, avg, min, max, count, upper, lower]生成lib/queryBuilder/FunctionBuilder.js。fn.now(precision)略有特殊精度参数会被解析为整数默认6并直接作为字面量拼进CURRENT_TIMESTAMP(precision)lib/queryBuilder/FunctionBuilder.js。源码注释说明这是为了让CURRENT_TIMESTAMP正常工作由于已确保 precision 是数字不存在 SQL 注入风险。mixin 与 compose组合多个插件const { mixin } require(objection); const { compose } require(objection);mixin和compose都是用于一次性应用多个 插件 的辅助函数二者用法略有差异。插件机制的完整说明见 插件指南 与 插件配方仓库中还提供了可直接运行的插件示例 examples/plugin 与 examples/plugin-with-options。mixin一次继承多个插件const { mixin, Model } require(objection); class Person extends mixin(Model, [ SomeMixin, SomeOtherMixin, EvenMoreMixins, LolSoManyMixins, ImAMixinWithOptions({ foo: bar }) ]) {}compose先组合、后继承const { compose, Model } require(objection); const mixins compose( SomeMixin, SomeOtherMixin, EvenMoreMixins, LolSoManyMixins, ImAMixinWithOptions({ foo: bar }) ); class Person extends mixins(Model) {}源码剖析从 lib/utils/mixin.js 看两者本质相同mixin(...)把参数拍平后用reduce依次把每个插件函数作用于当前类即mixinFunc(Class)链式包装compose(...)先拍平并收集插件列表返回一个高阶函数该函数再接收基类并调用mixin。因此compose更适合把插件集合定义为一个可复用的变量的场景。两者都支持传数组或不定参数且允许嵌套flatten会递归拍平。snakeCaseMappers 与 knexSnakeCaseMappers命名风格映射const { snakeCaseMappers } require(objection); const { knexSnakeCaseMappers } require(objection);这两个函数用于在数据库的snake_case列名与代码中的camelCase属性名之间自动转换。详细指南见 snake_case 到 camelCase 转换配方仓库中还有对应的集成测试 knexSnakeCase.js 与 snakeCase.js 可参考。两个函数都接受一个 options 对象可用选项完全一致OptionTypeDefaultDescriptionupperCasebooleanfalse设为true表示你的列名是 UPPER_SNAKE_CASE。underscoreBeforeDigitsbooleanfalse为true时数字前会插入下划线foo1Bar2→foo_1_bar_2为false时foo1Bar2→foo1_bar2。underscoreBetweenUppercaseLettersbooleanfalse为true时连续大写字母之间会插入下划线fooBAR→foo_b_a_r为false时fooBAR→foo_bar。snakeCaseMappers作用于模型层sankeCaseMappers返回{ parse, format }形式的列名映射器供模型的columnNameMappers使用const { Model, snakeCaseMappers } require(objection); class Person extends Model { static get columnNameMappers() { return snakeCaseMappers(); } }如果列名是 UPPER_SNAKE_CASEclass Person extends Model { static get columnNameMappers() { return snakeCaseMappers({ upperCase: true }); } }knexSnakeCaseMappers作用于 knex 层knexSnakeCaseMappers返回 knex 配置片段通过展开运算符注入 knex 配置const { knexSnakeCaseMappers } require(objection); const Knex require(knex); const knex Knex({ client: postgres, connection: { host: 127.0.0.1, user: objection, database: objection_test } ...knexSnakeCaseMappers() });UPPER_SNAKE_CASE 列名const knex Knex({ client: postgres, connection: { /* ... */ }, ...knexSnakeCaseMappers({ upperCase: true }) });旧版 Node 兼容写法对不支持对象展开的旧版 Nodeconst Knex require(knex); const knexSnakeCaseMappers require(objection).knexSnakeCaseMappers; const knex Knex({ client: postgres, connection: { /* ... */ }, ...knexSnakeCaseMappers() });源码剖析映射器的底层实现从 lib/utils/identifierMapping.js 可以看到实现要点snakeCaseMappers(opt)L165-L170返回{ parse: camelCase, format: snakeCase }且两个转换器都经过memoize缓存单参数函数的快速记忆化提升批量转换性能knexSnakeCaseMappers(opt)内部调用knexIdentifierMappersL172-L196生成 knex 的wrapIdentifier写 SQL 时把属性名格式化为列名与postProcessResponse读结果时把列名解析回属性名两个钩子snakeCase转换器按字符逐个处理并正确处理非 ASCII 字符与内部使用的:分隔符mapLastPart只转换最后一个:之后的部分见 L136-L143保证 objection 内部别名不受影响。knexIdentifierMapping任意静态映射const { knexIdentifierMapping } require(objection);knexIdentifierMapping与knexSnakeCaseMappers类似但用于在列名与属性名之间建立任意静态映射而不只是规则化的大小写转换。例如数据库中标识符为MyId、MyProp、MyAnotherProp你希望在代码中使用id、prop、anotherPropconst { knexIdentifierMapping } require(objection); const Knex require(knex); const knex Knex({ client: postgres, connection: { host: 127.0.0.1, user: objection, database: objection_test } ...knexIdentifierMapping({ MyId: id, MyProp: prop, MyAnotherProp: anotherProp }) });结合模型 jsonSchema 自动生成映射文档还给出了一个非常实用的进阶技巧可以在模型的jsonSchema中增加自定义属性如column然后批量扫描模型目录、自动生成映射对象避免手工维护映射表const { knexIdentifierMapping } require(objection); const Knex require(knex); const path require(path); const fs require(fs); // 指向你的模型目录。 const MODELS_PATH path.join(__dirname, models); const knex Knex({ client: postgres, connection: { host: 127.0.0.1, user: objection, database: objection_test } // 遍历所有模型用 jsonSchema 中的自定义属性 column 生成映射。 ...knexIdentifierMapping(fs.readdirSync(MODELS_PATH) .filter(it it.endsWith(.js)) .map(it require(path.join(MODELS_PATH, it))) .reduce((mapping, modelClass) { const properties modelClass.jsonSchema.properties; return Object.keys(properties).reduce((mapping, propName) { mapping[properties[propName].column] propName; return mapping; }, mapping); }, {}) ) });旧版 Node 写法const Knex require(knex); const knexIdentifierMapping require(objection).knexIdentifierMapping; const knex Knex({ client: postgres, connection: { /* ... */ }, ...knexIdentifierMapping({ MyId: id, MyProp: prop, MyAnotherProp: anotherProp }) });源码剖析从 lib/utils/identifierMapping.js 看knexIdentifierMapping(colToProp)会先根据传入的列名→属性名映射对象反向推导出属性名→列名的映射propToCol再交给knexIdentifierMappers生成 knex 的wrapIdentifier/postProcessResponse钩子未命中映射的标识符原样透传。这意味着该函数支持双向的任意映射且映射不完整时不会报错。错误类ValidationError、NotFoundError 与 db-errors 系列const { ValidationError } require(objection); const { NotFoundError } require(objection);模块还导出一组错误类用于区分不同类型的失败场景ValidationError模型验证失败时抛出对应 ValidationError 类型由 lib/model/ValidationError.js 实现配合 验证指南 使用NotFoundError查询目标不存在时抛出如findById未命中由 lib/model/NotFoundError.js 实现DBError及其子类全部来自db-errors库在 lib/objection.js 中直接引入并重新导出包括UniqueViolationError唯一约束冲突NotNullViolationError非空约束冲突ForeignKeyViolationError外键约束冲突ConstraintViolationError通用约束冲突CheckViolationError检查约束冲突DataError数据类错误如数值越界。这些错误类把不同数据库Postgres、MySQL、SQLite 等风格各异的底层驱动错误统一抽象为通用类型便于在业务代码中按错误类型做差异化的错误处理。相关实战写法可参考 错误处理配方 与仓库测试 error-handling 相关集成测试。典型用法const { Person, UniqueViolationError } require(objection); try { await Person.query().insert({ email: duplicateexample.com }); } catch (err) { if (err instanceof UniqueViolationError) { // 处理唯一键冲突 } }小结与阅读路线本文完整覆盖了 doc/api/objection/README.md 中require(objection)模块的全部公开导出核心类Model模型基类详见 Model API生命周期工具initialize预热表元数据、transaction多模型事务详见 事务指南SQL 构建工具ref标识符引用、raw裸 SQL、val值构建、fnSQL 函数全部对应 types API 中的构建器类插件组合mixin、compose详见 插件指南命名映射snakeCaseMappers、knexSnakeCaseMappers、knexIdentifierMapping详见 转换配方错误类ValidationError、NotFoundError及db-errors系列。在使用这些 API 时请记住 doc/api/README.md 的告诫只依赖文档化的公共 API避免使用本文与官方文档未提及的内部实现细节。掌握了模块层的全部导出你就能更自信地写出符合 objection.js 风格、安全且可维护的数据访问代码。赞分享数据库后端【免费下载链接】objection.jsAn SQL-friendly ORM for Node.js项目地址https://gitcode.com/gh_mirrors/ob/objection.js点击查看免费下载相关推荐Prisma 数据导出实战指南使用 prisma export 命令与 raw Export API 导出 NDF 数据Prisma 数据导出实战指南使用 prisma export 命令与 raw Export API 导出 NDF 数据 本指南聚焦 Prisma 1.x 服后端数据库GraphQLMMSegmentation 常用工具实战指南模型分析、导出与 TorchServe 部署MMSegmentation 常用工具实战指南模型分析、导出与 TorchServe 部署 MMSegmentation 在 tools/ 目录下提供了覆盖模人工智能深度学习计算机视觉Valdi Native View Model 实战指南用 ExportModel 与 ViewModel 将 TypeScript 数据模型导出到 iOS / AndroidValdi Native View Model 实战指南用 ExportModel 与 ViewModel 将 TypeScript 数据模型导出到 iO跨平台UI组件前端移动开发上一篇大模型训练中的 checkpoint 策略gh_mirrors/trl/trl实现下一篇使用 MXNet Scala API 构建深度学习应用从 NDArray 张量计算到分布式训练创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考