【问题标题】:Multiple SQL Queries In Node.jsNode.js 中的多个 SQL 查询
【发布时间】:2021-09-10 11:21:07
【问题描述】:

我正在为user registration 创建一个route,我需要使用multiple queries 以便确定usernameemail 是否存在于数据库中。

我的第一个解决方案是在connection.query 内创建一个connection.query,但它不起作用。

const { username, email, password, confirm } = req.body
var error_mssg = []
connection.query(
  `SELECT COUNT(*) as usernameCount FROM adminuser WHERE username="${username}"`,
  (err, data) => {
    if (err) throw err;

    if(data[0].usernameCount){
      error_mssg.push("username already exist")
    }

    connection.query(
      `SELECT COUNT(*) as emailCount FROM adminuser WHERE email="${email}"`,
      (err, data) => {
        if (err) throw err;

        if (data[0].emailCount > 0){
          error_mssg.push("email aready exist")
        }
  }
);

我还尝试在我的mysql.createConnection 中添加multipleStatemts: true,效果很好,但我记得如果在数据库中找不到相同的username/email 并且如果password == confirm,我仍然需要创建另一个connection.query

connection.query(
  `SELECT COUNT(*) as usernameCount FROM adminuser WHERE username="${username}";
  SELECT COUNT(*) as emailCount FROM adminuser WHERE email="${email}"`,
  (err, data) => {
    if (err) throw err;

    if(data[0][0].usernameCount){
      error_mssg.push("username already exist")
    }

    if(data[1][0].emailCount){
      error_mssg.push("email already exist")
    }

    if(password === confirm){
      if(error_mssg.lenght > 0){
        *******query to insert user information*******
      }else{
        res.send(error_mssg)
      }

    }else{
      error_mssg.push("password does not match")
      res.send(error_mssg)
    }
  }
);

如果error_mssg为空,我如何使用multiple queries然后插入数据。

【问题讨论】:

  • “它不起作用”的描述性并不好。问题陈述应准确说明某事未按预期运行,包括任何错误消息、输出或其他结果,以及期望/预期结果。有关asking good questions 的更多信息,请参阅帮助中心。

标签: mysql node.js express


【解决方案1】:

您可以尝试使用async 查询。最好的方法是在同一查询中尝试使用多个语句。

const usernameCount = await db.query( `SELECT COUNT(*) as usernameCount FROM adminuser WHERE username="${username}"` );
const emailCount = await db.query( `SELECT COUNT(*) as emailCount FROM adminuser WHERE email="${email}"` );

【讨论】:

  • 我有一个const Register = async (req, res) => { ... } 并尝试使用await 创建一个变量,但是当我尝试使用console.log(Register) 时它返回<ref *1> Query { ...long things I dont understand }。你在使用不同的库吗?我正在使用mysql
  • 请发布您的代码 sn-p 并提供更多详细信息,以便我以更好的方式为您提供帮助。
  • 感谢您的回答,我找到了可以支持async awaitmysql2 库,它解决了我的问题。
【解决方案2】:

注入

首先,关闭注入漏洞。用户输入不应该被信任,也不应该直接传递给另一个子系统。最好的办法是在旁通道中传递用户数据;在 SQL 中,这意味着使用准备好的查询。例如,您可以使用connection.execute,而不是connection.query

connection.execute(
    'SELECT Count(*) AS usernameCount FROM adminuser WHERE username=?',
    [username],
    (err, data) => {...}
)

可用的 MySQL 驱动程序可能只支持转义参数和模拟预准备语句,但这总比没有好(但如果是这种情况,您应该认真考虑切换到支持预准备语句的驱动程序):

connection.query(
    mysql.format('SELECT Count(*) AS usernameCount FROM adminuser WHERE username=?', [username]), 
    ...

逻辑错误

仔细查看多语句示例中的块:

if(password === confirm){
  if(error_mssg.lenght > 0){
    *******query to insert user information*******
  }else{
    res.send(error_mssg)
  }
...

注意如果有错误信息则插入用户信息,如果没有则返回(空)错误信息列表。这与您想要的相反。调试器可以帮助捕捉这种逻辑错误。

如果密码不匹配,您不仅可以发送存在错误消息,还可以发送所有错误消息。这样,用户可以一次更正所有错误,而不必只提交以发现其他错误。这也将更好地将请求验证与请求处理分开。

if (password !== confirm) {
    error_msgs.push("passwords do not match");
}

if (error_msgs.length) {
    res.send(error_msgs);
} else {
    // create new user
    ...
}

存在性测试

查询现有记录的另一种方法是使用单个查询来检查adminuser 数据库中的现有用户名和电子邮件地址。这比链式查询或awaits 具有效率优势,因为一个查询不需要等待另一个查询完成(尽管后两者的性能成本可能可以忽略不计)。

由于这两个字段是同一数据库中的不同列,因此查询很简单:

SELECT username, email FROM adminuser WHERE username=? OR email=?;

用户名和电子邮件应唯一的数据要求应通过使用UNIQUE 索引反映在架构中。这会将结果行限制为最多 2 行(每个值的最大出现次数为 1),然后可以检查是否存在唯一值。

connection.execute(
    'SELECT username, email FROM adminuser WHERE username=? OR email=?;',
    [username, email],
    (err, data) => {
        if (err) throw err;
        
        /*** Validate user creation request ***/
        /* There should be at most 2 rows, with at most 1 occurrence each of `username` and `email`. */
        for (var iRow=0; iRow < data.length; ++iRow) {
            if (username == data[iRow].username) {
                error_msgs.push("username already exists");
            }
            if (email == data[iRow].email) {
                error_msgs.push("email already exists");
            }
        }
        
        if (password !== confirm) {
            error_msgs.push("passwords do not match");
        }
        
        /*** Handle user creation request ***/
        if (error_msgs.length) {
            res.send(error_msgs);
        } else {
            // create new user
            ...
        }
    }
);

以更复杂的 SQL 语句为代价,可以使存在性检查更加健壮(以防数据库出现问题并返回多次出现的用户名或电子邮件)。如果驱动程序支持,命名占位符(而不是“?”)可以用于清晰和减少冗余。

// namedPlaceholders can also be an `execute` option
connection.config.namedPlaceholders = true
...

connection.execute(
    'SELECT Sum(If(:username=username, 1, 0) As usernameCount, 
            Sum(If(:email=email, 1, 0) AS emailCount 
         FROM adminuser 
         WHERE username=:username OR email=:email;',
    { username, email }, // you could instead pass req.body, assuming it's been checked for 'username' and 'email' properties
    (err, data) => {
        if (err) throw err;
        
        /*** Validate user creation request ***/
        if (data[0].usernameCount) {
            error_msgs.push("username already exists");
        }
        if (data[0].emailCount) {
            error_msgs.push("email already exists");
        }
        if (password !== confirm) {
            error_msgs.push("passwords do not match");
        }
        
        /*** Handle user creation request ***/
        if (error_msgs.length) {
            res.send(error_msgs);
        } else {
            // create new user
            ...
        }
    });

其他设计注意事项

示例代码混合了几个不同的问题:数据库访问和请求处理(验证等)。换句话说,示例代码承担了多重责任,这是不可取的。代码单元的职责应尽可能少;一个类应该有一个单一的职责。单一职责最流行的定义是Single Responsibility Principle:“一个类应该只有一个改变的理由”。此定义并不完全适用于大于类的代码单元(取决于如何表达更改原因)。

在示例中,考虑可能出现的维护任务:

  • 添加/切换 DBMS 支持
  • 更新用户字段验证(包括添加字段)
  • 在创建用户时发送消息(例如管理员、模组、新用户)

其中每一个都是更改代码的不同原因,因此是不同的责任。

Separating concerns 将导致类的职责减少。一个模块,数据访问层,应该处理管理数据库中的用户(例如检查现有记录中的用户名或电子邮件,添加新用户),另一个应该通过进行适当的调用来处理用户创建请求(验证和用户创建)到 DAL(可能是间接的)。特别是,SQL 语句和数据库调用不应出现在请求处理程序中。请求处理程序可能类似于:

async function validateUserCreationRequest(req) {
    const { username, email, password, confirm } = req.body;
    var error_msgs = [],
        userExists = await User.exists({ username, email }); // here, User is responsible for accessing the DAL

    if (userExists) {
        if (userExists.usernameCount) {
            error_msgs.push("username already exists");
        }
        if (userExists.emailCount) {
            error_msgs.push("email already exists");
        }
    }
    
    if (password !== confirm) {
        error_msgs.push("passwords do not match");
    }
    
    if (error_msgs.length) {
        return error_msgs;
    }
    return null;
}

function createUser(req) {
    return User.create(req); // again, User is responsible for accessing the DAL
}

function handleUserCreationRequest(req) {
    var errors = validateUserCreationRequest(req);
    if (errors) {
        res.send(errors);
    } else {
        createUser(req);
    }
}

当代码承担多重责任时,一般是高度coupled(处理不同任务的代码之间存在高度的相互依赖关系),这使得维护更加困难并导致更多的错误。分离这些职责减少了耦合并增加了cohesion(模块中的代码属于一起,因为它们只关注它们完成的任务),因为耦合和内聚是反向相关的。

concerns and responsibilities 之间有相当多的重叠,但它们是 distinct 原则。

在某些情况下可能会违反这些原则,但这应该源于软件需求并且是有意识的决定。

【讨论】:

  • 这是我读过的最详细的解释。我没有注意到我在这里错误地写错了我的逻辑,但我已经按照你的方式重写了我的逻辑。我也不认为我可以像那样验证username/email,这太棒了。最后一个问题为什么我不能在request 里面写query?我真的不明白coupling 是什么。
  • @Nellartsa:查看更新,并查看链接页面了解更多详情。
猜你喜欢
  • 2013-09-09
  • 1970-01-01
  • 2015-05-21
  • 1970-01-01
  • 2014-10-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多