Как я могу использовать один пул соединений mssql для нескольких маршрутов в веб-приложении Express 4?

Я хочу использовать node-mssql в качестве коннектора базы данных MSSQL в веб-приложении Node JS Express 4. Логика обработчика маршрута обрабатывается в отдельных файлах.

Как мне создать единый / глобальный пул соединений и использовать его в нескольких файлах, где обрабатывается логика маршрутизации? Я не хочу создавать новый пул соединений в каждой функции / файле обработчика маршрутов.


person Christiaan Westerbeek    schedule 20.05.2015    source источник


Ответы (6)


Прошло 3 года с тех пор, как я задал вопрос и ответил на него. С тех пор кое-что изменилось. Вот новое решение на основе ES6, mssql 4 и Express 4, которое я бы предложил сегодня.

Здесь играют роль два ключевых элемента.

  1. Модули кэшируются после первой загрузки. Это означает, что каждый вызов require ('./ db') будет возвращать один и тот же объект. Первое требование db.js запустит этот файл, создаст обещание и экспортирует его. Второе требование db.js вернет ТО же самое обещание без запуска файла. И это обещание разрешится с пулом.
  2. Обещание можно снова сформулировать. И если он разрешился раньше, он немедленно разрешится снова с тем, что было разрешено в первый раз, то есть пулом.

In server.js

const express = require('express')
// require route handlers.
// they will all include the same connection pool
const set1Router = require('./routes/set1')
const set2Router = require('./routes/set2')

// generic express stuff
const app = express()

// ...
app.use('/set1', set1Router)
app.use('/set2', set2Router)

// No need to connect the pool
// Just start the web server

const server = app.listen(process.env.PORT || 3000, () => {
  const host = server.address().address
  const port = server.address().port

  console.log(`Example app listening at http://${host}:${port}`)
})

In db.js

const sql = require('mssql')
const config = {/*...*/}

const poolPromise = new sql.ConnectionPool(config)
  .connect()
  .then(pool => {
    console.log('Connected to MSSQL')
    return pool
  })
  .catch(err => console.log('Database Connection Failed! Bad Config: ', err))

module.exports = {
  sql, poolPromise
}

В routes/set1.js и routes/set2.js

const express = require('express')
const router = express.Router()
const { poolPromise } = require('./db')

router.get('/', async (req, res) => {
  try {
    const pool = await poolPromise
    const result = await pool.request()
        .input('input_parameter', sql.Int, req.query.input_parameter)
        .query('select * from mytable where id = @input_parameter')      

    res.json(result.recordset)
  } catch (err) {
    res.status(500)
    res.send(err.message)
  }
})

module.exports = router

Подведем итоги

Вы всегда будете получать одно и то же обещание из-за кэширования модуля, и это обещание будет снова и снова выполняться с пулом, с которым оно было разрешено в первый раз. Таким образом, каждый файл маршрутизатора использует один и тот же пул.

Кстати: есть более простые способы попробовать поймать на экспресс-маршруте, о которых я не буду рассказывать в этом ответе. Прочтите об этом здесь: https://medium.com/@Abazhenov/using-async-await-in-express-with-node-8-b8af872c0016.

Старое решение

