knex.js的基本使用及动态SQL的生成

Knex 是 js 编写的 SQL 查询构建器。将 SQL 运算符封装成函数,可以链式调用。

基本使用

安装依赖包

shell 复制代码
pnpm add knex
shell 复制代码
pnpm add mysql2

编写代码

以下代码来自于 Knex 官网

  1. 创建一个数据库连接
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",
  },
});
  1. 调用查询构建器的函数

Knex 提供了丰富的查询函数,每个查询函数和 DBMS 的关系运算符一一对应。对于只有特定的 DBMS 才支持的运算符,Knex 官方文档中也有注明。

javascript 复制代码
const query = knex.select("title", "author", "year").from("books");
  1. 返回查询结果
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");
}

简单的业务逻辑可以这样写,但是,如果业务逻辑越来越复杂,这样写就不好维护了。

有以下缺点:

  1. knex 的执行上下文和业务逻辑耦合严重
  2. 业务逻辑中重复的部分无法复用

我们可以做以下改进:

  1. 参考策略模式,将每步关系运算分别定义成一个函数;将每种条件下的逻辑封装到一个函数 fn 中
  2. 将函数 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 对象

函数体逻辑

  1. 在业务逻辑中定义函数,封装某一小部分查询逻辑,把函数放入数组中
  2. 最后统一执行数组中的每个函数

代码实现

具体的业务逻辑这里就不写了,只写往数组中 push 函数的代码

  1. 定义一个数组,存放函数

    javascript 复制代码
    const array = [];
  2. 往数组中 push 函数 有几个细节要注意: 2.1 函数执行时,knex 的函数通过 this 来调用。原因:需要在上一次调用的基础上执行本次调用 2.2 函数不能用箭头函数,因为执行时要改变 this 的指向。 2.3 函数需要有返回值,返回 this(knex 的函数返回的也是 this) 2.4 如果需要依赖变量,可以在函数中直接引用外部定义的变量或者对象,形成闭包。

    javascript 复制代码
    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);
  3. 遍历数组,执行函数

遍历数组时,由于需要在上一次调用的基础上执行本函数,所以需要改变 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();
相关推荐
Bdygsl6 分钟前
Node.js(1)—— Node.js介绍与入门
node.js
Java 码农1 小时前
nodejs koa留言板案例开发
前端·javascript·npm·node.js
胡gh2 小时前
浏览器:我要用缓存!服务器:你缓存过期了!怎么把数据挽留住,这是个问题。
前端·面试·node.js
阿里云大数据AI技术3 小时前
ODPS 十五周年实录 | Data + AI,MaxCompute 下一个15年的新增长引擎
大数据·python·sql
mask哥5 小时前
详解flink SQL基础(四)
java·大数据·数据库·sql·微服务·flink
ccnocare5 小时前
Node.js ZIP 安装
node.js
码上有料10 小时前
Node.js中XLSX库的实践使用指南
node.js
携欢10 小时前
Portswigger靶场之 Blind SQL injection with time delays通关秘籍
数据库·sql
前端老鹰11 小时前
Node.js 网页解析神器:cheerio 模块实战指南,像 jQuery 一样玩转 HTML
后端·node.js
Hilaku11 小时前
前端需要掌握多少Node.js?
前端·javascript·node.js