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=1
        const { 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' });
    }
});
请求体中获取
  1. 使用传统的对象属性赋值方式
  2. 使用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; // 获取查询字符串中的 id

    try {
        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 });
  }
});
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