ZetCode

Bookshelf.js 教程

最后修改于 2023 年 10 月 18 日

在本文中,我们将展示如何使用 Bookshelf.js ORM 在 JavaScript 中编程数据库。Bookshelf.js 构建在 Knex 之上。

Bookshelf.js

Bookshelf.js 是一个用于 Node.js 的 JavaScript ORM,构建在 Knex SQL 查询构建器之上。 它同时支持基于 Promise 和传统的 callback 接口。Bookshelf 提供了事务支持、渴望/嵌套渴望关系加载、多态关联,以及对一对一、一对多和多对多关系的支持。

Bookshelf.js 适用于 PostgreSQL、MySQL 和 SQLite3。

对象关系映射 (ORM) 是一种从面向对象语言访问关系数据库的技术。 它是 Python 数据库 API 的抽象。 在本文中,我们使用 PostgreSQL。

城市表

我们使用 cities 表。

cities_postgresql.sql
DROP TABLE IF EXISTS cities;
CREATE TABLE cities(id serial PRIMARY KEY, name VARCHAR(255), population INT);

INSERT INTO cities(name, population) VALUES('Bratislava', 432000);
INSERT INTO cities(name, population) VALUES('Budapest', 1759000);
INSERT INTO cities(name, population) VALUES('Prague', 1280000);
INSERT INTO cities(name, population) VALUES('Warsaw', 1748000);
INSERT INTO cities(name, population) VALUES('Los Angeles', 3971000);
INSERT INTO cities(name, population) VALUES('New York', 8550000);
INSERT INTO cities(name, population) VALUES('Edinburgh', 464000);
INSERT INTO cities(name, population) VALUES('Berlin', 3671000);

设置 Bookshelf.js

我们安装 Bookshelf。

$ node -v
v16.10.0

我们使用 Node 版本 16.10.0。

$ npm init -y

我们启动一个新的 Node 应用程序。

$ npm i pg
$ npm i knex bookshelf

我们安装 PostgreSQL 驱动程序、Knex.js 和 Bookshelf.js。

Bookshelf 统计行数

在第一个示例中,我们统计 cities 表中的行数。

config/db.js
const knex = require('knex')({
    client: 'pg',
    connection: {
        host: '127.0.0.1',
        user: 'postgres',
        password: '',
        database: 'testdb',
        charset: 'utf8'
    }
});

module.exports.knex = knex;

db.js 文件中,我们定义了一个 Knex 客户端对象。

model/city.js
const knex = require('../config/db').knex;
const bookshelf = require('bookshelf')(knex);

const City = bookshelf.Model.extend({
    tableName: 'cities'
});

module.exports = City;

我们有一个模型对象。 模型对象映射到数据库表中的一行。

count_cities.js
const knex = require('./config/db').knex;
const City = require('./model/city');

City.count().then((count) => {
    console.log(`There are ${count} cities`);
}).catch((err) => {
    console.log(err);
}).finally(() => {
    knex.destroy();
});

该示例统计 cities 表中的行数。 它使用回调函数。

$ node count_cities.js
There are 8 cities
count_cities2.js
const knex = require('./config/db').knex;
const City = require('./model/city');

async function countCities() {

    try {

        let count = await City.count();

        console.log(`There are ${count} cities`);
    } catch (e) {

        logger.info(`No data found ${e}`);
    } finally {

        knex.destroy();
    }
}

countCities();

在第二个示例中,我们使用带 async/await 的 Promise。

Bookshelf fetch

fetch 从数据库中获取一个模型,使用当前在模型上设置的任何属性来形成一个 select 查询。 如果设置了 require 选项,则在结果为空时,返回的响应将因 NotFoundError 而被拒绝。

$ npm i winston

在此示例中,我们还使用了 Winston 日志模块。

fetch_city.js
const knex = require('./config/db').knex;
const City = require('./model/city');
const winston = require('winston');

const consoleTransport = new winston.transports.Console()
const options = {
    transports: [consoleTransport]
}
const logger = new winston.createLogger(options)

async function fetch_city() {

    try {

        let val = await City.where({ 'name': 'Bratislava' }).fetch({require:true});
        // console.log(val.toJSON());
        logger.info(val);
    } catch (e) {

        logger.info(`No data found ${e}`);
    } finally {

        knex.destroy();
    }
}

