NodeJs学习日志(3):express,sequelize进行增删改查(CRUD)
NodeJs学习日志(3):express,sequelize进行增删改查(CRUD)
问题与解答
Q: 使用事务和悲观锁可以防止数据竞争和不一致状态?
A:
- 事务:保证多个操作的原子性,要么全部成功,要么全部回滚。
- 悲观锁:在操作期间锁定记录,防止其他并发修改,适用于高并发写场景。
Q: 发送很多HTTP请求,同时操作一个表,会不会所有请求都必须等待前面的操作结束?
A: 不会。
- 如果操作的是不同行,通常可以并发执行,互不等待。
- 如果操作的是同一行,可能会因锁机制产生等待或阻塞。
- 只有在极端情况(如全表扫描、表锁)下,才会出现全局等待。
Q: HTTP请求参数有哪些?如何获取?
A:
- 查询参数:
req.query
,如GET /search?q=node&page=1
- 路由参数:
req.params
,如GET /users/123
中的123
- 请求体:
req.body
,如put>:put:http://localhost:3000/articles/func1/1
body:{“title”: “New Title”, “content”: “Updated Content”}
环境准备
-
第8回:使用 express-generator 创建正式项目,nodemon 监听代码变动
https://clwy.cn/chapters/node-express-generator#content -
第9回:MySQL 与 Sequelize ORM 的使用
https://clwy.cn/chapters/node-express-sequelize#content -
第10回:模型(Model)、迁移(Migration)与种子(Seeders)
https://clwy.cn/chapters/node-express-model#content -
第11回:查询文章列表接口,promise 还是 async/await
https://clwy.cn/chapters/node-express-promise#content
日志内容
模型导入方式
导入整个模型文件,或者解构赋值导入特定模型。
const models = require("../models");//导入整个模型文件
const { Article } = require("../models");//解构赋值导入特定模型
//导入整个模型文件的查询方式
//https://localhost:3000/func1
router.get("/func1", async (req, res) => {try {const articles = await models.Article.findAll();res.json({ articles: articles });}catch {res.status(500).json({ error: 'Internal Server Error' });}
});//模型导入方式不同,
//https://localhost:3000/func4
router.get("/func4", async function (req, res) {try {const articles = await Article.findAll();res.json({ articles: articles });}catch {res.status(500).json({ error: 'Internal Server Error' });}
});
async/await 与 .then().catch()
//使用,try-catch,async/await
//https://localhost:3000/func1
router.get("/func1", async (req, res) => {try {const articles = await models.Article.findAll();res.json({ articles: articles });}catch {res.status(500).json({ error: 'Internal Server Error' });}
});//使用then,catch
//https://localhost:3000/func2
router.get("/func2", function (req, res) {models.Article.findAll().then(articles => res.json({ articles })).catch(() => res.status(500).json({ error: 'Internal Server Error' }));
});
参数获取
get请求在url中获取参数
//在请求url中添加参数
//https://cn.bing.com/search?q=vscode&page=1
router.get("/search", (req, res) => {try {// 示例:/search?q=vscode&page=1const { q, page } = req.query; // q = "vscode", page = "1"res.json({ query: q, page });}catch {res.status(500).json({ error: 'Internal Server Error' });}
});//路由参数
//https://store.epicgames.com/zh-CN
router.get("/:lang", (req, res) => {try {const { lang } = req.params; //lang= "zh-CN"res.json({ "语言是": lang });}catch {res.status(500).json({ error: 'Internal Server Error' });}
});
请求体中获取
- 使用传统的对象属性赋值方式
- 使用ES6的解构赋值
//从请求体(body)中获取参数
//post: http://localhost:3000/get-args-1
//body: { title: "Hello", content: "World" }
router.post('/get-args-1', async function (req, res) {const data = {title: req.body.title,content: req.body.content};var article = await models.Article.create(data);res.json({ article: article });
});//从请求体(body)中获取参数
//post: http://localhost:3000/get-args-2
//body: { title: "Java", content: "java is a script language" }
router.post("/get-args-2", async (req, res) => {const { title, content } = req.body; // 从请求体获取const article = await models.Article.create({ title, content });res.json(article);
});
条件查询
//使用orderby进行添加条件的查询
//http://localhost:3000/func3
router.get("/func3", async (req, res) => {try {const data = await models.Article.findAll({order: [['id', 'DESC']]});res.json({ "data": data });}catch {res.status(500).json({ error: `Internal Server Error>${error}` });}
});
使用sql语句进行查询
语句 | 意义 |
---|---|
replacements: { id: articleId }, | 将 :id 这个命名参数替换为变量 articleId 的值 |
type: sequelize.QueryTypes.SELECT | 告诉 Sequelize:这是一个查询数据的操作(SELECT) |
// 使用原始SQL查询
app.get('/ray_sql/:articleId', async (req, res) => {const articleId = req.params; // 获取查询字符串中的 idtry {const [results] = await sequelize.query("SELECT * FROM articles WHERE id = :id", {replacements: { id: articleId },type: sequelize.QueryTypes.SELECT});if (!results || results.length === 0) {return res.status(404).send('Article not found');}res.json(results);} catch (error) {console.error('Error executing query:', error);res.status(500).send('Server error');}
});
多参数sql插入
语句 | 意义 |
---|---|
“SELECT * FROM articles WHERE id = :id AND title = :title”, | :id 与:title 都为占位符号 |
replacements: { id: articleId, title: articleTitle }, | 将占位符替换为实际的参数 |
const [results] = await sequelize.query("SELECT * FROM articles WHERE id = :id AND title = :title", {replacements: { id: articleId, title: articleTitle },type: sequelize.QueryTypes.SELECT}
);
使用数据库事务,悲观锁
先查询再修改
//修改用户数据-返回受到影响行数
//PUT http://localhost:3000/articles/func1/1
//body:{"title": "New Title", "content": "Updated Content"}
router.put("/articles/func1/:id", async (req, res) => {// 创建事务const transaction = await models.sequelize.transaction();try {const { id } = req.params;const { title, content } = req.body;// 1. 使用悲观锁查询记录const article = await models.Article.findOne({where: { id },lock: true, // 启用行级锁transaction // 关联事务});if (!article) {await transaction.rollback();return res.status(404).json({ error: "文章不存在" });}// 2. 执行更新操作const [affectedRows] = await models.Article.update({ title, content },{ where: { id },transaction // 关联事务});// 3. 提交事务await transaction.commit();res.json({ affectedRows });} catch (error) {// 4. 发生错误时回滚事务if (transaction) await transaction.rollback();res.status(500).json({ error: "更新失败", details: error.message });}
});
//修改用户数据
//PUT http://localhost:3000/articles/func2/1
//body:{“title”: “New Title”, “content”: “Updated Content”}
router.put("/articles/func2/:id", async (req, res) => {const transaction = await models.sequelize.transaction();try {const { id } = req.params;const { title, content } = req.body;// 1. 使用悲观锁查询记录const article = await models.Article.findOne({where: { id },lock: true, // 行级锁transaction});if (!article) {await transaction.rollback();return res.status(404).json({ error: "文章不存在" });}// 2. 执行更新const [affectedRows] = await models.Article.update({ title, content },{ where: { id },transaction});if (affectedRows <= 0) {await transaction.rollback();return res.json({ "data": "没有数据被更新" });}// 3. 重新查询更新后的数据(在事务中)const updatedArticle = await models.Article.findByPk(id, { transaction });// 4. 提交事务await transaction.commit();res.json(updatedArticle);} catch (error) {if (transaction) await transaction.rollback();res.status(500).json({ error: "更新失败", details: error.message });}
});
//先查询再删除
//DELETE: http://localhost:3000/func1/1
router.delete("/func1/:id", async (req, res) => {const transaction = await models.sequelize.transaction();try {const { id } = req.params;// 1. 使用悲观锁查询记录const article = await models.Article.findOne({where: { id },lock: true, // 行级锁transaction});if (!article) {await transaction.rollback();return res.status(404).json({ error: "文章不存在" });}// 2. 执行删除await article.destroy({ transaction });// 3. 提交事务await transaction.commit();res.json({ success: true });} catch (error) {if (transaction) await transaction.rollback();res.status(500).json({ error: "删除失败", details: error.message });}
});
//直接删除
////DELETE: http://localhost:3000/func1/1
router.delete("/func2/:id", async (req, res) => {const transaction = await models.sequelize.transaction();try {const { id } = req.params;// 1. 使用悲观锁查询记录const article = await models.Article.findOne({where: { id },lock: true, // 行级锁transaction});if (!article) {await transaction.rollback();return res.status(404).json({ error: "文章不存在" });}// 2. 执行删除await models.Article.destroy({ where: { id },transaction});// 3. 提交事务await transaction.commit();res.json({ success: true });} catch (error) {if (transaction) await transaction.rollback();res.status(500).json({ error: "删除失败", details: error.message });}
});