Knex 是 js 编写的 SQL 查询构建器。将 SQL 运算符封装成函数,可以链式调用。
基本使用
安装依赖包
shell
pnpm add knex
shell
pnpm add mysql2
编写代码
以下代码来自于 Knex 官网
- 创建一个数据库连接
javascript
const knex = require("knex")({
client: "mysql2",
connection: {
host: "127.0.0.1",
port: 3306,
user: "your_database_user",
password: "your_database_password",
database: "myapp_test",
},
});
- 调用查询构建器的函数
Knex 提供了丰富的查询函数,每个查询函数和 DBMS 的关系运算符一一对应。对于只有特定的 DBMS 才支持的运算符,Knex 官方文档中也有注明。
javascript
const query = knex.select("title", "author", "year").from("books");
- 返回查询结果
javascript
query.then((result) => {
console.log(result);
});
动态 SQL 构建
动态 SQL 和静态 SQL 的区别
上面的示例代码中生成的 SQL 查询是静态的,因为代码执行逻辑是固定的,不涉及到条件判断,循环执行。
如果需要根据业务逻辑,在 if-else
或者 for
循环中生成 SQL 查询语句,需要怎么做呢?
我们先看下最简单的办法:直接在 if-else
或者 for
循环中调用 knex 查询函数。
javascript
let age = 18;
// xxx 修改age的值
if (age >= 18) {
knex.select("xxx");
} else {
knex.select("yyy");
}
简单的业务逻辑可以这样写,但是,如果业务逻辑越来越复杂,这样写就不好维护了。
有以下缺点:
- knex 的执行上下文和业务逻辑耦合严重
- 业务逻辑中重复的部分无法复用
我们可以做以下改进:
- 参考策略模式,将每步关系运算分别定义成一个函数;将每种条件下的逻辑封装到一个函数 fn 中
- 将函数 fn 的定义和执行分开: 定义一个数组,存放函数 fn,业务逻辑专注于把不同条件下的函数放入数组中,最后统一执行数组中的每个函数
伪代码如下:
javascript
const array = [];
// 在业务逻辑中定义函数对象,按照执行顺序添加到数组中
if (age >= 18) {
let param = 6;
// 修改param的值
const fn = function () {
return this.where("xxx", param);
};
array.push(fn);
}
// 遍历数组,逐个执行函数
let prev = knex;
for (const item of array) {
prev = item();
}
我们编写一个函数来构建动态 SQL。
函数定义
输入
一个函数数组,每项为一个函数,函数没有入参;如果有变量依赖,就通过闭包的形式在函数内引用外部的变量 输出
执行完查询函数的 Knex 对象
函数体逻辑
- 在业务逻辑中定义函数,封装某一小部分查询逻辑,把函数放入数组中
- 最后统一执行数组中的每个函数
代码实现
具体的业务逻辑这里就不写了,只写往数组中 push 函数的代码
-
定义一个数组,存放函数
javascriptconst array = [];
-
往数组中 push 函数 有几个细节要注意: 2.1 函数执行时,knex 的函数通过
this
来调用。原因:需要在上一次调用的基础上执行本次调用 2.2 函数不能用箭头函数,因为执行时要改变this
的指向。 2.3 函数需要有返回值,返回this
(knex 的函数返回的也是this
) 2.4 如果需要依赖变量,可以在函数中直接引用外部定义的变量或者对象,形成闭包。javascriptconst selectFn = function () { return this.select("t_city.name", "t_city.abbreviation"); }; array.push(selectFn); const tableFn = function () { return this.from("t_city"); }; array.push(tableFn); const whereFn = function () { return this.where("t_city.name", "广州市"); }; array.push(whereFn);
-
遍历数组,执行函数
遍历数组时,由于需要在上一次调用的基础上执行本函数,所以需要改变 this
的指向。这里使用了 js function 的 call
函数
本函数的返回值,需要保存下来,供下个函数调用,所以这里用了临时变量 prev
来保存函数的返回值
javascript
let prev = knex;
for (const fn of array) {
prev = fn.call(prev);
}
const res = await prev;
最终生成的 SQL 语句如下:
sql
select `t_city`.`name`, `t_city`.`abbreviation` from `t_city` where `t_city`.`name` = ?
代码运行结果如下:

附: 数据库表结构
sql
CREATE TABLE `t_city` (
`city_id` int(11) NOT NULL,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`abbreviation` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`city_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
完整代码
javascript
const knex = require("knex")({
client: "mysql2",
connection: {
host: "127.0.0.1",
port: 3306,
user: "your_database_user",
password: "your_database_password",
database: "myapp_test",
},
});
async function main() {
const array = [];
const selectFn = function () {
return this.select("t_city.name", "t_city.abbreviation");
};
array.push(selectFn);
const tableFn = function () {
return this.from("t_city");
};
array.push(tableFn);
const whereFn = function () {
return this.where("t_city.name", "广州市");
};
array.push(whereFn);
let prev = knex;
for (const fn of array) {
prev = fn.call(prev);
}
const res = await prev;
console.log(prev.toSQL().sql);
console.log(res);
}
main();