fetch_city();

该示例检索指定的城市。

let val = await City.where({ 'name': 'Bratislava' }).fetch({require:true});

我们获取一个名为“Bratislava”的模型。

logger.info(val);

我们记录返回的数据。

$ node fetch_city.js
{"message":{"id":1,"name":"Bratislava","population":432000},"level":"info"}

Bookshelf fetch_all

fetch_all 从数据库中获取一组模型,使用当前在模型上设置的任何查询参数来形成一个 select 查询。

fetch_all.js
const knex = require('./config/db').knex;
const City = require('./model/city');

async function fetch_all() {

    try {

        let vals = await City.fetchAll();
        console.log(vals.toJSON());
    } catch (e) {

        console.log(`Failed to fetch data: ${e}`);
    } finally {

        knex.destroy();
    }
}

fetch_all();

该示例检索所有城市。

let vals = await City.fetchAll();

我们调用 fetchAll 函数。

console.log(vals.toJSON());

数据以 JSON 格式写入控制台。

$ node fetch_all.js
[ { id: 1, name: 'Bratislava', population: 432000 },
    { id: 2, name: 'Budapest', population: 1759000 },
    { id: 3, name: 'Prague', population: 1280000 },
    { id: 4, name: 'Warsaw', population: 1748000 },
    { id: 5, name: 'Los Angeles', population: 3971000 },
    { id: 6, name: 'New York', population: 8550000 },
    { id: 7, name: 'Edinburgh', population: 464000 },
    { id: 8, name: 'Berlin', population: 3671000 } ]

Bookshelf forge 助手

Bookshelf 的 forge 是一个简单的辅助函数,用于实例化一个新模型,而不需要 new 关键字。

forge_helper.js
const knex = require('./config/db').knex;
const City = require('./model/city');

async function fetch_city() {

    try {

        let val = await City.forge({ 'id': '4' }).fetch();
        console.log(val.toJSON());
    } catch (e) {

        console.info(`No data found ${e}`);
    } finally {

        knex.destroy();
    }
}

fetch_city();

在示例中,我们使用 forge 助手选择一个城市。

Bookshelf 保存城市

使用 save 保存一个新模型。

save_city.js
const knex = require('./config/db').knex;
const City = require('./model/city');

async function save_city() {

    try {

        let val = await City.forge({ 'name': 'Kyiv', 'population': 2884000}).save();
        console.log(val.toJSON());
    } catch (e) {

        console.log(`Failed to save data: ${e}`);
    } finally {

        knex.destroy();
    }
}

save_city();

该示例保存了一个新城市。

$ node save_city.js
{ name: 'Kyiv', population: 2884000, id: 9 }
$ node fetch_all.js
[ { id: 1, name: 'Bratislava', population: 432000 },
    { id: 2, name: 'Budapest', population: 1759000 },
    { id: 3, name: 'Prague', population: 1280000 },
    { id: 4, name: 'Warsaw', population: 1748000 },
    { id: 5, name: 'Los Angeles', population: 3971000 },
    { id: 6, name: 'New York', population: 8550000 },
    { id: 7, name: 'Edinburgh', population: 464000 },
    { id: 8, name: 'Berlin', population: 3671000 },
    { id: 9, name: 'Kyiv', population: 2884000 } ]

Bookshelf orderBy

orderBy 函数按指定的列名和排序顺序对检索到的数据进行排序。 order 参数是可选的,默认为“ASC”。

order_by.js
const knex = require('./config/db').knex;
const City = require('./model/city');

async function fetch_city() {

    try {

        let vals = await City.forge().orderBy('name', 'DESC').fetchAll({require:true});
        console.log(vals.toJSON());
    } catch (e) {

        console.log(`Failed to fetch data: ${e}`);
    } finally {

        knex.destroy();
    }
}

fetch_city();

在示例中,我们获取所有城市并按名称降序排列它们。

