Mongodb / Mongoose
介绍
KUN Visual Novel 后端整体是采用 koa + mongodb 技术栈的,mongodb 是一个 NoSQL 数据库,与传统的 MySQL 和 PostgreSQL 可能有一些区别。
- 由于我们是
Nodejs的后端,所以我们采用mongoose来作为mongodb的驱动,或许您可能了解ORM,mongoose就好比Hibernate,MyBatis之类的东东 - 在
mongodb中,Collection和Document可以类比于关系型数据库中的Table和Row - 关于
Schema,在mongodb中Schema是数据库中Collection的结构描述,在mongoose中,它是一层ORM,mongoose的schema不仅用于描述数据模型的结构,还可以定义数据模型的方法、虚拟字段等。 - 关于
Model,在mongodb中Model是Collection的抽象,它允许应用程序执行数据库操作。由于mongoose的缘故,使用mongoose.model可以很方便的使用Model与数据库进行交互,它通过定义Schema和使用模型的方式,将应用程序中的数据映射到数据库文档。 - 关于数据库范式,由于这是
NoSQL,所以关系范式没有啦。因此您可以看到我们的用户 Model 等完全不遵循关系数据库范式。
为什么要选 Mongodb
Mongodb 似乎非常适合论坛这种网站,只是听说
难道就我一个人觉得 Mongo 这个词很瑟琴吗
Mongodb
本项目中我们使用了 Mongodb 作为数据库,它是一个非关系型数据库
可以查看 Mongodb Documentation 来进行了解,这是良好的学习方式
Mongoose
本项目中我们使用了 Mongoose 来在 Nodejs 环境下操作 Mongodb,官网(v8.0.3)的描述是
elegant mongodb object modeling for node.js
可以查看 Mongoose Documentation 来进行了解
使用
本项目中在 src/db/connection.ts 定义了 Mongodb 的连接
import mongoose from 'mongoose'
import env from '@/config/config.dev'
const DB_URL = `mongodb://${env.MONGO_USERNAME}:${env.MONGO_PASSWORD}@${env.MONGO_HOSTNAME}:${env.MONGO_PORT}/${env.DB_NAME}`
mongoose.connect(DB_URL)
export default mongoose我们已经在根目录的 .env 文件中定义了所需的环境变量,我们这里将其参数(用户名、密码、HOST、PORT、数据库名)拼合在一起形成了一个 Mongodb 的地址,然后使用 Mongoose 来连接 Mongodb
Models
需要给 Mongoose 定义一层 Model 才能通过 Mongoose 将集合映射到数据库
大白话就是定义一个这样的数据结构,Mongoose 才能把数据写到数据库里
可以查看本项目 src/models 文件夹下,里面存放了所有的 model
Transaction
Transaction 是一个很重要的特性,在 mongodb 中同样存在 Transaction,KUN Visual Novel 采用 mongoose,会以如下方式使用 Transaction
// Start Transaction
const session = await mongoose.startSession()
session.startTransaction()
try {
...some operation
} catch (error) {
// If catch error, abort transaction
await session.abortTransaction()
session.endSession()
throw error
}Trigger
在 mongodb 中,没有 trigger,但是 KUN Visual Novel 借助 mongoose 的 pre-save 实现了类似的效果
// pre-save hook,increase nid before save document
NonMoeSchema.pre('save', increasingSequence('nid'))Join
mongodb 中没有 join,但是我们可以用类似的方法实现,例如 Embedded, References, Aggregation,由于 KUN Visual Novel 未采用 _id 作为集合的唯一查询标识 (_id 是有的,只是没有用而已),因为我们觉得在浏览器地址栏输入 topics/1 或者 kungalgamer/1,就能进入用户或者话题的主页是一件非常酷的事情,所以我们将每一个 Schema 都添加了一个字段 XXXid,用来标识唯一性。因此我们无法使用 ref 来进行类似于 join 的操作,所以我们使用了 Virtual 来实现类似的操作。
// Create virtual 'users'
TopicSchema.virtual('user', {
ref: 'user',
localField: 'uid',
foreignField: 'uid',
})使用的时候只需要
const topics = await TopicModel.find(query)
.sort(sortOptions)
.skip(skip)
.limit(limit)
.populate('user', 'uid avatar name')
.lean()