抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

Bookshelf.js 是基于 Knex SQL 查询生成器构建的 Node.js 的 JavaScript ORM。

Bookshelf 是个查询生成器,最终执行还得是 Knex。

初步构建

目录结构

├── database.ts
├── index.ts
└── model
    ├── post.ts
    └── user.ts

1 directory, 4 files

表结构

# user 表
mysql> desc user;
+-------+--------------+------+-----+---------+----------------+
| Field | Type         | Null | Key | Default | Extra          |
+-------+--------------+------+-----+---------+----------------+
| name  | varchar(255) | YES  |     | NULL    |                |
| id    | int(3)       | NO   | PRI | NULL    | auto_increment |
+-------+--------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)
# post 表 (外键:user_id)
mysql> desc post;
+---------+--------------+------+-----+---------+----------------+
| Field   | Type         | Null | Key | Default | Extra          |
+---------+--------------+------+-----+---------+----------------+
| title   | varchar(255) | YES  |     | NULL    |                |
| id      | int(5)       | NO   | PRI | NULL    | auto_increment |
| user_id | int(5)       | YES  | MUL | NULL    |                |
+---------+--------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)

我用的是 Typescript 可能跟官方文档的写法不太一样。

database.ts

// database.ts
import Knex = require("knex");
import Bookshelf = require("bookshelf");

const kn = Knex.knex({
  client: "mysql",
  connection: {
    host: "101.35.5.117",
    user: "xxxxx",
    password: "xxxxxx",
    database: "sunzehui",
    charset: "utf8",
  },
});
export = Bookshelf(kn as any) as Bookshelf;

model/post.ts

import bookshelf = require("../database");
import User = require("./user");

export default class Post extends bookshelf.Model<Post> {
  get tableName() {
    return "post";
  }
  user(): User {
    return this.belongsTo(User, "user_id");
  }
}

model/user.ts

import Bookshelf = require("bookshelf");
import bookshelf = require("../database");
import Post from "./post";

export default class User extends bookshelf.Model<User> {
  get tableName() {
    return "user";
  }

  post(): Bookshelf.Collection<Post> {
    return this.hasMany(Post);
  }
}

简单增删改查

需要先引入自己写的模块

import User from "./model/user";
import Post from "./model/post";
import bookshelf = require("./database");

以下操作均省略导入模块的代码

R - Retrieve

查询所有 user

new User()
  .fetchAll()
  .then((r) => r.toJSON())
  .then(console.log);
// => select * from user;

查询所有叫 sunzehui 的 user (条件)

new User()
  .where({ name: "sunzehui" })
  .fetchAll()
  .then((r) => r.toJSON())
  .then(console.log);
// => select * from user where name = 'sunzehui';

查询所有叫 sunzehui 的 id (投影)

new User()
  .where({ name: "sunzehui" })
  .fetchAll({
    columns: ["id"],
  })
  .then((r) => r.toJSON())
  .then(console.log);
// => select id from user where name = 'sunzehui';

查询 sunzehui 所有的 post (多表连接)

new User()
    .where({ name: "sunzehui" })
    .fetch({ withRelated: ["post"] })
    .then((r) => r.toJSON())
    .then((r) => {
      console.log("sunzehui 同学有什么书?", r);
    });
// => select * from user, post 
//			where user.id = post.user_id 
//			and user.name = 'sunzehui';
/*
output:
sunzehui 同学有什么书? {
  name: 'sunzehui',
  id: 26,
  post: [
    { title: '语义Web编程', id: 2, user_id: 26 }
  ]
}
*/

C - Create

添加用户

(async () => {
  await new User({
    name: "wangdefa",
  }).save();
})();

添加书籍

因为一个用户对应多本书,所以添加书籍信息时需要将所属用户 id 也填入,两条 SQL 就可以搞定。

但是为了数据完整性,需要使用事务来进行原子操作。

import bluePromise = require("bluebird");
bookshelf
    .transaction((t) => {
      return new User()
        .where({ name: "sunzehui" })
        .fetch()
        .tap((model) => {
          return bluePromise.map(
            [
              { title: "Canterbury Tales" },
              { title: "Moby Dick" },
              { title: "Hamlet" },
            ],
            (info) => {
              return new Post(info).save(
                { user_id: model.id },
                { transacting: t }
              );
            }
          );
        })
        .then(t.commit, t.rollback);
    })

这样,三本书就添加到了 sunzehui 的账户下了。

// 执行一次 联合查询:
sunzehui 同学有什么书? {
  name: 'sunzehui',
  id: 24,
  post: [
    { title: '语义Web编程', id: 1, user_id: 24 },
    { title: 'Canterbury Tales', id: 5, user_id: 24 },
    { title: 'Moby Dick', id: 6, user_id: 24 },
    { title: 'Hamlet', id: 7, user_id: 24 }
  ]
}

U - Update

简单更新

// update authors set "bio" = 'Short user bio' where "id" = 1
new Author({id: 1, first_name: 'User'})
    .save({bio: 'Short user bio'}, {patch: true})
    .then(model => {
        // ...
    });

D - Delete

简单删除

new Post()
  .where({ name: "语义web编程" })
  .destroy()
  .then(r=>r.toJSON())
  .then(console.log);
// => delete from post where name = '语义web编程';

级联删除

删除 user 的时候需要把附带的 book 信息也删除的话,可以使用官方推荐的插件:

seegno/bookshelf-cascade-delete: Cascade delete with Bookshelf.js (github.com)

需要在Model上写明依赖的属性

export default class User extends bookshelf.Model<User> {
  get tableName() {
    return "user";
  }
  static dependents: ["post"];
	// ...
}
new User()
  .where({ name: "zhang" })
  .destroy()
  .then((r) => {
    console.log(r);
    console.log(r.toJSON());
  });

也可以直接在数据库上设置好级联删除,感觉这个插件没什么必要

image-20211114135538118

吐槽

最近一直在研究这个东西,百度出来的都是该模块的仓库和文档,本人英语太渣,看不懂洋文,文档看的一头雾水。

看他祖宗 Knex 的写法,感觉更贴近原生 SQL 语句,对比一下:

// Bookshelf
new User()
	.where({
  	first_name: 'Test',
  	last_name:  'User'
	})
	.fetchAll({ columns: ["id"] })
// knex
knex('users').select('id').where({
  first_name: 'Test',
  last_name:  'User'
})
select `id` from `users` where `first_name` = 'Test' and `last_name` = 'User';

不知道这个 Bookshelf 存在的意义是什么。

评论