$ node order_by.js
[ { id: 4, name: 'Warsaw', population: 1748000 },
  { id: 3, name: 'Prague', population: 1280000 },
  { id: 6, name: 'New York', population: 8550000 },
  { id: 5, name: 'Los Angeles', population: 3971000 },
  { id: 9, name: 'Kyiv', population: 2884000 },
  { id: 7, name: 'Edinburgh', population: 464000 },
  { id: 2, name: 'Budapest', population: 1759000 },
  { id: 1, name: 'Bratislava', population: 432000 },
  { id: 8, name: 'Berlin', population: 3671000 } ]

Bookshelf 一对一关系

一对一关系使用 hasOnebelongsTo 函数定义。

employees_projects.sql
DROP TABLE IF EXISTS employees;
DROP TABLE IF EXISTS projects;

CREATE TABLE projects(id serial PRIMARY KEY, name VARCHAR(255));
INSERT INTO projects(name) VALUES('Project A');
INSERT INTO projects(name) VALUES('Project B');
INSERT INTO projects(name) VALUES('Project C');

CREATE TABLE employees(id serial PRIMARY KEY, project_id INT REFERENCES projects (id),
    name VARCHAR(255));
INSERT INTO employees(project_id, name) VALUES(2, 'John Doe');
INSERT INTO employees(project_id, name) VALUES(1, 'Lucia Smith');

我们有 employeesprojects。一个员工只能分配给一个项目。

Bookshelf hasOne

hasOne 函数定义模型之间的一对一关系。 hasOne 关系指定表只有一个其他类型的对象,由另一个表中的外键指定。

model/project.js
const knex = require('../config/db').knex;
const bookshelf = require('bookshelf')(knex);
const Employee = require('./employee');

const Project = bookshelf.Model.extend({
    tableName: 'projects',
    employee: function () {
        return this.hasOne(Employee);
    }
});

module.exports = Project;

Project 模型包含 hasOne 函数。 通过查询一个项目,我们可以获取其链接的员工。

model/employee.js
const knex = require('../config/db').knex;
const bookshelf = require('bookshelf')(knex);

const Employee = bookshelf.Model.extend({
    tableName: 'employees'
});

module.exports = Employee;

这是 Employee 模型。

has_one.js
const knex = require('./config/db').knex;
const Project = require('./model/project');

async function doQuery() {

    try {

        let val = await Project.where({ id: 2 }).fetch({
            withRelated: ['employee']
        });

        console.log(val.toJSON());
    } catch (e) {

        console.log(`Failed to fetch data: ${e}`);
    } finally {

        knex.destroy();
    }
}

doQuery();

在示例中,我们获取一个项目及其关联的员工。

let val = await Project.where({ id: 3 }).fetch({
    withRelated: ['employee']
});

指定了 withRelated 选项以获取集合的模型,急切地加载在模型上命名的任何指定关系。 如果没有此选项,我们只获得项目,而没有其链接的员工。

$ node has_one.js
{ id: 2,
  name: 'Project B',
  employee: { id: 1, project_id: 2, name: 'John Doe' } }

Bookshelf 在一对一关系中使用 belongsTo

当一个模型是另一个目标模型的成员时,使用 belongsTo 函数。 外键在当前(源)模型中定义。 belongsTo 函数用于一对一和 一对多 关系。

model/project.js
const knex = require('../config/db').knex;
const bookshelf = require('bookshelf')(knex);

const Project = bookshelf.Model.extend({
    tableName: 'projects'
});

module.exports = Project;

这是 Project 模型。

model/employee.js
const knex = require('../config/db').knex;
const bookshelf = require('bookshelf')(knex);
const Project = require('./project');

const Employee = bookshelf.Model.extend({
    tableName: 'employees',
    project: function () {
        return this.belongsTo(Project);
    }
});

module.exports = Employee;

Employee 包含 belongsTo 函数。

belongs_to.js
const knex = require('./config/db').knex;
const Employee = require('./model/employee');

async function doQuery() {

    try {

        let val = await Employee.where({ id: 1 }).fetch({
            withRelated: ['project'], require: true
        });

        console.log(val.toJSON());

    } catch (e) {

        console.log(`Failed to fetch data: ${e}`);
    } finally {

        knex.destroy();
    }
}

doQuery();

在示例中,我们获取一个员工及其链接的项目。

$ node belongs_to.js
{ id: 1,
    project_id: 2,
    name: 'John Doe',
    project: { id: 2, name: 'Project B' } }

