Node.js + OracleDb - вставлять последнюю дату много раз
Я получил этот JSON:
{
"TOTAL_RECORDS": 1029,
"REGISTROS": [
{
"CODIGO": "1",
"ATENDIMENTO": "1",
"PAGAMENTO": "1",
"VENCIMENTO": "2016-12-17 00:00:00",
"PROCESSAMENTO": "2016-12-10 00:00:00",
"VALOR": "1800.00000",
"NOSSO_NUMERO": "xxxxxxx",
"NUMERO_DOCUMENTO": "xxxxx",
"CODIGO_BANCO": "123",
"LINHA_DIGITAVEL": "XXX70110000180000",
"CODIGO_BARRAS": "XXX90940"
},
{
"CODIGO": "2",
"ATENDIMENTO": "2",
"PAGAMENTO": "2",
"VENCIMENTO": "2016-12-17 00:00:00",
"PROCESSAMENTO": "2016-12-10 00:00:00",
"VALOR": "2700.00000",
"NOSSO_NUMERO": "xxxxxxx",
"NUMERO_DOCUMENTO": "xxxxx",
"CODIGO_BANCO": "123",
"LINHA_DIGITAVEL": "XXX70110000180000",
"CODIGO_BARRAS": "XXX90940"
},...
Затем мне нужно поймать эту информацию и сохранить в БД Oracle, поэтому я делаю это:
module.exports = function (object, callback) {
var oracledb = require('oracledb');
for(const prop in object['REGISTROS']){
codigo = object['REGISTROS'][prop]['CODIGO'];
atendimento = object['REGISTROS'][prop]['ATENDIMENTO'];
pagamento = object['REGISTROS'][prop]['PAGAMENTO'];
vencimento = object['REGISTROS'][prop]['VENCIMENTO'];
processamento = object['REGISTROS'][prop]['PROCESSAMENTO'];
valor = parseInt(object['REGISTROS'][prop]['VALOR']);
nossoNumero = object['REGISTROS'][prop]['NOSSO_NUMERO'];
numeroDocumento = object['REGISTROS'][prop]['NUMERO_DOCUMENTO'];
codigoBanco = object['REGISTROS'][prop]['CODIGO_BANCO'];
linhaDigitavel = object['REGISTROS'][prop]['LINHA_DIGITAVEL'];
codigoBarras = object['REGISTROS'][prop]['CODIGO_BARRAS'];
oracledb.getConnection({
user: "x",
password:"xxx",
connectString: "mycon/string"
},
function(err, connection){
if (err){
console.error(err.message);
return;
}
connection.execute(
"INSERT INTO USU_TBOLETO VALUES (:USU_CODIGO, :USU_ATEND, :USU_PAGAMENTO, " +
":USU_VENCIMENTO, :USU_PROCESSA, :USU_VALOR, :USU_NOSSONUM, :NUMERODOC, :USU_CODBANCO, " +
":USU_LINHADIG , :USU_CODBARRAS)",
[codigo, atendimento, pagamento, vencimento, processamento, valor, nossoNumero,
numeroDocumento, codigoBanco, linhaDigitavel, codigoBarras],
{ autoCommit: true},
function(err, result){
if (err){
console.error(err.message);
doRelease(connection);
return;
}
console.log(codigo + ' - ' + atendimento + ' - ' + pagamento + ' - ' + vencimento);
///console.log(result.metaData);
///console.log(result.rows);
doRelease(connection);
});
});
}
function doRelease(connection) {
connection.release(
function(err){
if (err) { console.error(err.message); }
}
);
}
}
И проблема в том, что он вставляется в мою базу данных только последнюю запись, 1029 раз, как и все записи. Зачем? Я не понимаю почему. Код INSERT находится в состоянии FOR.
Правильным будет вставить 1029 раз, начиная codigo 1 до 1029.
Att. Диого
2 ответа
Когда вы запускаете код в том виде, в котором он у вас сейчас есть, он отправляет множество асинхронных операций в рабочую очередь libuv, и ваш код JavaScript теряет контроль. Я бы рекомендовал использовать метод eachSeries модуля async для поддержания контроля или цепочки обещаний (или async/await в Node.js 7.6+).
Кроме того, получение нового соединения для каждой итерации цикла сильно замедляет работу! Вам нужно только одно соединение для этой операции. Вы должны получить соединение, обработать данные, закрыть соединение.
Еще одна вещь, которую нужно пересмотреть, - это использование autoCommit: true. Если вы делаете это для каждой строки, вы рассматриваете каждую вставку как отдельную транзакцию. Если сбой произойдет на полпути, выяснить, что пошло не так, и исправить его (вставив оставшиеся строки) будет сложно и вручную. Я рекомендую использовать метод commit для объекта подключения после того, как все было вставлено (или использовать autoCommit: true на последней итерации цикла).
Вот пример:
const oracledb = require('oracledb');
const config = require('./dbConfig.js');
module.exports = function(object, callback) {
let conn;
function insertRegistro(registro, commit) {
if (commit) {
console.log('Last iteration of the loop, committing with this one');
}
return conn.execute(
`insert into usu_tboleto (
usu_codigo, usu_atend, usu_pagamento, usu_vencimento, usu_processa, usu_valor,
usu_nossonum, numerodoc, usu_codbanco, usu_linhadig, usu_codbarras
) values (
:usu_codigo, :usu_atend, :usu_pagamento, :usu_vencimento, :usu_processa, :usu_valor,
:usu_nossonum, :numerodoc, :usu_codbanco, :usu_linhadig , :usu_codbarras
)`,
{
usu_codigo: registro.CODIGO,
usu_atend: registro.ATENDIMENTO,
usu_pagamento: registro.PAGAMENTO,
usu_vencimento: registro.VENCIMENTO,
usu_processa: registro.PROCESSAMENTO,
usu_valor: registro.VALOR,
usu_nossonum: registro.NOSSO_NUMERO,
numerodoc: registro.NUMERO_DOCUMENTO,
usu_codbanco: registro.CODIGO_BANCO,
usu_linhadig: registro.LINHA_DIGITAVEL,
usu_codbarras: registro.CODIGO_BARRAS
},
{
autoCommit: commit
}
);
}
oracledb.getConnection(config)
.then(function(c) {
conn = c;
console.log('Got connection, starting loop');
let promiseChain = Promise.resolve();
object['REGISTROS'].forEach(function(registro, index) {
promiseChain = promiseChain
.then(function() {
return insertRegistro(registro, object['REGISTROS'].length === index + 1);
});
});
return promiseChain;
})
.catch(function(err) {
console.log(err);
console.log('Encountered error, rolling back transaction');
return conn.rollback()
.then(function() {
console.log('Transaction rolled back');
})
.catch(function(err) {
console.log('Error rolling back', err);
});
})
.then(function() {
return conn.close();
})
.then(function() {
console.log('Connection closed');
})
.catch(function(err) {
console.log(err);
});
}
Вы могли бы оптимизировать это дальше, сократив количество циклов туда и обратно с 1029 до 1 (делая все сразу) или 3 (если вы делаете наборы по 500 одновременно). Это значительно улучшит производительность. Вот пример, который делает пакетные вставки в группах по 500.
const oracledb = require('oracledb');
const config = require('./dbConfig.js');
module.exports = function(object, callback) {
let conn;
function insertRegistros(opts) {
if (opts.commit) {
console.log('Last iteration of the loop, committing with this one');
}
return conn.execute(
`declare
type varchar2_aat is table of varchar2(50)
index by pls_integer;
l_usu_codigo_vals varchar2_aat;
begin
l_usu_codigo_vals := :usu_codigo_vals;
forall x in 1 .. l_usu_codigo_vals.count
insert into usu_tboleto (
usu_codigo, usu_atend, usu_pagamento, usu_vencimento, usu_processa, usu_valor,
usu_nossonum, numerodoc, usu_codbanco, usu_linhadig, usu_codbarras
) values (
:usu_codigo_vals(x), :usu_atend_vals(x), :usu_pagamento_vals(x), :usu_vencimento_vals(x), :usu_processa_vals(x), :usu_valor_vals(x),
:usu_nossonum_vals(x), :numerodoc_vals(x), :usu_codbanco_vals(x), :usu_linhadig_vals(x) , :usu_codbarras_vals(x)
);
end;`,
{
usu_codigo_vals: {type: oracledb.STRING, dir: oracledb.BIND_IN, val: opts.usu_codigo_vals},
usu_atend_vals: {type: oracledb.STRING, dir: oracledb.BIND_IN, val: opts.usu_atend_vals},
usu_pagamento_vals: {type: oracledb.STRING, dir: oracledb.BIND_IN, val: opts.usu_pagamento_vals},
usu_vencimento_vals: {type: oracledb.STRING, dir: oracledb.BIND_IN, val: opts.usu_vencimento_vals},
usu_processa_vals: {type: oracledb.STRING, dir: oracledb.BIND_IN, val: opts.usu_processa_vals},
usu_valor_vals: {type: oracledb.STRING, dir: oracledb.BIND_IN, val: opts.usu_valor_vals},
usu_nossonum_vals: {type: oracledb.STRING, dir: oracledb.BIND_IN, val: opts.usu_nossonum_vals},
numerodoc_vals: {type: oracledb.STRING, dir: oracledb.BIND_IN, val: opts.numerodoc_vals},
usu_codbanco_vals: {type: oracledb.STRING, dir: oracledb.BIND_IN, val: opts.usu_codbanco_vals},
usu_linhadig_vals: {type: oracledb.STRING, dir: oracledb.BIND_IN, val: opts.usu_linhadig_vals},
usu_codbarras_vals: {type: oracledb.STRING, dir: oracledb.BIND_IN, val: opts.usu_codbarras_vals}
},
{
autoCommit: opts.commit
}
);
}
oracledb.getConnection(config)
.then(function(c) {
conn = c;
console.log('Got connection, starting loop');
const batchRowCount = 500;
const loops = Math.ceil(object['REGISTROS'].length / batchRowCount);
let promiseChain = Promise.resolve();
let registrosIdx = 0;
for (let outerIndex = 0; outerIndex < loops; outerIndex += 1) {
(function() {
const usu_codigo_vals = [];
const usu_atend_vals = [];
const usu_pagamento_vals = [];
const usu_vencimento_vals = [];
const usu_processa_vals = [];
const usu_valor_vals = [];
const usu_nossonum_vals = [];
const numerodoc_vals = [];
const usu_codbanco_vals = [];
const usu_linhadig_vals = [];
const usu_codbarras_vals = [];
for (let idx = 0; idx < batchRowCount; idx += 1) {
if (registrosIdx === object['REGISTROS'].length) {
break;
}
usu_codigo_vals.push(object['REGISTROS'][registrosIdx].CODIGO);
usu_atend_vals.push(object['REGISTROS'][registrosIdx].ATENDIMENTO);
usu_pagamento_vals.push(object['REGISTROS'][registrosIdx].PAGAMENTO);
usu_vencimento_vals.push(object['REGISTROS'][registrosIdx].VENCIMENTO);
usu_processa_vals.push(object['REGISTROS'][registrosIdx].PROCESSAMENTO);
usu_valor_vals.push(object['REGISTROS'][registrosIdx].VALOR);
usu_nossonum_vals.push(object['REGISTROS'][registrosIdx].NOSSO_NUMERO);
numerodoc_vals.push(object['REGISTROS'][registrosIdx].NUMERO_DOCUMENTO);
usu_codbanco_vals.push(object['REGISTROS'][registrosIdx].CODIGO_BANCO);
usu_linhadig_vals.push(object['REGISTROS'][registrosIdx].LINHA_DIGITAVEL);
usu_codbarras_vals.push(object['REGISTROS'][registrosIdx].CODIGO_BARRAS);
registrosIdx += 1;
}
promiseChain = promiseChain
.then(function() {
return insertRegistros({
usu_codigo_vals: usu_codigo_vals,
usu_atend_vals: usu_atend_vals,
usu_pagamento_vals: usu_pagamento_vals,
usu_vencimento_vals: usu_vencimento_vals,
usu_processa_vals: usu_processa_vals,
usu_valor_vals: usu_valor_vals,
usu_nossonum_vals: usu_nossonum_vals,
numerodoc_vals: numerodoc_vals,
usu_codbanco_vals: usu_codbanco_vals,
usu_linhadig_vals: usu_linhadig_vals,
usu_codbarras_vals: usu_codbarras_vals,
commit: outerIndex + 1 === loops
});
});
})();
}
return promiseChain;
})
.catch(function(err) {
console.log(err);
console.log('Encountered error, rolling back transaction');
return conn.rollback()
.then(function() {
console.log('Transaction rolled back');
})
.catch(function(err) {
console.log('Error rolling back', err);
});
})
.then(function() {
return conn.close();
})
.then(function() {
console.log('Connection closed');
})
.catch(function(err) {
console.log(err);
});
}
Как видите, код становится немного сложнее. Однако это должно упроститься в будущем, когда драйвер добавляет поддержку связывания массивов записей, а не только строк или чисел.
Вы можете прочитать больше об этом и об обходе, который я использую в моем блоге здесь: https://jsao.io/2017/01/plsql-record-types-and-the-node-js-driver/
for
Цикл выполняется практически сразу, тогда как работа с базой данных асинхронная и медленная. for
Цикл должен быть завершен во время выполнения обратного вызова асинхронной операции, и поэтому он будет видеть значения только во время последней итерации цикла.
Чтобы избежать этого, вы можете объявлять переменные с помощью ES6 const
который создаст переменную области блока. Измените свой код на это, и оно должно работать:
module.exports = function(object, callback) {
var oracledb = require('oracledb');
for (const prop in object['REGISTROS']) {
const codigo = object['REGISTROS'][prop]['CODIGO'];
const atendimento = object['REGISTROS'][prop]['ATENDIMENTO'];
const pagamento = object['REGISTROS'][prop]['PAGAMENTO'];
const vencimento = object['REGISTROS'][prop]['VENCIMENTO'];
const processamento = object['REGISTROS'][prop]['PROCESSAMENTO'];
const valor = parseInt(object['REGISTROS'][prop]['VALOR']);
const nossoNumero = object['REGISTROS'][prop]['NOSSO_NUMERO'];
const numeroDocumento = object['REGISTROS'][prop]['NUMERO_DOCUMENTO'];
const codigoBanco = object['REGISTROS'][prop]['CODIGO_BANCO'];
const linhaDigitavel = object['REGISTROS'][prop]['LINHA_DIGITAVEL'];
const codigoBarras = object['REGISTROS'][prop]['CODIGO_BARRAS'];
oracledb.getConnection({
user: "x",
password: "xxx",
connectString: "mycon/string"
},
function(err, connection) {
if (err) {
console.error(err.message);
return;
}
connection.execute(
"INSERT INTO USU_TBOLETO VALUES (:USU_CODIGO, :USU_ATEND, :USU_PAGAMENTO, " +
":USU_VENCIMENTO, :USU_PROCESSA, :USU_VALOR, :USU_NOSSONUM, :NUMERODOC, :USU_CODBANCO, " +
":USU_LINHADIG , :USU_CODBARRAS)",
[codigo, atendimento, pagamento, vencimento, processamento, valor, nossoNumero,
numeroDocumento, codigoBanco, linhaDigitavel, codigoBarras
], {
autoCommit: true
},
function(err, result) {
if (err) {
console.error(err.message);
doRelease(connection);
return;
}
console.log(codigo + ' - ' + atendimento + ' - ' + pagamento + ' - ' + vencimento);
///console.log(result.metaData);
///console.log(result.rows);
doRelease(connection);
});
});
}
function doRelease(connection) {
connection.release(
function(err) {
if (err) {
console.error(err.message);
}
}
);
}
}
Подробнее о цикле и замыканиях:
Javascript печально известный вопрос петли?
Закрытие JavaScript внутри циклов - простой практический пример