NodeJS+持久化学习笔记
持久化
nodejs中实现持久化的方法
- 文件系统fs
- 数据库:
关系型数据库——MySQL
文档型数据库——MongoDB
键值对数据库——Redis
文件系统数据库
- 自己简单实现一个文件系统读写数据库,代码如下:
// 实现一个文件系统读写数据库
const fs = require('fs')
function get(key) {
fs.readFile('./db.json', (err, data) => {
const json = JSON.parse(data)
console.log(json[key]);
})
}
function set(key, value){
fs.readFile('./db.json', (err, data) => {
// 可能是空文件,则设置为对象
const json = data ? JSON.parse(data) : {}
json[key] = value // 设置值
// 重新写入文件
fs.writeFile('./db.json', JSON.stringify(json), err => {
if(err){
console.log(err)
}
console.log('Operation Successfully!')
})
})
}
// 命令行接口部分
const readline = require('readline')
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})
rl.on('line', function(input) {
const [op, key, value] = input.split(' ')
if(op === 'get'){
get(key)
}else if(op === 'set'){
set(key, value)
}else if(op === 'quit'){
rl.close()
}else{
console.log('There is no such operation.')
}
})
rl.on('close', function() {
console.log('Program end')
process.exit(0)
})
MySQL
资源
安装配置
- Mac环境安装MySQL
- Windows环境安装MySQL
node.js原生驱动
- 安装 mysql 模块:npm i mysql --save
- mysql 模块基本使用
const mysql = require('mysql')
// 连接配置
const cfg = {
host: 'localhost',
user: 'root',
password: 'yw@910714',
database: 'yw_test_db'
}
// 创建连接对象
const conn = mysql.createConnection(cfg)
// 连接数据库
conn.connect(err => {
if(err){
throw err
}
console.log('Connection Successfully!')
})
// sql语句
const CREATE_TABLE = `
create table if not exists test (
id int not null auto_increment,
message varchar(45) null,
primary key(id)
)
`
const INSERT_SQL = `insert into test(message) values(?)`
const SELECT_SQL = `select * from test`
// 查询 conn.query()
conn.query(CREATE_TABLE, err => {
if(err){
throw err
}
// 插入数据
conn.query(INSERT_SQL, 'hello, mysql', (err, result) => {
if(err){
throw err
}
console.log(result)
// 查询数据
conn.query(SELECT_SQL, (err, results) => {
console.log(results)
conn.end() // 若query语句有嵌套,则end需要在此执行
})
})
})
- query的Promise封装
function query(conn, sql, params=null) {
return new Promise((resolve, reject) => {
conn.query(sql, params, (err, results) => {
if(err) {
reject(err)
}else{
resolve(results)
}
})
})
}
// 测试
conn.query(CREATE_TABLE, err => {
if(err){
throw err
}
query(conn, SELECT_SQL)
.then(results => console.log(results))
.catch(err => console.log(err))
})
Sequelize
- 概述:基于 Promise 的 ORM,支持多种数据库、事务、关联等,类似于 Java 中的Hibernate
- 安装:npm i sequelize mysql2 -S
基本使用:
const Sequelize = require('sequelize')
// 建立连接
const sequelize = new Sequelize('yw_test_db', 'root', 'yw@910714', {
host: 'localhost',
dialect: 'mysql', // 方言,mysql\postgre\sql server\sql lite ...
operatorsAliases: false
})
// 定义模型 Model - Table
const Fruit = sequelize.define('Fruit', {
name: {type: Sequelize.STRING(20), allowNull: false},
price: {type: Sequelize.FLOAT, allowNull: false},
stock: {type: Sequelize.INTEGER, defaultValue: 0}
})
// 同步数据库,force:true则会删除已存在表
Fruit.sync().then(() => {
// 添加测试数据
return Fruit.create({name: '香蕉', price: 3.5})
}).then(() => {
// 查询
Fruit.findAll().then(fruits => {
console.log(JSON.stringify(fruits));
})
})
- 执行后输入:
(node:23896) [SEQUELIZE0004] DeprecationWarning: A boolean value was passed to options.operatorsAliases. This is a no-op with v5 and should be removed.
Executing (default): CREATE TABLE IF NOT EXISTS `Fruits` (`id` INTEGER NOT NULL auto_increment , `name` VARCHAR(20) NOT NULL, `price` FLOAT NOT NULL, `stock` INTEGER DEFAULT 0, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB;
Executing (default): SHOW INDEX FROM `Fruits`
Executing (default): INSERT INTO `Fruits` (`id`,`name`,`price`,`stock`,`createdAt`,`updatedAt`) VALUES (DEFAULT,?,?,?,?,?);
Executing (default): SELECT `id`, `name`, `price`, `stock`, `createdAt`, `updatedAt` FROM `Fruits` AS `Fruit`;
[{"id":1,"name":"香蕉","price":3.5,"stock":0,"createdAt":"2019-04-05T14:43:20.000Z","updatedAt":"2019-04-05T14:43:20.000Z"}]
- 强制同步:创建表之前先删除已存在的表
Fruit.sync({force: true})
- 避免自动生成时间戳字段
const Fruit = sequelize.define('Fruit', {...}, {
timestamps: false
})
- 指定表名:
freezeTableName: true
或tableName: 'xxx'
设置前者则以 modelName 作为表名;设置后者则按其值作为表名
Getters & Setters
Getters & Setters 可用于定义伪属性或映射待数据库字段的保护属性
// 定义属性的一部分
name: {
type: Sequelize.STRING(20),
allowNull: false,
get(){
const fname = this.getDataValue('name')
const price = this.getDataValue('price')
const stock = this.getDataValue('stock')
return `${fname}(价格:¥${price} 库存:${stock}kg)`
}
}
{
// 定义模型选项
...
getterMethods: {
amount(){
return this.getDataValue('stock') + 'kg'
}
},
setterMethods: {
amount(val){
const idx = val.indexOf('kg')
const v = val.slice(0, idx)
this.setDataValue('stock', v)
}
}
}
// 通过模型实例触发setterMethods
Fruit.findAll().then(fruits => {
console.log(JSON.stringify(fruits));
// 修改 amount,触发 setterMethods
fruits[0].amount = '150kg'
fruits[0].save()
})
校验
- 资料:http://docs.sequelizejs.com/manual/models-definition.html#validations
- 可以通过校验功能验证模型字段格式、内容,校验会在
create
、update
和save
时自动运行
price: {
type: Sequelize.FLOAT,
allowNull: false,
validate: {
isFloat: {msg: '价格字段请输入数字'},
min: {args: [0], msg: '价格字段必须大于0'}
}
},
stock: {
type: Sequelize.INTEGER,
defaultValue: 0,
validate: {
isNumeric: {msg: '库存字段请输入数字'}
}
}
模型扩展
- 可以添加模型实例方法或类方法扩展实例
// 添加类级别方法
Fruit.classify = function(name) {
const tropicFruits = ['香蕉', '芒果', '椰子'] // 热带水果
return tropicFruits.includes(name) ? '热带水果' : '其他水果'
}
// 添加实例级别方法
Fruit.prototype.totalPrice = function(count){
return (this.price * count).toFixed(2)
}
- 使用
// 使用类方法
const fruitArr = ['香蕉', '草莓']
fruitArr.forEach(f => console.log(f+'是'+Fruit.classify(f)))
// 使用实例方法
Fruit.findAll().then(fruits => {
console.log(JSON.stringify(fruits));
// 修改 amount,触发 setterMethods
fruits[0].amount = '150kg'
fruits[0].save()
// 使用实例方法
console.log(`买5kg${fruits[0].name}需要¥${fruits[0].totalPrice(5)}`);
})
- 结果输出:
香蕉是热带水果
草莓是其他水果
(node:24608) [SEQUELIZE0004] DeprecationWarning: A boolean value was passed to options.operatorsAliases. This is a no-op with v5 and should be removed.
Executing (default): DROP TABLE IF EXISTS `Fruits`;
Executing (default): CREATE TABLE IF NOT EXISTS `Fruits` (`id` INTEGER NOT NULL auto_increment , `name` VARCHAR(20) NOT NULL, `price` FLOAT NOT NULL, `stock` INTEGER DEFAULT 0, PRIMARY KEY (`id`)) ENGINE=InnoDB;
Executing (default): SHOW INDEX FROM `Fruits`
Executing (default): INSERT INTO `Fruits` (`id`,`name`,`price`,`stock`) VALUES (DEFAULT,?,?,?);
Executing (default): SELECT `id`, `name`, `price`, `stock` FROM `Fruits` AS `Fruit`;
[{"name":"香蕉(价格:¥3.5 库存:0kg)","id":1,"price":3.5,"stock":0}]
买5kg香蕉(价格:¥3.5 库存:150kg)需要¥17.50
Executing (default): UPDATE `Fruits` SET `stock`=? WHERE `id` = ?
数据查询
- 通过id查询(API不可用??)
Fruit.findById(1).then(fruit => {
// fruit是一个Fruit实例,若没有则为null
console.log('findById', fruit.get());
})
- 条件查询 where
Fruit.findOne({where: {name: '香蕉'}}).then(fruit => {
// fruit是首个配项,若没有则为null
console.log('条件查询findOne', fruit.get());
})
- 获取数据和总条数
Fruit.findAndCountAll().then(result => {
console.log(result.count);
console.log(result.rows.length)
})
- 查询操作符 Sequelize.Op
const op = Sequelize.Op
Fruit.findAll({
where: {price: {[op.lt]:6, [op.gte]:2}}
}).then(fruits => {
console.log('查询操作符\n', JSON.stringify(fruits, null, 2))
})
// Executing (default): SELECT `id`, `name`, `price`, `stock` FROM `Fruits` AS `Fruit` WHERE (`Fruit`.`price` < '6' AND `Fruit`.`price` >= '2');
- 或语句
Fruit.findAll({
// where: {[op.or]:[{price: {[op.lt]:4}}, {stock: {[op.gte]: 100}}]}
where: {price: {[op.or]: [{[op.gt]: 8}, {[op.lt]: 5}]}}
}).then(fruits => {
console.log('或语句\n', JSON.stringify(fruits, null, 2))
})
// Executing (default): SELECT `id`, `name`, `price`, `stock` FROM `Fruits` AS `Fruit` WHERE (`Fruit`.`price` > '8' OR `Fruit`.`price` < '5');
- 分页
Fruit.findAll({offset: 0, limit: 2}).then(fruits => {
console.log('分页\n', JSON.stringify(fruits, null, 2))
})
// Executing (default): SELECT `id`, `name`, `price`, `stock` FROM `Fruits` AS `Fruit` LIMIT 0, 2;
- 排序
Fruit.findAll({order: [['price', 'DESC']]}).then(fruits => {
console.log('排序\n', JSON.stringify(fruits, null, 2))
})
// Executing (default): SELECT `id`, `name`, `price`, `stock` FROM `Fruits` AS `Fruit` ORDER BY `Fruit`.`price` DESC;
- 聚合函数
setTimeout(() => {
Fruit.max('price').then(max => {
console.log('聚合函数 max', max)
})
Fruit.sum('price').then(sum => {
console.log('聚合函数 sum', sum)
})
}, 500)
// Executing (default): SELECT max(`price`) AS `max` FROM `Fruits` AS `Fruit`;
// Executing (default): SELECT sum(`price`) AS `sum` FROM `Fruits` AS `Fruit`;
更新
- 方式1
Fruit.findById(1).then(fruit => {
fruit.price = 4
fruit.save().then(() => {
console.log('update !!!')
})
})
- 方式2
Fruit.update({price: 6}, {where: {name: '香蕉'}}).then(result => {
console.log('更新 update', result)
})
// UPDATE `Fruits` SET `price`=? WHERE `name` = ?
删除
- 方式1
Fruit.findOne({where: {id: 1}}).then(result => {
result.destroy()
})
- 方式2
Fruit.destroy({where: {id: 2}}).then(result => {
console.log('删除 destroy', result)
})
// DELETE FROM `Fruits` WHERE `id` = 2
关联
- 一对多、多对一
const Player = sequelize.define('player', {
name: Sequelize.STRING
})
const Team = sequelize.define('team', {
name: Sequelize.STRING
})
// 建立关系
Player.belongsTo(Team) // 这里会添加 teamId 到 player 表作为外键
Team.hasMany(Player)
// 同步
sequelize.sync({force: true}).then(async () => {
await Team.create({name: '火箭'})
await Player.bulkCreate([{name: '哈登', teamId: 1}, {name: '保罗', teamId: 1}])
// 一对多查询
const team = await Team.findOne({where: {name: '火箭'}, includes: [Player]})
console.log('关联查询 一对多', JSON.stringify(team, null, 2))
// 多对一查询
const players = await Player.findAll({includes: [Team]})
console.log('关联查询 多对一', JSON.stringify(players, null, 2))
})
- 多对多
const Fruit = sequelize.define('fruit', {name: Sequelize.STRING})
const Category = sequelize.define('category', {name: Sequelize.STRING})
Fruit.FruitCategory = Fruit.belongsToMany(Category, {
through: 'FruitCategory'
})
sequelize.sync({force: true}).then(async () => {
// 插入测试数据
await Fruit.create(
{name: '香蕉', categories: [{id: 1, name: '热带'}, {id: 2, name: '温带'}]},
{include: [Fruit.FruitCategory]}
)
// 多对多联合查询
const fruit = await Fruit.findOne({
where: {name: '香蕉'},
// 通过 through 指定条件、字段等
include: [{model: Category, through: {attributes: ['id', 'name']}}]
})
console.log('多对多关联查询\n', JSON.stringify(fruit, null, 2))
})
事务
待补充
MongoDB
资源
安装配置
- 安装教程:http://www.runoob.com/mongodb/mongodb-window-install.html
- 创建 dbpath 文件夹
- 启动:mongod --dbpath=/data
- 测试
// 查询所有数据库
show dbs
// 切换、创建数据库,当创建一个集合的时候会自动创建当前数据库
use test
// 得到当前db的所有聚集集合
db.getCollectionNames()
// 查询
db.fruits.find()
// 插入一条数据
db.fruits.save({name: '苹果', price: 5})
// 条件查询
db.fruits.find({price: 5})
db.fruits.find({price: {$lte: 10}})
- MongoDB命令行操作:https://www.cnblogs.com/wywnet/p/5102946.html
MongoDB原生驱动
- 安装 mongodb 模块:npm install mongodb --save
基本使用
- 连接MongoDB
const MongoClient = require('mongodb').MongoClient
// 连接URL
const url = 'mongodb://127.0.0.1:27017'
// 数据库名
const dbName = 'kaikeba';
(async function(){
// 0. 创建客户端
const client = new MongoClient(url, {useNewUrlParser: true})
try {
// 1. 连接数据库(异步)
await client.connect()
console.log('Connected Successfully!')
// 2. 获取数据库
const db = client.db(dbName)
// 3. 获取集合
const fruitColl = db.collection('fruits')
// 4. 插入文档
let r;
r = await fruitColl.insertOne({name: '芒果', price: 20.0})
console.log('插入成功', r.result)
// 5. 查询文档
r = await fruitColl.findOne()
console.log('查询结果', r)
// 6. 更新文档
r = await fruitColl.updateOne({name: '芒果'}, {$set: {name: '苹果'}})
console.log('更新成功', r.result)
// 7. 删除文档
r = await fruitColl.deleteOne({name: '苹果'})
console.log('删除成功', r.result)
} catch (error) {
console.log(error)
}
client.close()
})()
实战案例-瓜果超市
- MongoDB操作封装:
const conf = require('./conf')
const MongoClient = require('mongodb').MongoClient
// 事件派发器
const EventEmittter = require('events').EventEmitter
class MongoDB {
constructor(conf){
// 保存conf
this.conf = conf
this.emitter = new EventEmittter()
// 连接
this.client = new MongoClient(conf.url, {useNewUrlParser: true})
this.client.connect(err => {
if(err){
throw err
}
console.log('Connected Successfully!')
this.emitter.emit('connect')
})
}
// 监听事件方法
once(event, cb){
this.emitter.once(event, cb)
}
// 获取集合
col(colName, dbName = this.conf.dbName){
return this.client.db(dbName).collection(colName)
}
}
module.exports = new MongoDB(conf)
- mock数据:
const mongodb = require('./db')
mongodb.once('connect', async () => {
const col = mongodb.col('fruits')
try {
// 删除已存在的数据
await col.deleteMany();
// 插入测试数据
await col.insertMany([
{name: '苹果', price: 9.9, category: '水果'},
{name: '香蕉', price: 5.9, category: '水果'},
{name: '芒果', price: 11.9, category: '水果'},
{name: '砂糖橘', price: 12, category: '水果'},
{name: '土豆', price: 3.5, category: '蔬菜', stack: 100},
{name: '西红柿', price: 2.8, category: '蔬菜', stack: 100},
{name: '茄子', price: 2, category: '蔬菜'},
{name: '韭菜', price: 1.5, category: '蔬菜'}
])
console.log('测试数据插入成功')
} catch (error) {
console.log('测试数据插入失败', error)
}
})
- 后台Server
const mongo = require('../models/db.js')
const testdata = require('../models/test-data.js')
const express = require('express')
const path = require('path')
const app = express()
app.get('/index', (req, res) => {
res.sendFile(path.resolve('./index.html'))
})
// 分页查询水果蔬菜数据
app.get('/api/list', async (req, res) => {
// 分页数据
const {page} = req.query
// 查询
try {
const col = mongo.col('fruits')
const fruits = await col.find().skip((page-1)*4).limit(4).toArray()
// 查询总条数
const total = await col.find().count()
res.json({code: '0000', data: {fruits, pagination: {total, page}}})
} catch (error) {
console.log(error)
}
})
app.listen(3000)
- 前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"/>
<title>瓜果超市</title>
</head>
<body>
<div id="app">
<!-- 列表 -->
<ul>
<li v-for="fruit in fruits" :key="fruit._id">
{{fruit.name}} - {{fruit.price}}
</li>
</ul>
<!-- 分页 -->
<el-pagination layout="prev, pager, next" @current-change="currentChange" :total="total" :page-size="4">
</el-pagination>
</div>
<script>
new Vue({
el: '#app',
data: {
fruits: [],
page: 1,
total: 0
},
created() {
this.getData()
},
methods: {
getData(){
axios.get(`/api/list?page=${this.page}`).then(res => res.data).then(({data}) => {
this.fruits = data.fruits
this.total = data.pagination.total
})
},
currentChange(page){
this.page = page
this.getData()
}
}
})
</script>
</body>
</html>
操作符
- 资料:https://docs.mongodb.com/manual/reference/operator/query/
查询操作符
提供多种方式定位数据库
- 比较:$eq、$gt、$gte、$in 【等于、大于、大于等于、包含】
// price > 10
await fruitColl.find({price: {$gt: 10}})
- 逻辑:$and、$or、$not、$nor 【与、或、非、取反】
// price>10 or pricec<5
await fruitColl.find({$or: [{price: {$gt: 10}}, {price: {$lt: 5}}]})
// price不大于10 且 price不小于5
await fruitColl.find({$nor: [{price: {$gt: 10}}, {price: {$lt: 5}}]})
- 元素:$exists、$type
fruitsCol.insertOne({name: '芒果', price: 20.0, stack: true})
await fruitColl.find({stack: {$exists: true}})
- 模拟:$regex、$text、$expr 【正则、全文匹配】
await fruitColl.find({name: {$regex: /芒/}})
// 验证文本搜索需要首先对字段加索引
await fruitColl.createIndex({name: 'text'})
// 按词搜索,单独子查询不出结果
await fruitColl.find({$text: {$search: '芒果'}})
- 数组:$all、$elemMatch、$size
// 插入带标签数据
fruitColl.insertOne(..., tags: ['热带', '甜'])
// $all:查询指定字段包含所有指定内容的文档
await fruitColl.find({tags: {$all: ['热带', '甜']}})
// $elemMatch:指定字段数组中至少有一个元素满足所有查询规则
fruitColl.insertOne({hisPrice: [20, 25, 30]})
await fruitColl.find({hisPrice: {$elemMatch: {$gt: 24, $lt: 26}}}) // 24~26
- 地理空间:$geoIntersects、$geoWithin、$near、$nearSphere
// 创建 stations 集合
const stations = db.collection('stations')
// 添加测试数据,执行一次即可
await stations.insertMany([])
// 创建索引
await stations.createIndex({ loc: "2dsphere" });
// 查询方圆1公里以内的地铁站
r = await stations
.find({
loc: {
$nearSphere: {
$geometry: {
type: "Point",
coordinates: [116.403847, 39.915526]
},
$maxDistance: 1000
}
}
}).toArray();
更新操作符
可以修改数据库数据或添加附加数据
- 字段相关:$set、$unset、$setOnInsert、$rename、$inc、$min、$max、$mul
// 更新多个字段
await fruitColl.updateOne(
{name: '芒果'},
{$set: {price: 19.8, category: '热带水果'}}
)
// 更新内嵌字段
{$set: {..., area: {city: '三亚'}}}
- 数组相关:$、$[]、$addToSet、$pull、$pop、$push、$pullAll
// $push 用于新增
fruitColl.insertOne(tags: ['热带', '甜'])
fruitColl.updateMany({name: '芒果'}, {$push: {tags: '上火'}})
// $pull、$pullAll用于删除符合条件项,$pop删除首项-1或尾项1
fruitColl.updateMany({name: '芒果'}, {$pop: {tags: 1}})
fruitColl.updateMany({name: '芒果'}, {$pop: {tags: 1}})
// $、$[]用于修改
fruitColl.updateMany({name: '芒果', tags: '甜'}, {$set: {'tags.$': '香甜'}})
- 修改器,常结合数组操作符使用:$each、$opsition、$slice、$sort
$push: {tags: {$each: ['上火', '真香'], $slice: -3}}
聚合操作符
使用 aggregate 方法,使文档顺序通过管道阶段从而得到最终结果
- 聚合管道阶段:$group、$count、$sort、$skip、$limit、$project
- 聚合管道操作符:$add、$avg、$sum
let r = await fruitColl.insertOne({ name: "芒果", price: 20.0 });
r = await fruitColl.insertOne({ name: "百香果", price: 15 });
// 分组,求和
r = await fruitColl
.aggregate([
{ $match: { name: "芒果" } }, // 匹配阶段
{ $group: { _id: "$name", total: { $sum: "$price" } } } // 分组阶段
])
.toArray();
console.log(r);
案例使用
瓜果超市按条件查询
- 查询分类
app.get('/api/category', async (req, res) => {
const col = mongo.col('fruits')
const data = await col.distinct('category')
res.json({code: '0000', data})
})
- 按分类查询
app.get('/api/list', async (req, res) => {
// 分页数据
const {page, category} = req.query
// 查询
try {
const col = mongo.col('fruits')
// 构造条件
const condition = {}
if(category){
condition.category = category
}
const fruits = await col.find(condition).skip((page-1)*4).limit(4).toArray()
// 查询总条数
const total = await col.find(condition).count()
res.json({code: '0000', data: {fruits, pagination: {total, page}}})
} catch (error) {
console.log(error)
}
})
- 搜索商品
app.get('/api/search', async (req, res) => {
const {keyword} = req.query
const col = mongo.col('fruits')
const data = await col.find({
name: {$regex: new RegExp(keyword)}
}).toArray()
res.json({code: '0000', data})
})
ODM-Mongoose
- 资料:https://mongoosejs.com/docs/guide.html
- 概述:优雅的NodeJS对象文档模型 Object Document Model,Mongoose有两个特点:
* 通过关系型数据库的思想来设计非关系型数据库
* 基于mongodb驱动,简化解析
- 安装:npm install mongoose -S
基本使用
const mongoose = require('mongoose')
// 1. 连接
mongoose.connect('mongodb://127.0.0.1:27017/test', {useNewUrlParser: true})
const conn = mongoose.connection
conn.on('error', () => {
console.log('连接数据库失败')
})
conn.once('open', async () => {
// 2. 定义一个 Schema - Table
const Schema = mongoose.Schema({
category: String,
name: String
})
// 3. 编译一个Model,它对应数据库中复数、小写的Collection
const Model = mongoose.model('fruit', Schema)
try {
// 4. 创建,create返回Promise
let r = await Model.create({
category: '热带水果',
name: '苹果',
price: 5
})
console.log('插入数据', r)
// 5. 查询,find返回Query,它是实现了then和catch,可以当Promise使用
// 如果需要返回Promise,调用其exec()
r = await Model.find({name: '苹果'})
console.log('查询结果', r)
// 6. 更新
r = await Model.updateOne({name: '苹果'}, {$set: {name: '芒果'}})
console.log('更新结果', r)
// 7. 删除
r = await Model.deleteOne({name: '苹果'})
console.log('删除结果', r)
} catch (error) {
console.log(error)
}
})
Schema
- 字段定义
const blogSchema = mongoose.Schema({
// 定义校验规则
title: {type: String, require: [true, '标题为必填项']},
author: String,
body: String,
// 定义对象数组
comments: [{body: String, data: Date}],
// 指定默认值
date: {type: Date, default: Data.now},
hidden: Boolean,
// 定义对象
meta: {
votes: Number,
favs: Number
}
})
// 定义多个索引,正1升序,负1降序
blogSchema.index({title: 1, author: 1, date: -1})
const BlogModel = mongoose.model('blog', blogSchema)
const blog = new BlogModel({
title: 'nodejs持久化',
author: 'jerry',
body: '...'
})
const r = await blog.save()
console.log('新增blog', r)
- 定义实例方法:抽象出常用方法便于复用
// 定义实例方法
blogSchema.methods.findAuthor = function(){
return this.model('blog').find({author: this.author}).exec()
}
// 获得模型实例
const BlogModel = mongoose.model('blog', blogSchema)
const blog = new BlogModel({...})
// 调用实例方法
r = await blog.findAuthor()
console.log('findAuthor', r)
- 静态方法
// 静态方法
blogSchema.statics.findAuthor = function(author){
return this.model('blog').find({author}).exec()
}
r = await BlogModel.findAuthor('jerry')
console.log('findAuthor', r)
- 虚拟属性
blogSchema.virtual('commentsCount').get(function(){
return this.comments.length
})
let r = await blog.findOne({author: 'jerry'})
console.log('blog留言数:', r.commentsCount)