Node.js ORM - Sequelize
概述:基于Promise的ORM(Object Relation Mapping),⽀持多种数据库、事务、关联等
(async () => {
const Sequelize = require("sequelize");
// 建⽴连接
const sequelize = new Sequelize("kaikeba", "root", "example", {
host: "localhost",
dialect: "mysql",
operatorsAliases: false
});
// 定义模型
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则会删除已存在表
let ret = await Fruit.sync()
console.log('sync',ret)
ret = await Fruit.create({
name: "⾹蕉",
price: 3.5
})
console.log('create',ret)
ret = await Fruit.findAll()
await Fruit.update(
{ price: 4 },
{ where: { name:'⾹蕉'} }
)
console.log('findAll',JSON.stringify(ret))
const Op = Sequelize.Op;
ret = await Fruit.findAll({
// where: { price: { [Op.lt]:4 }, stock: { [Op.gte]: 100 } }
where: { price: { [Op.lt]: 4, [Op.gt]: 2 } }
})
console.log('findAll', JSON.stringify(ret, '', '\t'))
})()
强制同步:创建表之前先删除已存在的表
Fruit.sync({force: true})
避免⾃动⽣成时间戳字段
const Fruit = sequelize.define("Fruit", {}, {
timestamps: false
})
指定表名: freezeTableName: true 或 tableName:'xxx'
设置前者则以modelName作为表名;设置后者则按其值作为表名。
蛇形命名 underscored: true,
默认驼峰命名
UUID-主键
id: {
type: Sequelize.DataTypes.UUID,
defaultValue: Sequelize.DataTypes.UUIDV1,
primaryKey: true
},
Getters & Setters:可⽤于定义伪属性或映射到数据库字段的保护属性
// 定义为属性的⼀部分
name: {
type: Sequelize.STRING,
allowNull: false,
get() {
const fname = this.getDataValue("name");
const price = this.getDataValue("price");
const stock = this.getDataValue("stock");
return `${fname}(价格:¥${price} 库存:${stock}kg)`;
}
}
// 定义为模型选项
// options中
{
getterMethods:{
amount(){
return this.getDataValue("stock") + "kg";
}
},
setterMethods:{
amount(val){
const idx = val.indexOf('kg');开课吧web全栈架构师
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();
});
校验:可以通过校验功能验证模型字段格式、内容,校验会在 create 、 update 和 save 时⾃动
运⾏
price: {
validate: {
isFloat: { msg: "价格字段请输⼊数字" },
min: { args: [0], msg: "价格字段必须⼤于0" }
}
},
stock: {
validate: {
isNumeric: { msg: "库存字段请输⼊数字" }
}
}
模型扩展:可添加模型实例⽅法或类⽅法扩展模型
// 添加类级别⽅法
Fruit.classify = function(name) {
const tropicFruits = ['⾹蕉', '芒果', '椰⼦']; // 热带⽔果
return tropicFruits.includes(name) ? '热带⽔果':'其他⽔果';
};
// 添加实例级别⽅法
Fruit.prototype.totalPrice = function(count) {
return (this.price * count).toFixed(2);
};
// 使⽤类⽅法
['⾹蕉','草莓'].forEach(f => console.log(f+'是'+Fruit.classify(f)));
// 使⽤实例⽅法
Fruit.findAll().then(fruits => {
const [f1] = fruits;开课吧web全栈架构师
console.log(`买5kg${f1.name}需要¥${f1.totalPrice(5)}`);
});
数据查询
// 通过id查询(不⽀持了)
Fruit.findById(1).then(fruit => {
// fruit是⼀个Fruit实例,若没有则为null
console.log(fruit.get());
});
// 通过属性查询
Fruit.findOne({ where: { name: "⾹蕉" } }).then(fruit => {
// fruit是⾸个匹配项,若没有则为null
console.log(fruit.get());
});
// 指定查询字段
Fruit.findOne({ attributes: ['name'] }).then(fruit => {
// fruit是⾸个匹配项,若没有则为null
console.log(fruit.get());
});
// 获取数据和总条数
Fruit.findAndCountAll().then(result => {
console.log(result.count);
console.log(result.rows.length);
});
// 查询操作符
const Op = Sequelize.Op;
Fruit.findAll({
// where: { price: { [Op.lt]:4 }, stock: { [Op.gte]: 100 } }
where: { price: { [Op.lt]:4,[Op.gt]:2 }}
}).then(fruits => {
console.log(fruits.length);
});
// 或语句
Fruit.findAll({
// where: { [Op.or]:[{price: { [Op.lt]:4 }}, {stock: { [Op.gte]: 100
}}] }
where: { price: { [Op.or]:[{[Op.gt]:3 }, {[Op.lt]:2 }]}}
}).then(fruits => {
console.log(fruits[0].get());
});
Fruit.findAll({
offset: 0,
limit: 2,
})
// 排序
Fruit.findAll({
order: [['price', 'DESC']],
})
// 聚合
Fruit.max("price").then(max => {
console.log("max", max);
});
Fruit.sum("price").then(sum => {
console.log("sum", sum);
});
更新
Fruit.findById(1).then(fruit => {
// ⽅式1
fruit.price = 4;
fruit.save().then(()=>console.log('update!!!!'));
});
// ⽅式2
Fruit.update({price:4}, {where:{id:1}}).then(r => {
console.log(r);
console.log('update!!!!')
})
删除
// ⽅式1
Fruit.findOne({ where: { id: 1 } }).then(r => r.destroy());
// ⽅式2
Fruit.destroy({ where: { id: 1 } }).then(r => console.log(r));
实体关系图和与域模型 ERD
const log = (text, data) => {
console.log(`===========${text}========`)
console.log(JSON.stringify(data,null,"\t"))
console.log(`==========================`)
}
const sequelize = require('./util/database')
// 定义模型 1:N 关系
const Product = require('./models/product');
const User = require('./models/user');
const Cart = require('./models/cart');
const CartItem = require('./models/cart-item');
Product.belongsTo(User, {
constraints: true,
onDelete: 'CASCADE'
});
User.hasMany(Product)
await sequelize.sync({ force: true })
// 创建⽤户
let user = await User.findByPk(1)
if (!user) {
user = await User.create({
name: 'kaikeba',
email: 'mail@kaikeba.com'
})
}
// 添加商品
let product = await user.createProduct({
title: '商品⼀',
price: 123,
imageUrl: 'abc.png',
description: '商品描述'
})
log('product', product)
// N : N关系
User.hasOne(Cart)
Cart.belongsTo(User)
Cart.belongsToMany(Product, {
through: CartItem
});
Product.belongsToMany(Cart, {
through: CartItem
});
await sequelize.sync({ force: true })
// 创建⽤户
let user = await User.findByPk(1)
if (!user) {
user = await User.create({
name: 'kaikeba',
email: 'mail@kaikeba.com'
})
}
// 添加商品
let product = await user.createProduct({
title: '商品⼀',
price: 123,
imageUrl: 'abc.png',
description: '商品描述'
})
log('product', product)开课吧web全栈架构师
// 添加购物⻋
await user.createCart()
ret = await User.findAll({ include: [Cart] })
log('getCart:', ret)
// 添加购物⻋商品
let cart = await user.getCart()
await cart.addProduct(product, {
through: {
quantity: 1
}
})
// 获取购物⻋商品数量
const productId = 1
const item = await cart.getProducts({
where: {
id: productId
}
})
log('item', item)
// 商品是否存在
if(item.length > 0){
console.log('商品存在....................')
await cart.addProduct(product, {
through: {
quantity: item[0].cartItem.quantity + 1
}
})
}else{
await cart.addProduct(product, {
through: {
quantity: 1
}
})
}
log('cart', cart)