Как я могу использовать один пул соединений mssql для нескольких маршрутов в веб-приложении Express 4?
Я хочу использовать node-mssql в качестве коннектора базы данных MSSQL в веб-приложении Node JS Express 4. Логика обработчика маршрута обрабатывается в отдельных файлах.
Как мне создать один / глобальный пул соединений и использовать его в нескольких файлах, где обрабатывается логика маршрута? Я не хочу создавать новый пул соединений в каждой функции / файле обработчика маршрута.
5 ответов
Прошло 3 года с тех пор, как я спросил и ответил на вопрос. С тех пор несколько вещей изменились. Вот новое решение, основанное на ES6, mssql 4 и Express 4, которое я бы предложил сегодня. Это не было проверено, хотя.
Здесь действуют два ключевых элемента.
- Модули кэшируются после первой загрузки. Это означает, что каждый вызов require('./db') будет возвращать один и тот же объект. Первое требование db.js запустит этот файл, создаст обещание и экспортирует его. Второе требование db.js будет экспортировать ТАКОЕ обещание. Это обещание будет решаться с пулом.
- Обещание может быть снова придумано. И если он был разрешен ранее, он сразу же разрешится снова с тем, что он разрешил в первый раз, а именно с пулом.
В 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}`)
})
В 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) на node-mssql обсуждается эта тема.
В 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);
});
В 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;
};
Когда вы настраиваете свое приложение (например, когда вы создаете экспресс-сервер), установите соединение с БД. Убедитесь, что это сделано, прежде чем вам потребуются все ваши маршруты! (finagle требует в верхней части файла)
Так же, как документы:
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 таким же образом. А также щедрая помощь удивительным.
Вот как я это сделал, и я думаю, что это немного проще, чем некоторые другие решения.
Файл базы данных (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' }))
})
РЕДАКТИРОВАТЬ: Обновлено, чтобы соответствовать сущности Кристиана Вестербека, который был намного чище.
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 Reference, React Starter Kit.
Я использовал похожую концепцию (single connection pool
), но обернул логику соединения в один файл (нет необходимости передавать пул соединений в другие места). connPoolPromise
ниже будет инициализирован только один раз, так как модули кэшируются после первой загрузки.
например DBUtil.js
var sql = require('mssql'),
connPoolPromise = null;
function getConnPoolPromise() {
if (connPoolPromise) return connPoolPromise;
connPoolPromise = new Promise(function (resolve, reject) {
var conn = new sql.Connection(require('./dbConfig'));
conn.on('close', function () {
connPoolPromise = null;
});
conn.connect().then(function (connPool) {
return resolve(connPool);
}).catch(function (err) {
connPoolPromise = null;
return reject(err);
});
});
return connPoolPromise;
}
// Fetch data example
exports.query = function(sqlQuery, callback) {
getConnPoolPromise().then(function (connPool) {
var sqlRequest = new sql.Request(connPool);
return sqlRequest.query(sqlQuery);
}).then(function (result) {
callback(null, result);
}).catch(function (err) {
callback(err);
});
};
использование user.js
:
var DBUtil = require('./DBUtil');
DBUtil.query('select * from user where userId = 12', function (err, recordsets) {
if (err) return callback(err);
// Handle recordsets logic
}
Не без ума от примеров, которые я видел до сих пор для настройки объединенного соединения. Я делаю:
const pool = new mssql.ConnectionPool(msConfig).connect()
.then(_ => { return _ } )
.catch(e => console.error("Database Trouble! ", e))
/* ... */
pool
.then(_ => _.query( /* ... */ )
.then(result => { /* ... */ })
.catch(e => { /* ... */ })