数据库编程-ORM
ORM(对象关系映射) 是一种编程技术,用于将数据库中的数据转换为面向对象的编程语言中的对象。
它简化了数据库操作,使开发者可以使用面向对象的方式来操作数据库,而不需要直接编写SQL语句。
Sequelize是一个基于Promise的Node.js ORM框架,支持多个SQL数据库(如MySQL、PostgreSQL、SQLite、MSSQL)。
Sequelize提供了一个易于使用的API来进行数据库建模、查询和事务处理。
Sequelize基础
Sequelize可以针对现有数据库或无数据库的情况进行创建。
const { Sequelize } = require("sequelize"); //command JS模块化//使用mysql ,一个数据库就对应一个Sequelize
const db = new Sequelize("mytestdb", "root", "******", {host: "localhost",dialect: "mysql", //数据库类型// logging: false,//禁止打印sql语句(Sequelize语句)
});// 测试数据库连接
//authenticate()方法测试数据库连接;authenticate()返回promise
db.authenticate().then(() => {console.log("数据库连接成功");}).catch((err) => {console.log("数据库连接失败", err);});
// Executing (default): SELECT 1+1 AS result// 输出Sequelize语句
// 数据库连接成功module.exports = db; //command JS导出
模型基础
模型是 Sequelize 的本质。模型是代表数据库中表的抽象。在Sequelize中,它是一个 Model 的扩展类。
模型应具有名称,但不必与它在数据库中表示的表的名称相同。
用法
调用sequelize.define(modelName, attributes, options)
import db from "./01-connect-db-ESM.js";
import { DataTypes } from "sequelize"; // 引入数据类型
// 定义模型 名称映射
// 名称写单数,数据库为负数。确保符合英语的复数规则。
const BookInfo = db.define("bookinfo",{id: {type: DataTypes.INTEGER,primaryKey: true,autoIncrement: true,},isbn: {type: DataTypes.CHAR(13),allowNull: false, //不能为空},name: {type: DataTypes.STRING(200),allowNull: false,},author: {type: DataTypes.STRING(100),allowNull: false,},press: {type: DataTypes.STRING(100),allowNull: false,},price: {type: DataTypes.DECIMAL(7, 2), // 7位长度,2位小数allowNull: false,},pubdate: {type: DataTypes.DATEONLY,allowNull: false,},},{// Sequelize默认会为每个模型添加createdAt和updatedAt字段,如果不需要则可以配置timestamps为false。timestamps: false, //时间戳//默认模型名称为复数。// tableName: "bookinfo", // 指定表名,不会按照模型名称自动生成复数。}
);export { BookInfo };
扩展Model并调用init(attributes, options)
class BookInfo extends Sequelize.Model {}BookInfo.init({id: {type: DataTypes.INTEGER,primaryKey: true,autoIncrement: true,},isbn: {type: DataTypes.CHAR(13),allowNull: false,},// ...pubdate: {type: DataTypes.DATEONLY,},},{sequelize,modelName: "bookinfos",timestamps: false,}
);
使用模型
建立模型后,即可通过模型对象所提供的API对数据进行增删查改(CRUD)操作
唯一查询
const book = await BookInfo.findOne({where: {isbn: "9787115383440",},
});
console.log(book.dataValues);
console.log(JSON.stringify(book, null, 2));
插入记录
const book = await BookInfo.create({isbn: "9787115383440",name: "JavaScript高级程序设计",author: "Nicholas C. Zakas",press: "机械工业出版社",price: 100,pubdate: "2018-01-01",
});
console.log(book.toJSON());
修改记录
const [affectedRows] = await BookInfo.update({price: 99.99,},{where: {isbn: "9787505051080",},}
);
console.log(affectedRows);
删除记录
// 删除一条记录
const count = await BookInfo.destroy({where: {isbn: "9787115383440",},
});
console.log(count);
模型优先
先构建模型,然后通过Sequelize创建数据库表。但,需要先在MySQL中创建对应的数据库。
创建Sequelize对象与“数据库优先”相同。
构建模型时,可以使用sequelize.define()方法,也可以使用扩展Model类的方式。
同步模型,使用sequelize.sync()方法,将模型同步到数据库。
Sequelize进阶
Sequelize 支持标准关联关系: 一对一、 一对多和多对多。
Sequelize 提供了四种关联类型
- HasOne
- BelongsTo
- HasMany
-
BelongsToMany
HasOne
A.hasOne(B) 关联意味着 A 和 B 之间存在一对一的关系,外键在目标模型(B)中定义。
BelongsTo
A.belongsTo(B)关联意味着 A 和 B 之间存在一对一的关系,外键在源模型中定义(A)。
HasMany
A.hasMany(B) 关联意味着 A 和 B 之间存在一对多关系,外键在目标模型(B)中定义。
BelongsToMany
A.belongsToMany(B, { through: ‘C’ }) 关联意味着将表 C 用作联结表,在 A 和 B 之间存在多对多关系,具有外键(例如:aId 和 bId)。
关系映射
Sequelize 关联通常成对定义
- 创建 一对一 关系,hasOne 和 belongsTo 关联一起使用。
- 创建 一对多 关系,hasMany he belongsTo 关联一起使用。
- 创建 多对多 关系,两个 belongsToMany 调用一起使用。
一对一关系
User与UserDetails为一对一的关系
import db from "../db/connect-db.js";
import { DataTypes, Model } from "sequelize";class User extends Model {}
User.init({id: {type: DataTypes.INTEGER,primaryKey: true,autoIncrement: true,allowNull: false,},username: {type: DataTypes.STRING(60),allowNull: false,validate: {len: [1, 60],is: /^[\w]+$/,//是否唯一:用一个回调函数,返回true或falseisUnique: async (value) => {const user = await User.findOne({where: { username: value },});if (user) {throw new Error("用户名已存在");}},},},password: {type: DataTypes.STRING(60),allowNull: false,validate: {len: [1, 60],},},// birthday: {// type: DataTypes.DATEONLY,// },},{sequelize: db,modelName: "user", //小写的单数单词timestamps: false, //不生成时间戳}
);class UserDetail extends Model {}
UserDetail.init({id: {type: DataTypes.INTEGER,primaryKey: true,autoIncrement: true,},birthday: {type: DataTypes.DATEONLY,},address: {type: DataTypes.STRING(200),},phone: {type: DataTypes.CHAR(11),},},{sequelize: db,modelName: "userDetail",timestamps: false,tableName: "user_details",}
);//配置关系
User.hasOne(UserDetail, {foreignKey: "userId",as: "details",onDelete: "CASCADE", //级联删除
});
//可有可无
UserDetail.belongsTo(User, {foreignKey: "userId",
});export { User, UserDetail };
一对多关系
Category与BookInfo为一对多的关系。
import { Model, DataTypes } from "sequelize";
import { BookInfo } from "./bookinfo.js";
import db from "../db/connect-db.js";class Category extends Model {}
Category.init({id: {type: DataTypes.INTEGER,primaryKey: true,autoIncrement: true,},title: {type: DataTypes.STRING(100),allowNull: false,},},{sequelize: db,modelName: "category",timestamps: false,}
);Category.hasMany(BookInfo, {foreignKey: "cateId",as: "books",
});
BookInfo.belongsTo(Category, {foreignKey: "cateId",
});export { Category };
多对多关系
用户与书籍之间存在借阅关系,该关系为多对多。
// 定义借阅模型,用于跟踪图书的借阅情况。
const Borrowing = sequelize.define("borrowing",{id: {type: DataTypes.INTEGER,primaryKey: true,autoIncrement: true,},borrowDate: {type: DataTypes.DATE,defaultValue: DataTypes.NOW,},returnDate: {type: DataTypes.DATE,},},{timestamps: false,}
);
// 配置用户与书籍之间的借阅模型,该关系为多对多。
User.belongsToMany(BookInfo, {through: { model: Borrowing, unique: false },foreignKey: "userId",as: "borrowedBooks",otherKey: "bookId",
});BookInfo.belongsToMany(User, {through: { model: Borrowing, unique: false },foreignKey: "bookId",as: "borrowers",otherKey: "userId",
});
原始查询
使用 sequelize.query 方法,可以直接执行SQL语句。
const sql = `SELECT book.name, book.price, cate.title
FROM bookinfos as book, categories as cate
WHERE book.cateId = cate.id AND cate.id = ?`;const books = await sequelize.query(sql, {replacements: [1], // 参数替换type: sequelize.QueryTypes.SELECT, // 查询类型
});books.forEach((book) => {console.log(book.name, book.price, book.title);
});
关联参数
onDelete
,用于配置删除时的数据库行为。
CASCADE
:当父表中的记录被删除时,子表中与之关联的记录也会被删除。(多对多默认值)SET NULL
:当父表中的记录被删除时,子表中与之关联的外键列会被设置为NULL
。(一对多、一对一的默认值)RESTRICT
:阻止删除父表中有关联的记录,除非先删除子表中的关联记录。NO ACTION
:与RESTRICT
类似,阻止删除父表中有关联的记录。
onUpdate
,用于配置修改时的数据库行为。
CASCADE
:当父表中的主键值被更新时,子表中与之关联的外键值也会被更新。(一对多、一对一、多对多的默认值)SET NULL
:当父表中的主键值被更新时,子表中与之关联的外键值会被设置为NULL
。RESTRICT
:阻止更新父表中的主键值,除非先更新子表中的关联外键值。NO ACTION
:与RESTRICT
类似,阻止更新父表中的主键值。