Bookshelf 一对多关系

一对多关系使用 hasManybelongsTo 函数定义。

users_tasks.js
DROP TABLE IF EXISTS tasks;
DROP TABLE IF EXISTS users;

CREATE TABLE users(id serial PRIMARY KEY, name VARCHAR(255));
INSERT INTO users(name) VALUES('John Doe');
INSERT INTO users(name) VALUES('Lucia Smith');

CREATE TABLE tasks(id serial PRIMARY KEY, user_id INT REFERENCES users (id),
    name VARCHAR(255));
INSERT INTO tasks(user_id, name) VALUES(1, 'Task A');
INSERT INTO tasks(user_id, name) VALUES(1, 'Task B');
INSERT INTO tasks(user_id, name) VALUES(1, 'Task C');
INSERT INTO tasks(user_id, name) VALUES(2, 'Task D');
INSERT INTO tasks(user_id, name) VALUES(2, 'Task E');

我们有 userstasks。一个用户可以有一个或多个待办事项。 一个任务只能由一个用户拥有。

Bookshelf hasMany

hasMany 定义模型之间的一对多关系。 该关系指定当前模型在另一个表中有一个或多个与此模型的主键匹配的行。

model/user.js
const knex = require('../config/db').knex;
const bookshelf = require('bookshelf')(knex);
const Task = require('./task');

const User = bookshelf.Model.extend({
    tableName: 'users',
    tasks: function() {
        return this.hasMany(Task);
      }
});

module.exports = User;

User 模型包含 hasMany 函数。

model/task.js
const knex = require('../config/db').knex;
const bookshelf = require('bookshelf')(knex);

const Task = bookshelf.Model.extend({
    tableName: 'tasks',
});

module.exports = Task;

这是 Task 模型。

has_many.js
const knex = require('./config/db').knex;
const User = require('./model/user');

async function doQuery() {

    try {

        let val = await User.where({ id: 1 }).fetch({
            withRelated: ['tasks'], require: true
        });

        console.log(val.toJSON());

    } catch (e) {

        console.log(`Failed to fetch data: ${e}`);
    } finally {

        knex.destroy();
    }
}

doQuery();

在示例中,我们获取一个用户及其任务。

$ node has_many.js
{ id: 1,
    name: 'John Doe',
    tasks:
    [ { id: 1, user_id: 1, name: 'Task A' },
        { id: 2, user_id: 1, name: 'Task B' },
        { id: 3, user_id: 1, name: 'Task C' } ] }

ID 为 1 的用户有三个任务。

Bookshelf 在一对多关系中使用 belongsTo

在一对多数量中,belongsTohasMany 的逆,并且是关联的一侧。

model/user.js
const knex = require('../config/db').knex;
const bookshelf = require('bookshelf')(knex);

const User = bookshelf.Model.extend({
    tableName: 'users',
});

module.exports = User;

这是 User 模型。

model/task.js
const knex = require('../config/db').knex;
const bookshelf = require('bookshelf')(knex);
const User = require('./user');

const Task = bookshelf.Model.extend({
    tableName: 'tasks',
    user: function() {
        return this.belongsTo(User);
    }
});

module.exports = Task;

Task 模型包含 belongsTo 函数。

belongs_to2.js
const knex = require('./config/db').knex;
const Task = require('./model/task');

async function doQuery() {

    try {

        let val = await Task.where({ id: 4 }).fetch({
            withRelated: ['user'], require: true
        });

        console.log(val.toJSON());

    } catch (e) {

        console.log(`Failed to fetch data: ${e}`);
    } finally {

        knex.destroy();
    }
}

doQuery();

在示例中,我们获取一个任务及其关联的用户。

来源

Bookshelf 文档

在本文中,我们使用了 Bookshelf 库。 我们创建了一些与 PostgreSQL 交互的命令行程序。

作者

我的名字是 Jan Bodnar,我是一位充满激情的程序员,拥有丰富的编程经验。 自 2007 年以来,我一直在撰写编程文章。 迄今为止,我撰写了 1,400 多篇文章和 8 本电子书。 我拥有十多年教授编程的经验。

查看 所有 JavaScript 教程。