Это решение, которое я опубликовал 3 года назад, потому что я считал, что у меня есть ответ, которым стоит поделиться, и я не мог найти документированное решение в другом месте. Также в нескольких вопросах (# 118, # 164, # 165) на узле-mssql обсуждается эта тема.

In server.js

var express = require('express');
var sql     = require('mssql');
var config  = {/*...*/};
//instantiate a connection pool
var cp      = new sql.Connection(config); //cp = connection pool
//require route handlers and use the same connection pool everywhere
var set1    = require('./routes/set1')(cp);
var set2    = require('./routes/set2')(cp);

//generic express stuff
var app = express();

//...
app.get('/path1', set1.get);
app.get('/path2', set2.get);

//connect the pool and start the web server when done
cp.connect().then(function() {
  console.log('Connection pool open for duty');

  var server = app.listen(3000, function () {

    var host = server.address().address;
    var port = server.address().port;

    console.log('Example app listening at http://%s:%s', host, port);

  });
}).catch(function(err) {
  console.error('Error creating connection pool', err);
});

In routes/set1.js

var sql     = require('mssql');

module.exports = function(cp) {
  var me = {
    get: function(req, res, next) {
      var request = new sql.Request(cp);
      request.query('select * from test', function(err, recordset) {
        if (err) {
          console.error(err);
          res.status(500).send(err.message);
          return;
        }
        res.status(200).json(recordset);
      });
    }
  };

  return me;
};
person Christiaan Westerbeek    schedule 20.05.2015
comment
Что делать, если пул соединений закрывается или возникает ошибка? Если он выйдет из строя и вы перезапустите его, как вы можете обновить соединение во всех других файлах маршрутов? - person Wait what; 17.11.2016
comment
Я следил за большей частью вашего кода, но пропустил передачу cp. А в set1.js у меня нет параметра в вызове Request. И это все еще работало! Меня беспокоят последствия этого. Есть ли еще пул соединений при использовании этого метода? Откуда код в set1.js получил соединение? - person Old Geezer; 15.02.2017
comment
@OldGeezer Согласно документации, в этом случае используется глобальный пул. Цитата: If you omit pool/transaction argument, global pool is used instead. ref: (github .com / patriksimek / node-mssql # запрос) - person snajahi; 30.05.2017
comment
В последней версии следующая строка var cp = new sql.Connection (config); изменен на var cp = new sql.ConnectionPool (config); - person ajaysinghdav10d; 21.11.2017
comment
Иногда я получаю ResourceTimeoutError. Что делать в случае ошибки? Я добавил, чтобы прослушивать событие ошибки пула и повторно подключать пул после закрытия пула. Сообщите мне, если это не так. Спасибо! - person bugwheels94; 20.03.2018
comment
Я обновил свой ответ, чтобы отразить новые идеи, текущие стандарты, а также включить предложения из других ответов. Буду рад получить комментарии по доработкам. - person Christiaan Westerbeek; 15.06.2018
comment
Большое спасибо, это решение работает нормально, после того как раньше у меня было много проблем, связанных с обработкой соединения. - person Jay; 30.07.2018
comment
Экспорт module.exports = { sql, poolPromise }, импорт и использование следующим образом: const { poolPromise } = require('./db') , const pool = await poolPromise не будет работать. Должно быть const pool = await poolPromise.poolPromise или лучше называть переменные. - person Maihan Nijat; 05.10.2018
comment
@MaihanNijat Я не думаю, что это правильно. const pool = await poolPromise.poolPromise ведет к пулу - ›undefined - person Christiaan Westerbeek; 03.01.2019
comment
@ChristiaanWesterbeek И это обещание, которое разрешится с пулом ... Я думаю, в ответе должно было быть сказано, что это обещание, которое разрешится с помощью соединения из пула; Это приводит меня к тому, что в вашем ответе чего-то не хватает - после каждого запроса соединение должно быть возвращено в пул - с использованием правильной терминологии, например. dbConn вместо пула, заставило меня понять, что dbConn.release () отсутствует; [Но все равно отличный ответ] - person joedotnot; 11.04.2021

Когда вы настраиваете свое приложение (например, при создании экспресс-сервера), установите соединение с БД. Убедитесь, что это сделано ДО того, как вам потребуются все ваши маршруты! (финал требует в верхней части файла)

Как и в документации:

var sql = require('mssql'); var connection = new sql.Connection(..... //store the connection sql.globalConnection = connection;

Затем во всех ваших файлах маршрута вы можете сделать это:

var sql = require('mssql'); var sqlConn = sql.globalConnection; var request = new sql.Request(sqlConn); //...

Это должно сработать!

С учетом всего сказанного, используйте knex для управления построением запросов MySQL. Он имеет встроенный пул соединений, и вы сохраняете подключенный экземпляр knex таким же образом. А также щедрая порция потрясающего.

person clay    schedule 20.05.2015
comment
Это утверждение: sql.globalConnection = connection; Это нормально для веб-сервера? Я имею в виду, будут ли проблемы со многими пользователями? - person Wexoni; 17.12.2015
comment
Мой пример был просто способом сохранить эту переменную соединения в файлах. Использует то, как require() работает в Node (в основном это глобальный общий объект, и вы можете что-то добавить к нему). Пул соединений - лучшая идея для веб-сервера для обработки большего количества одновременных действий с БД. - person clay; 17.12.2015
comment
Можете ли вы предоставить мне пример, как загрузить его с помощью пула соединений? - person Wexoni; 18.12.2015
comment
В своем ответе я рекомендую использовать knexjs. Вот пример конфигурации для knexjs. После подключения вам не нужно ничего делать с пулом подключений. Похоже, вы также можете просто использовать пул в node-mysql. - person clay; 18.12.2015

src/config.js

export default {
  database: {
    server: process.env.DATABASE_SERVER || '<server>.database.windows.net',
    port: 1433,
    user: process.env.DATABASE_USER || '<user>@<server>',
    password: process.env.DATABASE_PASSWORD || '<password>',
    database: process.env.DATABASE_NAME || '<database>',
    connectionTimeout: 30000,
    driver: 'tedious',
    stream: false,
    options: {
      appName: '<app-name>',
      encrypt: true
    }
  }
};

src/server.js

import sql from 'mssql';
import express from 'express';
import config from './config';

// Create and configure an HTTP server
const server = express();
server.set('port', (process.env.PORT || 5000));

// Register Express routes / middleware
server.use('/api/user', require('./api/user');

// Open a SQL Database connection and put it into the global
// connection pool, then launch the HTTP server
sql.connect(config.database, err => {
  if (err) {
    console.log('Failed to open a SQL Database connection.', err.stack);
  }
  server.listen(server.get('port'), () => {
    console.log('Node app is running at http://127.0.0.1:' + server.get('port'));
  });
});

sql.on('error', err => console.log(err.stack));

src/api/user.js

import sql from 'mssql';
import { Router } from 'express';

const router = new Router();

router.get('/:id', async (req, res, next) => {
  try {
    const request = new sql.Request();
    request.input('UserID', req.params.id);
    request.multiple = true;

    const dataset = await request.query(`
      SELECT UserID, Name, Email
      FROM [User] WHERE UserID = @UserID;
      SELECT r.RoleName FROM UserRole AS r
        INNER JOIN [User] AS u ON u.UserID = r.UserID
      WHERE u.UserID = @UserID
    `);

    const user = dataset[0].map(row => ({
      id: row.UserID,
      name: row.Name,
      email: row.Email,
      roles: dataset[1].map(role => role.RoleName)
    })).shift();

    if (user) {
      res.send(user);
    } else {
      res.statusCode(404);
    }
  } catch (err) {
    next(err);
  }
});

export default router;

См. также MSSQL SDK для Node.js, Справочник по T-SQL, React Starter Kit

person Community    schedule 30.08.2015
comment
в этом примере используется машинописный текст, замените import sql from 'mssql'; и т. д. на const sql = requires('mssql');. обратите внимание, что const не будет работать, если вы используете старую версию узла, вместо этого используйте var. также export default <xx> следует заменить на module.exports = <xx>. - person snajahi; 30.05.2017

Вот как я это сделал, что, на мой взгляд, немного проще, чем некоторые другие решения.

Файл базы данных (db.js):

const sql = require('mssql')

const config = {}

const pool = new sql.ConnectionPool(config)
  .connect()
  .then(pool => {
    console.log('Connected to MSSQL')
    return pool
  })
  .catch(err => console.log('Database Connection Failed! Bad Config: ', err))

module.exports = {
  sql, pool
}

Запрос:

const { pool, sql } = require('../db')

return pool.then(conn => {
    const ps = new sql.PreparedStatement(conn)
    ps.input('xxxx', sql.VarChar)

    return ps.prepare(`SELECT * from table where xxxx = @xxxx`)
      .then(data => ps.execute({ xxxx: 'xxxx' }))
  })

РЕДАКТИРОВАТЬ: обновлено, чтобы соответствовать сути Кристиана Вестербика, которая была намного чище.

person ozzieisaacs    schedule 13.06.2018
comment
Сегодня я, наверное, тоже сделал бы что-то подобное. Но я не думаю, что вам нужен тайм-аут. Кроме того, обещание, которое уже было разрешено ранее, может быть снова изменено, и в этом случае оно немедленно разрешится с более ранним результатом. Итак, вам не нужны ни тайм-аут, ни условие. - person Christiaan Westerbeek; 14.06.2018
comment
Если у вас нет тайм-аута и вы попытаетесь выполнить запрос до того, как пул будет готов, он все равно будет иметь значение NULL. Таким образом, мы просто продолжаем ждать 200 мс каждый раз, пока пул окончательно не инициализируется. Может я не понимаю твоего объяснения? - person ozzieisaacs; 14.06.2018
comment
Спасибо за суть, это лучший способ сделать это! Я проигнорировал, что он уже вернул обещание. Я собираюсь отредактировать то, что написал выше. - person ozzieisaacs; 14.06.2018
comment
Я удалил ссылку на свою суть, поскольку она включена в мой обновленный ответ - person Christiaan Westerbeek; 24.06.2018
comment
почему не использовать Unprepare после заявления? - person platinums; 27.03.2019

Я использовал аналогичную концепцию (single connection pool), но заключил логику подключения в один файл (нет необходимости передавать пул подключений в другие места). Приведенный ниже connPoolPromise будет инициализирован только один раз, поскольку модули кэшируются после первой загрузки.

e.g. DBUtil.js

const sql = require('mssql');
const dbConfig = require('./dbconfig');
let connPoolPromise = null;

const getConnPoolPromise = () => {
  if (connPoolPromise) return connPoolPromise;

  connPoolPromise = new Promise((resolve, reject) => {
    const conn = new sql.ConnectionPool(dbConfig);

    conn.on('close', () => {
      connPoolPromise = null;
    });

    conn.connect().then(connPool => {
      return resolve(connPool);
    }).catch(err => {
      connPoolPromise = null;
      return reject(err);
    });
  });

  return connPoolPromise;
}

// Fetch data example using callback
exports.query = (sqlQuery, callback) => {

  getConnPoolPromise().then(connPool => {

    return connPool.request().query(sqlQuery);

  }).then(result => {
    callback(null, result);
  }).catch(err => {
    callback(err);
  });

};

Использование user.js:

const DBUtil = require('./DBUtil');
DBUtil.query('select * from user where userId = 12', (err, recordsets) => {
  if (err) return callback(err);

  // Handle recordsets logic

}
person Jonathan    schedule 14.03.2017
comment
Мне нравится эта идея, чтобы она работала сейчас var conn = new sql.Connection необходимо изменить на var conn = new sql.ConnectionPool - person platinums; 27.03.2019
comment
Мне этот нравится, мне пришлось изменить функцию запроса. обратный вызов, который я должен был вставить с запросом. Вот так: connPool.request (). Query (sqlQuery, callback) и удалите then и поймайте там. - person Andres; 03.09.2019
comment
Спасибо за это. Я тестировал исходный ответ, отключив соединение SQL, и он не справился с этим. То, как вы возвращаете обещание и устанавливаете для него значение null при ошибках, хорошо работает :) - person Adam91Holt; 13.12.2019

Не без ума от примеров, которые я видел до сих пор для настройки объединенного соединения. Я делаю:

const pool = new mssql.ConnectionPool(msConfig).connect()
  .then(_ => { return _ } )
  .catch(e => console.error("Database Trouble!  ", e))
  
 /* ... */
 
 pool
  .then(_ => _.query( /* ... */ )
  .then(result => { /* ... */ })
  .catch(e => { /* ... */ })

person Shawn Kelly    schedule 04.07.2019
comment
я тоже, страницы npm тоже повсюду. У вас есть самая лаконичная версия просьбы, которую я когда-либо видел: Well Done! - person platinums; 26.11.2019