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());
});
也可以直接在数据库上设置好级联删除,感觉这个插件没什么必要
吐槽
最近一直在研究这个东西,百度出来的都是该模块的仓库和文档,本人英语太渣,看不懂洋文,文档看的一头雾水。
看他祖宗 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 存在的意义是什么。