如何在Node.Js中组织与数据库的通信?

时间:2019-07-08 17:11:12

标签: javascript node.js database express

我在Nodejs的应用程序中设计与MySQL数据库的通信时遇到问题。 最大的问题是查询是异步的,因此设计我的项目变得很复杂。例如,我有excercises.js

EXCERCISES.JS

var express = require('express');
var database = require('../database/database.js');

var router = express.Router();

console.log(database.db(saveDbData))
/* GET users listing. */
router.get('/', function(req, res, next) {

  res.render('exercises',{title: 'Exercises', ex: #DATABASE RESULT});
});

module.exports = router;

需要在 ex 字段中写入查询结果。

然后我编写一个模块来处理mysql连接

DATABASE.JS

var pool = mysql.createPool({
    connectionLimit: 10000,
    host: 'localhost',
    user: 'root',
    password: 'password',
    database: 'Example'
});

var results;

var db = function(){
    pool.query('SELECT name FROM Exercises', function(error, results, fields){
    if (error) throw error;
    res = results;
})
    return res;
}

module.exports = {
    db: db,
}

很显然,它不起作用,因为pool.query是异步的。 我在网上找到的唯一替代方法是这样的:

EXERCISES.JS


var pool = mysql.createPool({
    connectionLimit: 10000,
    host: 'localhost',
    user: 'root',
    password: 'password',
    database: 'Example'
});


pool.query('SELECT name FROM Exercises', function(error, results, fields){
    if (error) throw error;
    router.get('/', function(req, res, next) {
        res.render('exercises',{title: 'Exercises', ex: result[0].name});
    }); 
})

但是以这种方式,mysql部分和路由/渲染部分混合在一起。它仍然是设计良好的解决方案吗?有更优雅的解决方案吗?

EDIT

我已经修改了文件,并像这样使用Promise

EXERCISES.JS

var express = require('express');
var database = require('../database/database.js');

var router = express.Router();


var data = database.db()
.then(
    function(data){
        console.log("Resolved");
        /* GET users listing. */
        router.get('/', function(req, res, next) {
            res.render('exercises',{title: 'Exercises', ex: data[0].name});
        });
    })
.catch(
    error => console.error(error));



module.exports = router;

DATABASE.JS

ar mysql = require('mysql');


var pool = mysql.createPool({
    connectionLimit: 10000,
    host: 'localhost',
    user: 'root',
    password: 'password',
    database: 'Example'
});

var res;
var db = function(){
    return new Promise(function(resolve, reject){
        pool.query('SELECT name FROM Exercises', function(error, results, fields){
            if (error) reject(error);
            resolve(results)

        })
    })
}


module.exports = {
    db: db,
}

它有效,但是我认为这不是最佳解决方案。例如,如果我想从更多查询中获取用于渲染的数据该怎么办? 我是这些技术的新手,因此无法找出整合数据库和html页面呈现的最佳方法。

2 个答案:

答案 0 :(得分:0)

您可以使用npm模块通过MySQL实现异步任务。 我建议选择 sequilize jm-ez-mysql 。如果您使用 jm-ez-mysql ,那么您的代码结构就像

server.js

require('./config/database.js');

./ config / database.js

const sql = require('jm-ez-mysql');

// Init DB Connection
const connection = My.init({
  host: process.env.DBHOST,
  user: process.env.DBUSER,
  password: process.env.DBPASSWORD,
  database: process.env.DATABASE,
  dateStrings: true,
  charset: 'utf8mb4',
  timezone: 'utc',
  multipleStatements: true,
});

module.exports = {
  connection,
};

之后,您可以异步使用MySQL。

./ exercise.js

const sql = require('jm-ez-mysql');

const exerciseUtil = {};

exerciseUtil.searchUserById = async (id) => {
  try {
    // table name, db fields, condition, values
    const result = await sql.first('users', ['id, name, email'], 'id = ?', [id]);
    return result; // Which return your result in object {}
  } catch (err) {
    console.log(err);
    throw err;
  }
};

module.exports = exerciseUtil;

我希望它会有所帮助。 快乐编码:)

答案 1 :(得分:0)

您是否想知道为什么节点中的所有Web框架都要求您使用res对象而不是return返回响应?这是因为所有Web框架都希望您需要执行异步操作。

考虑类似于Laravel(PHP)或Spring Framework(Java)的Web框架设计:

// Theoretical synchronous framework API:
app.get('/path', function (request) {
    return "<html><body> Hello World </body></html>";
});

然后,如果您需要执行异步操作,则将面临以下问题:在您需要返回HTTP请求时,所获取的数据尚未返回:

// Theoretical synchronous framework API:
app.get('/path', function (request) {
    return ??? // OH NO! I need to return now!!
});

因此,javascript中的Web框架不会对返回值起作用。相反,它会在完成后为您传递一个回调以供调用:

// Express.js
app.get('/path', function (request, response) {
    doSomethingAsync((err, result) => {
        response.send(result);
    });
});

因此,对于您的代码,您只需要执行以下操作:

router.get('/', function(req, res) {
    pool.query('SELECT name FROM Exercises', function(error, results, fields){
        if (error) throw error;   
        res.render('exercises',{title: 'Exercises', ex: result[0].name});
    }); 
});

导出数据库

导出数据库就像导出pool一样简单:

db.js

var pool = mysql.createPool({
    connectionLimit: 10000,
    host: 'localhost',
    user: 'root',
    password: 'password',
    database: 'Example'
});

module.exports = {
    pool: pool
}

exercises.js

let db = require('./db');

// you can now use db.pool in the rest of your code
// ..

重复使用您的查询

您可以(并且应该)在您的数据库模块中对它们进行编码,而不是在控制器(路由)中对SELECT语句进行编码:

db.js

var pool = mysql.createPool({
    connectionLimit: 10000,
    host: 'localhost',
    user: 'root',
    password: 'password',
    database: 'Example'
});

function getExerciseNames (callback) {
    pool.query('SELECT name FROM Exercises',callback);
}

module.exports = {
    pool: pool
}

然后,您只需在控制器逻辑中执行以下操作即可:

router.get('/', function(req, res) {
    db.getExerciseNames(function(error, results, fields){
        if (error) throw error;   
        res.render('exercises',{
            title: 'Exercises',
            ex: result[0].name
        });
    }); 
});

缓存

如果您打算仅一次查询数据库以缓存Exercises的值,则不要反转Express路由流程。而是在您的数据库层实现缓存:

db.js

var exerciseNamesCache = [];
var exerciseNamesFields = [];

function getExerciseNames (callback) {
    if (exerciseNamesCache.length > 0 && exerciseNamesFields.length > 0) {
        callback(null, exerciseNamesCache, exerciseNamesFields);
    }
    pool.query('SELECT name FROM Exercises',function(error, results, fields){
        if (!error) {
            exerciseNamesCache = results;
            exerciseNamesFields = fields;
        }
        callback(error, results, fields);
    });
}

承诺

Promise是用于处理回调的设计模式。它仅比Java的Futures(CompletionStage等)轻巧得多。如果您使用的API返回承诺而不是接受回调,那么您需要在承诺的res.render()方法内调用.then()

router.get('/', function(req, res, next) {
    doSomethingAsync()
        .then(function(result){
            res.send(result);
        })
        .catch(next); // remember to pass on asynchronous errors to next()
});

如果您使用的API接受了回调,那么无论是否天气,您将其包装在Promise中都是一个问题。我个人不会这样做,除非您还使用另一个返回承诺的API。

异步/等待

promise的一个优点是您可以将它们与await一起使用。 Express特别适用于异步/等待。请记住,您只能在标有await的函数内使用async

router.get('/', async function(req, res, next) {
    let result = await doSomethingAsync();
    res.send(result);
});

多个异步操作

获取多个异步数据可以很简单:

router.get('/', function(req, res, next) {
    doSomethingAsync(function(result1){
        doSomethingElse(function(result2) {
            res.json([result1, result2]);
        });
    });
});

承诺如下:

router.get('/', function(req, res, next) {
    doSomethingAsync()
        .then(function(result1){
            return doSomethingElse()
                .then(function(result2) {
                    return [result1, result2];
                 });
         })
         .then(function(results){
             res.json(results);
         })
         .catch(next);
});

但是以上两个代码都依次执行请求(先获取result1,然后再获取result2)。如果要并行获取两个数据,可以使用Promises:

router.get('/', function(req, res, next) {
    Promise.all([
        doSomethingAsync(),  // fetch in parallel
        doSomethingElse()
    ])
    .then(function(results){
        res.json(results);
    });
})

使用回调函数会更加复杂。您可以使用一种设计模式,实际上有人已经将其实现为名为async.js的库,但是通常最简单的解决方案是将它们包装在Promises中并使用Promise.all()。不过,还是要检查一下async.js,因为它具有对批处理请求,条件为真时执行异步操作等有用的功能(该库的基于Promise的副本是async-q