版本查询¶
shentongdb提供了两种方式获得版本信息,一种以字符串方式返回,一种以数值方式返回:
- 字符串方式
const shentongdb= require('shentongdb');
console.log("Driver version is " + shentongdb.versionString);
- 以数值方式
const shentongdb= require('shentongdb');
console.log("Driver version number is " + shentongdb.version);
注解
数字表示版本x.y.z时,返回数字为:(10000*x)+(100*y)+z
数据库连接¶
普通连接¶
在不经常使用连接的应用程序中,使用shentongdb.getConnection()创建连接。连接应随连接一起释放。不再需要时关闭()
连接数据库示例:
连接数据库示例:
const shentongdb = require('shentongdb');
async function run() {
let connection;
try {
connection = await shentongdb.getConnection({
user : "sysdba",
password : "szoscar55",
connectString : "localhost:2003/OSRDB"
});
result = await connection.execute("SELECT version");
console.log("Result is:", result);
} catch (err) {
console.error(err.message);
} finally {
if (connection) {
try {
await connection.close(); // 关闭连接
} catch (err) {
console.error(err.message);
}
}
}}
run();
执行结果:
- Result is: { metaData: [ { name: 'VERSION' } ],
- rows:
- [ [ '神通数据库7.0.8.211101 for Windows(x86 64bit) (200 connections) (license invalid after 127 days)' ] ] }
连接池连接¶
经常创建和关闭连接的应用程序应使用连接池。由于池提供了数据库高可用性功能,因此如果您有一个长时间运行的应用程序,特别是在没有数据库工作的情况下释放到池的连接,也建议使用该功能。
示例如下:
const shentongdb = require('shentongdb');
async function run() {
let pool;
try {
pool = await shentongdb.createPool({
user : "sysdba",
password : "szoscar55",
connectString : "localhost:2003/OSRDB",
poolAlias: 'hrpool',
poolIncrement : 1,
poolMax : 1000,
poolMin : 2
});
let connection;
try {
connection = await pool.getConnection();
result = await connection.execute("SELECT version");
console.log("Result is:", result);
} catch (err) {
throw (err);
} finally {
if (connection) {
try {
await connection.close(); // 将连接返回池中
} catch (err) {
throw (err);
}
}
}
} catch (err) {
console.error(err.message);
} finally {
pool.close(0);
}}
run();
详细用例参考examplesconnectionpool.js用例
连接池的设置¶
如果打开了四个以上的连接,例如通过增加poolMax,则应增加node-shentongdb可用的工作线程数。线程池太小可能会导致连接请求失败,并出现错误NJS-040:连接请求超时或NZS-076:连接请求被拒绝。
线程池大小应等于或大于最大连接数。如果应用程序同时执行数据库和非数据库工作,则还可能需要额外的线程以获得最佳吞吐量。
在启动Node.js之前,通过设置环境变量UV_THREADPOOL_size来增加线程池大小。例如,在Linux上,您的软件包。json可以有如下脚本:
"scripts": {
"start": "export UV_THREADPOOL_SIZE=10 && node index.js"
},
. . .
windows下:
"scripts": {
"start": "SET UV_THREADPOOL_SIZE=10 && node index.js"
},
. . .
安全连接¶
node-shentongdb依赖aci驱动,aci驱动支持了ssl安全通信,且可以通过连接字符串设置,因此node-shentongdb也可以在连接字符串设置ssl通信相关配置,多个参数之间用“;”间隔,比如:
let connection;
connection = await shentongdb.getConnection({
user : "SYSDBA",
password : "szoscar55",
connectString : "localhost:2003/OSRDB?ssl_wallet_enable=1;ssl_wallet_path=/opt/wallet/;ssl_wallet_pwd=szoscar55$"
});
详细可以设置的参数,可参考 《ACI (C/C++)程序员开发手册-->ACI编程概述-->参数配置文件-->参数说明》 中的参数及参数功能描述。
事务控制¶
默认情况下,事务是手动提交模式,不会提交INSERT、UPDATE和DELETE等DML语句。
node-shentongdb模块实现connection.commit()和connection.rollback()方法,可用于显式控制事务。
如果autoCommit属性设置为true,则在每次execute()或executeMany()调用结束时都会发生提交。与显式提交()不同,这不需要往返数据库。为了获得最大效率,请将事务的最后一次execute()或executeMany()调用的autoCommit设置为true,而不是使用额外的显式commit()调用。
当connection.executeMany()与batchErrors标志一起使用,如果存在数据错误,将忽略自动提交。
释放连接后,任何正在进行的事务都将回滚。
当应用程序结束时,连接上任何未提交的事务都将回滚。
注意:DDL语句不受事务控制。
语句执行¶
可以使用Connection execute()方法执行单个SQL或PL/SQL语句。可以使用下面显示的回调样式,或promises,或Async/Await。
结果可以在单个数组中返回,也可以使用ResultSet批量获取。查询可以选择使用queryStream()方法进行流式传输。
node-shentongdb的execute()和queryStream()方法使用语句缓存来提高语句的重新执行效率。这样就不需要单独的“prepare”方法来解析语句。
通过调整fetchArraySize和prefetchRows来优化查询性能,请参阅优化提取性能。
连接上的所有数据库调用完成后,应用程序应使调用connection.close()以释放连接。
执行select语句¶
直接获取行¶
默认情况下,查询作为“直接获取”处理,这意味着所有结果都将在回调结果中返回。行属性:
const result = await connection.execute(
"SELECT * from
FROM STUDENTS
WHERE id = :did",
[8],
{ maxRows: 10 } // 最大返回行数
);
console.log(result.rows); // 打印结果集
不返回超出maxRows限制的任何行。如果maxRows为0(默认值),则返回所有行,但受限于Node.js的内存。
为了提高数据库效率,SQL查询应该使用行限制子句,如OFFSET/FETCH或等效子句。maxRows属性可用于阻止编码错误的查询意外返回大量行。
在内部,从数据库中批量提取行以提高性能。内部批大小基于fetchArraySize和maxRows中的较小者。还可以调整行预取以进行调优,请参阅调优提取性能。每个内部获取的批处理都连接到数组中,并最终返回给应用程序。
对于预期返回少量行的查询,通过node-shentongdb减少fetchArraySize值以减少内部内存开销。
对于直接获取,JavaScript内存在两种情况下可能成为限制:
- 返回的绝对数据量太大,JavaScript无法保存在单个数组中。
- 由于从数据库中提取的记录的连续缓冲区的串联,JavaScript堆可能会被超出,或者变得支离破碎。要将此最小化,请使用由调优确定的fetchArraySize和prefetchRows值。
使用示例:
const shentongdb = require('shentongdb');
async function run() {
let connection;
try {
connection = await shentongdb.getConnection({
user : "sysdba",
password : "szoscar55",
connectString : "localhost:2003/OSRDB"
});
result = await connection.execute(
"SELECT * from students where id = :id",
[8],
{ maxRows: 10 }
);
console.log("Result is:", result);
} catch (err) {
console.error(err.message);
} finally {
if (connection) {
try {
await connection.close(); // 关闭连接
} catch (err) {
console.error(err.message);
}
}
}}
run();
从结果集中获取行¶
当查询行数相对较大或无法预测时,建议使用带有回调的结果集,如本节所述,或通过查询流,如后所述。这可以防止查询结果意外地被maxRows限制截断或超过Node.js内存限制。否则,对于返回已知少量行的查询,非结果集查询的开销可能较小。
当execute()选项属性ResultSet为true时,将创建一个ResultSet。可以使用execute()回调函数的结果上的getRow()或getRows()获取结果集行。resultSet属性。
对于结果集,将忽略maxRows限制。可以提取所有行。
当所有行都已提取,或者应用程序不想继续获取更多行时,应使用close()释放结果集。在不从结果集提取行的情况下,还应显式关闭结果集。
REFCURSORS 通过shentongdb.CURSOR OUT 绑定返回结果集。
每行的格式将是数组或对象,具体取决于outFormat的值。
- 一次提取一行,请使用getRow():
const result = await connection.execute(
`SELECT employee_id, last_name
FROM employees
WHERE ROWNUM < 5
ORDER BY employee_id`,
[], // 无绑定变量
{
resultSet: true // 返回一个结果集
});
const rs = result.resultSet;
let row;
let i = 1;
while ((row = await rs.getRow())) {
console.log("getRow(): row " + i++);
console.log(row);}
await rs.close(); // 需要关闭结果集
详细用例参考examplesquery-getrow.js用例
- 一次提取多行,使用getRows():
const numRows = 10;
const result = await connection.execute(
`SELECT employee_id, last_name
FROM employees
WHERE ROWNUM < 25
ORDER BY employee_id`,
[],
{
resultSet: true
});
const rs = result.resultSet;
let rows;
do {
rows = await rs.getRows(numRows);
if (rows.length > 0) {
console.log("getRows(): Got " + rows.length + " rows");
console.log(rows);
}} while (rows.length === numRows);
await rs.close();
详细用例参考examplesquery-getrows.js用例
流查询¶
用connection.queryStream()创建流从顶级查询并侦听事件。您也可以调用connection.execute()并使用connection.toQueryStream()从返回的结果集、外绑定引用游标结果集或隐式结果集返回流。
通过流式传输,每一行作为数据事件返回。查询元数据可通过元数据事件获得。结束事件表示查询结果的结束。收到结束事件后,应调用Stream destroy()函数以正确清理资源。任何进一步的提取逻辑结束,特别是连接释放,都应该处于关闭事件中。
查询结果应完整获取以避免资源泄漏,或者可以使用Stream destroy()函数提前终止流。提取时,连接必须保持打开状态,直到完全读取流并接收到关闭事件。还应首先处理任何返回的Lob对象。
查询流实现是ResultSet类的包装器。特别是,对getRow()的连续调用是在内部进行的。每行将生成一个数据事件。要进行调整,请调整连接的值。querystream()选项fetchArraySize和prefetchRows,请参阅调整提取性能。
流式查询结果的一个示例是:
const stream = connection.queryStream(`SELECT employees_name FROM employees`);
stream.on('error', function (error) {
// handle any error...});
stream.on('data', function (data) {
// handle data row...});
stream.on('end', function () {
// all data has been fetched...
stream.destroy(); // the stream should be closed when it has been finished});
stream.on('close', function () {
// can now close connection... (Note: do not close connections on 'end')});
stream.on('metadata', function (metadata) {
// access metadata of query});
// listen to any other standard stream events...
详细用例参考examplesselectstream.js用例
查询输出格式¶
根据outFormat的值,查询行可以作为列值数组或JavaScript对象返回。
每行的默认格式是列值数组。例如:
const result = await connection.execute(
"SELECT department_id, department_name
FROM departments
WHERE manager_id < :id",
[110] // bind value for :id
);
console.log(result.rows);
如果使用的示例HR模式运行,则输出为:
[ [ 60, 'IT' ], [ 90, 'Executive' ], [ 100, 'Finance' ] ]
详细用例参考examples详细可以参考用例:select1.js用例
为了提高效率,建议使用此格式。或者行可以作为JavaScript对象获取。为此,请将outFormat选项指定为shentongdb..OUT_FORMAT_OBJECT:
shentongdb.outFormat = shentongdb.OUT_FORMAT_OBJECT;
该值也可以设置为execute()选项:
const result = await connection.execute(
`SELECT department_id, department_name
FROM departments
WHERE manager_id < :id`,
[110], // bind value for :id
{ outFormat: shentongdb.OUT_FORMAT_OBJECT });
console.log(result.rows);
输出:
- [ { DEPARTMENT_ID: 60, DEPARTMENT_NAME: 'IT' },
- { DEPARTMENT_ID: 90, DEPARTMENT_NAME: 'Executive' }, { DEPARTMENT_ID: 100, DEPARTMENT_NAME: 'Finance' } ]
详细用例参考examples详细可以参考用例:select2.js用例
在前面的示例中,每一行都是一个JavaScript对象,指定列名及其各自的值。请注意,属性名称遵循数据库的标准名称大小写规则。它们通常是大写的,因为大多数应用程序使用不带引号、不区分大小写的名称创建表。
查询列元数据¶
查询的列名在查询执行在execute()回调的的结果集属性中:
const result = await connection.execute(
`SELECT department_id, department_name
FROM departments
WHERE manager_id < :id`,
[110] // bind value for :id
);
console.dir(result.metaData, { depth: null }); // 显示元数据
使用ResultSet时,在result.ResultSet.metadata中也可以使用元数据。对于使用queryStream()的查询,元数据可通过元数据事件获得。
元数据是一个对象数组,每列一个。默认情况下,每个对象都有一个名称属性:
[ { name: 'DEPARTMENT_ID' }, { name: 'DEPARTMENT_NAME' } ]
这些名字是大写的。这是使用不带引号、不区分大小写的列名创建数据库表时Oracle客户端程序的默认大小写行为。
- 扩展元数据
更多的元数据包含在shentongdb.extendedMetaData中或在connection.execute() 选项为extendedMetaData 为true是返回:
const result = await connection.execute(
`SELECT department_id, department_name
FROM departments
WHERE manager_id < :id`,
[110], // bind value for :id
{ extendedMetaData: true });
console.dir(result.metaData, { depth: null }); // 显示扩展元数据
详细用例参考examplesmetadata.js用例
查询结果类型映射¶
数据库的number, date, character, ROWID, binary、varbinary 列类型被映射为Numbers, Dates, Strings, 或Buffers. BLOBs 和CLOBs被映射为Lobs。
可以使用fetchAsBuffer或fetchAsString更改某些类型的默认映射。fetchInfo属性还可用于更改单个列的默认映射或覆盖全局映射。
SELECT语句中不受支持的数据类型会给出错误NJS-010:SELECT列表中不受欢迎的数据类型。其中包括INTERVAL、BFILE类型。
字符串获取
数据库中列类型为CHAR, VARCHAR2,and NVARCHAR时返回JavaScript 的strings类型.
数值获取
默认情况下,所有数字列都映射到JavaScript数字。Node.js使用双浮点数作为其本机数字类型。
当从数据库中提取数字时,转换为JavaScript不太精确的二进制数字格式可能会导致“意外”的表示。例如:
const result = await connection.execute(`SELECT 38.73 FROM dual`);
console.log(result.rows[0]); // 38.730000000000004
Node.js也只能表示2^53以下的数字,即9007199254740992。大于2^53的数字将被截断。
数字处理的主要建议是使用数据库的SQL或PL/SQL进行数学运算,尤其是货币计算。
时间获取
默认情况下,date 和timestamp列映射到JavaScript日期对象。在内部,使用会话时区将日期、时间戳、带本地时区的时间戳和带时区的时间戳记列提取为带本地时区时间戳。不支持interval类型。
请注意,JavaScript日期具有毫秒精度,因此时间戳在获取时将丢失任何亚毫秒小数部分。
为了使应用程序更具可移植性,建议始终将会话时区设置为预先确定的值,如UTC。会话时区通常应与客户端系统时区匹配,例如TZ环境变量或Windows时区区域。
如果此变量在应用程序中设置,则必须在建立第一个连接之前设置:
process.env.ORA_SDTZ = 'UTC';
const shentongdb = require('shentongdb');
const connection = await shentongdb.getConnection(. . . );
字符串获取其他类型
全局fetchAsString属性可用于强制应用程序查询的所有数字或日期列(以及CLOB列)作为字符串而不是本地格式获取。允许以字符串形式获取数据有助于避免使用JavaScript类型可能导致数字精度损失或不需要日期转换的情况。此方法可用于长度不超过1 GB的CLOB。
例如,要强制将应用程序中查询使用的所有日期和数字提取为字符串:
const shentongdb = require('shentongdb');
shentongdb.fetchAsString = [ shentongdb.DATE, shentongdb.NUMBER ];
对于日期和数字,创建的字符串的最大长度可以是200字节。
单个查询可以使用execute()选项fetchInfo将单个数字或日期列映射到字符串,而不会影响其他列或其他查询。可以覆盖任何全局fetchAsString设置,以允许特定列以本机格式返回数据:
const shentongdb= require('shentongdb');
shentongdb.fetchAsString = [ shentongdb.NUMBER ];
const connection = await shentongdb.getConnection(
{
user : "sysdba",
password : "szoscar55",
connectString : "localhost:2003/OSRDB"
});
const result = await connection.execute(
`SELECT last_name, hire_date, salary, commission_pct FROM employees WHERE employee_id = :id`,
[178],
{
fetchInfo :
{
"HIRE_DATE": { type : shentongdb.STRING },
"COMMISSION_PCT": { type : shentongdb.DEFAULT }
}
});
console.log(result.rows);
输出结果:
[ [ 'Grant', '24-MAY-07', '7000', 0.15 ] ]
大对象获取¶
默认情况下,BLOB、CLOB和NCLOB列将被提取到Lob实例中。对于长度小于1 GB的LOB,通过使用全局fetchAsBuffer或fetchAsString设置或每列fetchInfo设置将其直接提取到缓冲区或字符串中会更高效、更方便。
请参见“大象操作”一节。
二进制获取
查询将返回数据到Node.js的缓冲区。
查询行数限制或分页¶
可使用神通数据库的limit offset语法限制查询结果:
const myoffset = 0; // do not skip any rows (start at row 1)
const mymaxnumrows = 20; // get 20 rows
const sql = `SELECT last_name
FROM employees limit :n offset m
ORDER BY last_name
OFFSET :offset ROWS FETCH NEXT :maxnumrows ROWS ONLY`;
const result = await connection.execute(
sql,
{ n: mymaxnumrows , m: myoffset},
{ prefetchRows: mymaxnumrows + 1, fetchArraySize: mymaxnumrows });
也可以使用兼容Oracle的ROWNUM列实现:
SELECT *
FROM (SELECT a.*, ROWNUM AS rnum
FROM (YOUR_QUERY_GOES_HERE
) a
WHERE ROWNUM <= MAX_ROW)
WHERE rnum >= MIN_ROW
自动增长列¶
神通数据库支持兼容msyql的auto increment选项,建表是可以设置;也可以通过创序列,并将序列作为列的default值。
获取last insert id的方法如下:
. . .const result = await connection.execute(
`INSERT INTO mytable (mydata) VALUES ('Hello') RETURN myid INTO :id`,
{id : {type: shentongdb.NUMBER, dir: shentongdb.BIND_OUT } }
);
console.log(result.outBinds.id); // 打印最后一次插入的id值
与使用应用程序生成的标识符不同,您可能更喜欢使用ROWIDs,也可以用execute的回调方法获得lastRowid:
readonly String lastRowid
用例如下:
const fs = require('fs');
const shentongdb= require('shentongdb');
const dbConfig = require('./dbconfig.js');
async function run() {
let connection;
try {
connection = await shentongdb.getConnection(dbConfig);
const stmts = [
`DROP TABLE no_lastinsertid`,
`CREATE TABLE no_lastinsertid (id int auto_increment primary key, name VARCHAR2(40))`,
];
for (const s of stmts) {
try {
await connection.execute(s);
} catch (e) {
if (e.errorNum != 942)
console.error(e);
}
}
const sql = `INSERT INTO no_lastinsertid(name) VALUES (:name)`;
const result = await connection.execute(
sql,
{
name: "Chris"
},
{ autoCommit: true }
);
console.log("The ROWID is", result.lastRowid);
} catch (err) {
console.error(err);
} finally {
if (connection) {
try {
await connection.close();
} catch (err) {
console.error(err);
}
}
}
}
run();
详细用例参考exampleslastinsertid.js用例
注解
值得注意的是,神通数据库的lastRowid属性返回的是具有auto_increment自动增长列的最后一次数据插入值。
游标管理¶
游标是“特定于会话的私有SQL区域的句柄,该区域保存已解析的SQL语句和其他处理信息”。如果您的应用程序返回错误ORA-1000:超过最大打开游标,则可能的解决方案如下:
避免一次打开太多未完成处理的语句:
- 确保应用程序按照预期的顺序处理连接和语句。
- 在释放连接之前关闭结果集。
- 如果使用DBMS_SQL打开游标。在PL/SQL块中的OPEN_CURSOR(),在块返回之前将其关闭-将REF CURSOR传递回node-shentongdb的情况除外。
选择适当的语句缓存大小。node-shentongdb每个连接都有一个语句缓存。当node-shentongdb在内部发布一条语句时,它将被放入该连接的语句缓存中,其游标将保持打开状态。这使得语句重新执行非常有效。
缓存大小可通过shentongdb.stmtCacheSize属性。您选择的大小取决于您对语句的位置以及应用程序可用资源的了解。是否重新执行语句?当它们被执行时,它们还会在缓存中吗?要缓存多少条语句?在很少的情况下,如果语句没有重新执行,或者可能不在缓存中,您甚至可能希望禁用缓存以消除其管理开销。
错误调整语句缓存大小将降低应用程序效率。
使用绑定变量,否则语句的每个变体都有自己的语句缓存条目和游标。通过适当的绑定,只需要一个条目和游标。
PL/SQL执行¶
可以使用execute()从node-shentongdb中调用PL/SQL存储过程、函数和匿名块。
执行存储过程¶
存储过程定义如下:
CREATE OR REPLACE PROCEDURE myproc (id IN NUMBER, name OUT VARCHAR2, salary OUT NUMBER) AS
BEGIN
SELECT last_name, salary INTO name, salary FROM employees WHERE employee_id = id;
END;
调用方式:
const result = await connection.execute(
`BEGIN
myproc(:id, :name, :salary);
END;`,
{
id: 159,
name: { dir: shentongdb.BIND_OUT, type: shentongdb.STRING, maxSize: 40 },
salary: { dir: shentongdb.BIND_OUT, type: shentongdb.NUMBER }
}
);
console.log(result.outBinds);
输出结果:
{ name: 'Smith', salary: 8000 }
输入输出和输出参数需要绑定。强烈建议使用IN参数。有关准备好的语句,请参见绑定参数。
执行函数¶
函数定义如下:
CREATE OR REPLACE FUNCTION myfunc RETURN VARCHAR2 AS
BEGIN
RETURN 'Hello';
END;
可以通过使用函数返回值的外绑定变量调用:
const result = await connection.execute(
`BEGIN
:ret := myfunc();
END;`,
{
ret: { dir: shentongdb.BIND_OUT, type: shentongdb.STRING, maxSize: 40 }
});
console.log(result.outBinds);
输出结果:
{ ret: 'Hello' }
执行匿名块¶
匿名PL/SQL块可以从node-shentongdb调用,如:
const result = await connection.execute(
`BEGIN
SELECT last_name INTO :name FROM employees WHERE employee_id = :id;
END;`,
{ // bind variables
id: 134,
name: { dir: shentongdb.BIND_OUT, type: shentongdb.STRING, maxSize: 40 },
});
console.log(result.outBinds);
输出结果:
{ name: 'Rogers' }
隐式结果集¶
隐式结果允许将PL/SQL中的查询返回Node.js,而不需要引用游标或绑定变量。
PL/SQL代码使用DBMS_SQL。RETURN_RESULT()返回查询结果。这些可以在execute()回调隐式结果属性中访问。
举例如下:
const plsql = `
DECLARE
c1 SYS_REFCURSOR;
c2 SYS_REFCURSOR;
BEGIN
OPEN c1 FOR SELECT city, postal_code
FROM locations
WHERE location_id < 1200;
DBMS_SQL.RETURN_RESULT(c1);
OPEN C2 FOR SELECT job_id, employee_id, last_name
FROM employees
WHERE employee_id < 103;
DBMS_SQL.RETURN_RESULT(c2);
END;`;
result = await connection.execute(plsql);
console.log(result.implicitResults);
对于较大的查询结果,建议提取结果集:
result = await connection.execute(plsql, [], { resultSet: true });
for (const i = 0; i < result.implicitResults.length; i++) {
console.log(" Implicit Result Set", i + 1);
const rs = result.implicitResults[i]; // get the next ResultSet
let row;
while ((row = await rs.getRow())) {
console.log(" ", row);
}
console.log();
await rs.close();
}
输出结果:
- Implicit Result Set 1
- [ 'Roma', '00989' ] [ 'Venice', '10934' ]
- Implicit Result Set 2
- [ 'AD_PRES', 100, 'King' ] [ 'AD_VP', 101, 'Kochhar' ] [ 'AD_VP', 102, 'De Haan' ]
详细用例参考examplesimpres.js用例
创建存储过程或函数¶
在node-shentongdb中创建PL/SQL过程和函数时,必须手动检查编译警告。这可以通过查询USER_ERRORS来完成,如:
await connection.execute(
`CREATE OR REPLACE PROCEDURE badproc AS
BEGIN
INVALID
END;`);
const r = await connection.execute(
`SELECT line, position, text
FROM user_errors
WHERE name = 'BADPROC' AND type = 'PROCEDURE'
ORDER BY name, type, line, position`,
[], { outFormat: shentongdb.OUT_FORMAT_OBJECT }
);
if (r.rows.length) {
console.error(r.rows[0].TEXT);
console.error('at line', r.rows[0].LINE, 'position', r.rows[0].POSITION);
}
参数绑定¶
SQL和PL/SQL语句可能包含绑定参数,由冒号前缀标识符或数字表示。这些值指示在执行语句时在语句中替换单独指定的值的位置,或在执行后返回值的位置。
IN绑定是传递到数据库中的值。Out绑定用于检索数据。In out绑定传递并在语句执行后返回不同的值。
建议使用绑定参数,而不是通过字符串连接或模板文字构造SQL或PL/SQL语句。这是为了性能和安全。
绑定的插入数据与语句文本分开传递到数据库。它永远不能直接执行。这意味着不需要转义插入数据库的绑定数据。
如果使用不同的绑定参数值多次执行一条语句,则数据库可以重新使用初始执行时的上下文,从而提高性能。但是,如果类似的语句包含硬编码值而不是绑定参数,数据库会发现语句文本不同,效率也会降低。
绑定参数可用于替换数据值。它们不能用于动态构造语句中列名或表名的直接替换,请参阅查询中的绑定列名和表名。
绑定变量不能在DDL语句中使用,例如CREATE TABLE或ALTER命令。
可以绑定值集以在connection.executeMany()使用,请参批量执行章节。
注解
不能将对象名称作为绑定对象,比如表名和列名
IN参数绑定¶
对于IN绑定,在执行SQL或PL/SQL期间,将数据值传递到数据库并替换到语句中。
用名称绑定¶
要绑定数据值,execute()的bindParams参数应包含具有dir、val和type属性的绑定变量对象。每个绑定变量对象名称必须与语句的绑定参数名称匹配:
const shentongdb = require('shentongdb');
const result = await connection.execute(
`INSERT INTO countries VALUES (:country_id, :country_name)`,
{
country_id: { dir: shentongdb.BIND_IN, val: 90, type: shentongdb.NUMBER },
country_name: { dir: shentongdb.BIND_IN, val: "Tonga", type: shentongdb.STRING }
});
console.log("Rows inserted " + result.rowsAffected);
方向dir是shentongdb.BIND_IN,这是未指定dir时的默认值。
val属性可以是常量或JavaScript变量。
如果省略tpye,则从绑定数据值派生该类型。如果已设置,则它可以是类型表中的值之一。典型的类型是shentongdb.STRING,shentongdb.NUMBER,shentongdb.DATE或shentongdb.BUFFER与标准节点匹配的缓冲区。传递到数据库中的数据类型。使用shentongdb的绑定类型shentongdb.BLOB或shentongdb.CLOB传递Lob实例。
由于dir和type都有默认值,因此在绑定中有时会忽略这些属性。绑定可以类似于:
const result = await connection.execute(
`INSERT INTO countries VALUES (:country_id, :country_name)`,
{country_id: 90, country_name: "Tonga"});
console.log("Rows inserted " + result.rowsAffected);
用位置绑定¶
数据也可以在数组中,而不是使用命名绑定参数。在本例中,绑定参数:country_id?和:country_name值如下:
const result = await connection.execute(
`INSERT INTO countries VALUES (:country_id, :country_name)`,
[90, "Tonga"]);
数组值的位置与SQL绑定参数在语句中出现时的位置相对应,而与它们的名称无关。即使绑定参数的名称为:0、:1等,这仍然是正确的。以下代码段将失败,因为国家名称需要是数组的第二个条目,因此它将成为INSERT语句中的第二值
const result = await connection.execute(
`INSERT INTO countries (country_id, country_name) VALUES (:1, :0)`,
["Tonga", 90] // fail);
在SQL语句的上下文中,输入数组位置“n”表示语句第n个位置的bind参数。然而,在PL/SQL语句的上下文中,当从左到右扫描时,绑定调用中的位置“n”表示语句中第n个唯一参数名的绑定。
如果绑定参数名称在SQL字符串中重复,则应使用“按名称绑定”语法。
OUT和IN OUT参数绑定¶
OUT绑定用于从数据库检索数据。IN-OUT绑定被传递到数据库中,并且在语句执行后可能返回不同的值。IN-OUT绑定可以用于PL/SQL调用,但不能用于SQL。
对于bindParams中的每个OUT和IN-OUT-bind参数,使用包含dir、val、type和maxSize属性的绑定变量对象:
dir属性应该是shentongdb.BIND_OUT或shentongdb.BIND_INOUT,这取决于数据是仅从数据库返回还是另外传入数据库。
当绑定in和OUT将值传递到数据库时,需要val参数in。它不用于外绑定。
type属性可以是类型表中讨论的常量之一。这决定了数据库类型和JavaScript类型之间的映射。
应该为OUT绑定设置该属性。如果未指定类型,则为shentongdb.STRING为默认类型。
对于IN-OUT绑定,可以从输入数据值类型推断类型。但是,建议显式设置类型,因为如果输入数据为null,则无法确定正确的值。输出数据类型将始终与输入数据类型相同。
应为String和Buffer OUT或IN-OUT绑定设置maxSize属性。这是bind参数将返回的最大字节数。如果输出值不适合maxSize字节,则会出现错误。
如果未为字符串或缓冲区中返回的外绑定提供maxSize,则使用默认值200字节。
在node-shentongdb中,表示UROWID的字符串的长度可能高达5267字节。
请注意,在PL/SQL块返回之前,所有OUT绑定都应显式设置为一个值。这包括将被忽略的绑定变量。将简单变量设置为NULL。将REF CURSORS设置为空结果集。
获取OUT绑定值
execute()回调的results参数包含一个带有返回的OUT和IN-OUT绑定值的outBinds属性。
给定PL/SQL过程TESTPROC的创建:
CREATE OR REPLACE PROCEDURE testproc (p_in IN VARCHAR2, p_inout IN OUT VARCHAR2, p_out OUT NUMBER)ASBEGIN
p_inout := p_in || p_inout;
p_out := 101;
END;
/
可以通过以下方式调用过程TESTPROC:
const bindVars = {
i: 'Chris', // default direction is BIND_IN. Data type is inferred from the data
io: { val: 'Jones', dir: shentongdb.BIND_INOUT },
o: { dir: shentongdb.BIND_OUT, type: shentongdb.NUMBER },};
const result = await connection.execute(
`BEGIN testproc(:i, :io, :o); END;`,
bindVars);
console.log(result.outBinds);
由于bindParams是作为对象传递的,因此outBinds属性也是一个对象。Node.js输出为:
{ io: 'ChrisJones', o: 101 }
PL/SQL允许在过程和函数调用中使用命名参数。这可以在execute()中使用,如:
`BEGIN testproc(p_in => :i, p_inout => :io, p_out => :o); END;`,
Returning 绑定值¶
“DMLRETURNING”语句,例如INSERT INTO tab VALUES (:1) RETURNING ROWID INTO :2 是一种从DML语句返回有关行更改的信息的方法。例如,可以使用DML返回来获取新插入行的行ID。另一个常见的用例是返回自动递增的列值。
对于影响单行的语句,您可能更喜欢使用lastRowid。
DML返回语句的绑定参数可以使用shentongdb.BLOB,shentongdb.CLOB,shentongdb.STRING,shentongdb.NUMBER或shentongdb.DATE的 BIND_OUT类型。
数据库DATE, TIMESTAMP, TIMESTAMP WITH LOCAL TIME ZONE and TIMESTAMP WITH TIME ZONE可以绑定为shentongdb.DATE 。这些类型也可以绑定为shentongdb.STRING,如果需要。要返回的ROWID和UROWID数据可以绑定为shentongdb.STRING。请注意,表示UROWID的字符串的长度可能高达5267字节。
对于字符串和缓冲区类型,如果maxSize不够大,无法保存返回值,则会发生错误。
请注意,每个返回绑定输出参数的DML都作为包含零个或多个元素的数组返回。如果确认返回的数组长度不大于1,则设计为仅期望一个值的应用程序代码可以变得更健壮。这将有助于识别导致返回更多结果的无效数据或错误WHERE子句。
具有RETURNING子句的DML语句中不允许重复绑定(在语句中多次使用相同的绑定名称),DML部分和语句返回部分中的绑定参数之间不允许重复。
一个示例如下:
const result = await connection.execute(
`UPDATE mytab SET name = :name
WHERE id = :id
RETURNING id, ROWID INTO :ids, :rids`,
{
id: 1001,
name: "Krishna",
ids: { type: shentongdb.NUMBER, dir: shentongdb.BIND_OUT },
rids: { type: shentongdb.STRING, dir: shentongdb.BIND_OUT }
});
console.log(result.outBinds);
Ref Cursor绑定值¶
数据库的REF CURSORS可以用shentongdb的shentongdb.CURSOR来进行绑定,对于OUT绑定,生成的绑定变量将成为ResultSet,允许使用getRow()或getRows()获取行。还可以使用toQueryStream()将结果集转换为可读流。隐式结果是从PL/SQL返回查询结果的另一种方法。
如果使用getRow()或getRows(),则当所有行都已提取或应用程序不想继续获取更多行时,必须使用close()释放结果集。如果REF游标设置为NULL或未在PL/SQL过程中设置,则返回的ResultSet无效,并且调用getRows()等方法时将返回错误。
给定定义如下的PL/SQL过程:
CREATE OR REPLACE PROCEDURE get_emp_rs (
p_sal IN NUMBER,
p_recordset OUT SYS_REFCURSOR) ASBEGIN
OPEN p_recordset FOR
SELECT first_name, salary, hire_date
FROM employees
WHERE salary > p_sal;
END;
/
操作REF CURSORS方式如下:
const result = await connection.execute(
`"BEGIN get_emp_rs(:sal, :cursor); END;`,
{
sal: 6000,
cursor: { type: shentongdb.CURSOR, dir: shentongdb.BIND_OUT }
},
{
prefetchRows: 1000, // tune the internal getRow() data fetch performance
fetchArraySize: 1000
});
const resultSet = result.outBinds.cursor;
let row;
while ((row = await resultSet.getRow())) {
console.log(row);
}
await resultSet.close(); // always close the ResultSet
通过调用getRows()不带参数,可以在一次操作中获取所有行。当已知查询返回“少量”行时,这非常有用:
const result = await connection.execute(
`"BEGIN get_emp_rs(:sal, :cursor); END;`,
{
sal: 6000,
cursor: { type: shentongdb.CURSOR, dir: shentongdb.BIND_OUT }
},
{
prefetchRows: 200, // tune the getRows() call
fetchArraySize: 200
});
const resultSet = result.outBinds.cursor;
const rows = await resultSet.getRows();
console.log(rows);
await resultSet.close(); // always close the ResultSet
prefetchRows和[fetchArraySize](#PropDBFetchArarySize)可用于调优getRows()调用。必须在获取结果集之前或之后设置这些值。
要将引用游标结果集转换为流,请使用toQueryStream():
const result = await connection.execute(
`"BEGIN get_emp_rs(:sal, :cursor); END;`,
{
sal: 6000,
cursor: { type: shentongdb.CURSOR, dir: shentongdb.BIND_OUT }
});
const cursor = result.outBinds.cursor;
const queryStream = cursor.toQueryStream();
const consumeStream = new Promise((resolve, reject) => {
queryStream.on('data', function(row) {
console.log(row);
});
queryStream.on('error', reject);
queryStream.on('close', resolve);});
await consumeStream;
在完全读取流之前,连接必须保持打开状态。查询结果必须提取到完成,以避免资源泄漏。提取所有数据后,将在内部执行流式查询结果的ResultSet close()调用。
如果要用shentongdb.BIND_IN将查询的结果集传递到PL/SQL中。然后将返回结果集的查询的prefetchRows 设置为0。这将停止node-shentongdb以静默方式获取第一行,并且在稍后接收的PL/SQL代码中不可用。例如:
const result = await connection.execute(
`SELECT * FROM locations`,
[],
{
resultSet: true,
prefetchRows: 0 // stop node-shentongdb internally fetching rows from the ResultSet
});
// Pass the ResultSet as a REF CURSOR into PL/SQL
await conn.execute(
`BEGIN myproc(:rc); END;`,
{
rc: { val: result.resultSet, type: shentongdb.CURSOR, dir: shentongdb.BIND_IN }
});
因为默认绑定方向是bind_IN,并且可以从结果推断类型。在resultSet中,PL/SQL过程调用可以简化为:
await conn.execute(`BEGIN myproc(:rc); END;`, [result.resultSet]);
LOB参数绑定¶
数据库CLOB可以使用设置为shentongdb.CLOB的类型进行绑定。数据库BLOB可以绑定为shentongdb.BLOB。这些绑定接受执行Node.js流接口返回Node-shentongdb的 Lob实例。
LOB可以表示数据库的持久LOB(存储在表中的LOB)或临时LOB(例如,使用createLob()创建的LOB或某些SQL和PL/SQL操作返回的LOB)。
LOB可以与shentongdb.BIND_IN/shentongdb.BIND_OUT或shentongdb.BIND_INOUT的方向绑定,具体取决于上下文。
请注意,在PL/SQL代码完成之前,任何PL/SQL OUT LOB参数都应该在PL/SQL块中初始化-甚至只初始化为NULL。确保在所有PL/SQL代码路径(包括错误处理程序)中都这样做。
在许多情况下,使用JavaScript字符串和缓冲区比使用LOB更容易。这些类型可以直接绑定到SQL IN绑定中,以插入或更新LOB列。它们还可以绑定到PL/SQL LOB参数。将绑定类型设置为shentongdb.STRING,BLOB类型设置为shentongdb.BUFFER。这些绑定在输出方向上使用的默认大小是200,因此请适当设置maxSize。
有关绑定和使用LOB的示例和更多信息,请参见使用CLOB和BLOB数据。
限制LOB的长度
当CLOB被绑定为shentongdb.STRING,BLOB被绑定为shentongdb.BUFFER,则其大小限制为1GB。通常,实际限制是Node.js和V8引擎的可用的内存。对于大于数LOB数据,建议绑定为shentongdb.CLOB或shentongdb.BLOB并使用Lob流。
在内部,当为PL/SQL调用绑定大于32KB的字符串和缓冲区时,使用临时LOB。释放临时LOB是自动处理的。对于SQL调用,不使用临时LOB。
Where IN字句中绑定多个值¶
将单个JavaScript值绑定到SQL WHERE IN子句很容易:
sql = `SELECT first_name, last_name FROM employees WHERE first_name IN (:bv)`;
binds = ['Christopher'];
await connection.execute(sql, binds, function(...));
但是SQL WHERE IN子句的一个常见用例是多个值,例如,当web用户选择多个复选框选项时,查询应该匹配所有选择的值。 要在WHERE in bind子句中使用固定的少量值,SQL查询应具有单独的绑定参数,例如:
const sql = `SELECT first_name, last_name FROM employees WHERE first_name IN (:bv1, :bv2, :bv3, :bv4)`;
const binds = ['Alyssa', 'Christopher', 'Hazel', 'Samuel'];
const result = await connection.execute(sql, binds);
如果有时使用较少的项执行查询,则可以为每个“缺失”值绑定null:
const binds = ['Alyssa', 'Christopher', 'Hazel', null];
如果多次重新执行完全相同的语句文本,而不管用户提供的值的数量如何,这将提供性能和扩展优势,因为不需要运行多个唯一的SQL语句。 如果不打算重新执行该语句,或者只有在运行时才知道值的数量,则可以构建SQL语句:
const binds = ['Christopher', 'Hazel', 'Samuel'];
let sql = `SELECT first_name, last_name FROM employees WHERE first_name IN (`;
for (const i = 0; i < binds.length; i++)
sql += (i > 0) ? ", :" + i : ":" + i;
sql += ")";
会生成以下类似语句:
SELECT first_name, last_name FROM employees WHERE first_name IN (:0, :1, :2)
大对象操作¶
数据库使用LOB数据类型存储长对象。CLOB类型用于字符数据,BLOB类型用于二进制数据。在node-shentongdb中,Lob可以由Lob类的实例或字符串和缓冲区表示。
简单大对象插入¶
Node.js字符串或缓冲区类型可以传递到PL/SQL块中,或者通过绑定到LOB列或PL/SQL参数插入到数据库中。
给定表:
CREATE TABLE mylobs (id NUMBER, c CLOB, b BLOB);
插入示例:
const fs = require('fs');
const str = fs.readFileSync('example.txt', 'utf8');
. . .
const result = await connection.execute(
`INSERT INTO mylobs (id, myclobcol) VALUES (:idbv, :cbv)`,
{ idbv: 1, cbv: str } // type and direction are optional for IN binds
);
console.log('CLOB inserted from example.txt');
. . .
更新大对象:
const result = await connection.execute(
`UPDATE mylobs SET myclobcol = :cbv WHERE id = :idbv`,
{ idbv: 1, cbv: str }
);
类似地,可以绑定缓冲区以插入或更新BLOB列。
将lob传入PL/SQL参数
PROCEDURE lobs_in (p_id IN NUMBER, c_in IN CLOB, b_in IN BLOB) . . .
const bigStr = 'My string to insert';
const bigBuf = Buffer.from([. . .]);
const result = await connection.execute(
`BEGIN lobs_in(:id, :c, :b); END;`,
{ id: 20,
c: bigStr, // type and direction are optional for CLOB and BLOB IN binds
b: bigBuf }
}
);
简单大对象查询或OUT绑定¶
查询LOB
使用shentongdb.fetchAsString或shentongdb.fetchAsBuffer(或fetchInfo),从数据库查询的小于1 GB的LOB可以作为字符串或缓冲区返回。如果数据大于1G,则需要从Lob流式传输,如下文“流式Lob”中所述。
例如,要使应用程序查询的每个CLOB都作为字符串返回:
shentongdb.fetchAsString = [ shentongdb.CLOB ];
const result = await connection.execute(`SELECT c FROM mylobs WHERE id = 1`);
if (result.rows.length === 0)
console.error("No results");
else {
const clob = result.rows[0][0];
console.log(clob);
}
可以使用fetchInfo将单个查询中的CLOB列提取为字符串:
const result = await connection.execute(
`SELECT c FROM mylobs WHERE id = 1`,
[], // no binds
{ fetchInfo: {"C": {type: shentongdb.STRING}} }
);
if (result.rows.length === 0) {
console.error("No results");
}
else {
const clob = result.rows[0][0];
console.log(clob);
}
BLOB查询示例非常相似。要强制将应用程序中的每个BLOB作为缓冲区返回,请执行以下操作:
shentongdb.fetchAsBuffer = [ shentongdb.BLOB ];
const result = await connection.execute(`SELECT b FROM mylobs WHERE id = 2`);
if (result.rows.length === 0)
console.error("No results");
else {
const blob = result.rows[0][0];
console.log(blob.toString()); // assuming printable characters
}
可以使用fetchInfo将单个查询中的BLOB列提取为缓冲区:
const result = await connection.execute(
`SELECT b FROM mylobs WHERE id = 2`,
[ ], // no binds
{ fetchInfo: {"B": {type: shentongdb.BUFFER}} }
);
if (result.rows.length === 0) {
console.error("No results");
} else {
const blob = result.rows[0][0];
console.log(blob.toString()); // assuming printable characters
}
PL/SQL中获取LOB
要将PL/SQL LOB OUT参数设置为字符串或缓冲区,请将绑定类型设置为:
- shentongdb.STRING 对应 CLOB
- shentongdb.BUFFER 对应 BLOB
const result = await connection.execute(
`BEGIN lobs_out(:id, :c, :b); END;`,
{ id: 20,
c: {type: shentongdb.STRING, dir: shentongdb.BIND_OUT, maxSize: 50000},
b: {type: shentongdb.BUFFER, dir: shentongdb.BIND_OUT, maxSize: 50000}
}
);
const str = result.outBinds.c; // a String
const buf = result.outBinds.b; // a Buffer
. . . // do something with str and buf
提取的字符串和缓冲区可以直接在Node.js中使用。
大对象流¶
Lob类实现了该Node.js Stream接口提供对CLOB和BLOB数据库列以及PL/SQL绑定参数的流式访问。
LOB可以表示持久LOB(永久存储在数据库中的LOB)或临时LOB(如使用connection.createLob()创建的LOB,或从某些SQL或PL/SQL返回的LOB)。
如果同时对多个LOB进行流式处理,工作线程将在连接上有效地序列化。
应用程序负责确保连接在流操作(如pipe())进行时保持打开。
读取LOB数据
作为流对象,从数据库读取的Lob有两种操作模式:“流动模式”和“暂停模式”。在流动模式下,数据通过管道传输到另一个流,或者在读取数据时发布事件。在暂停模式下,应用程序必须显式调用read()以获取数据。
BLOB的读取(大小)单位为字节,CLOB的读取单位为字符。
当从数据库读取LOB时,在可读流完成时或出现LOB错误时,会自动释放资源。Lob.destroy()方法还可用于关闭尚未流式完成的持久LOB。
可读Lob对象以暂停模式启动。如果添加了数据事件处理程序,或者Lob通过管道传输到可写流,则Lob将切换到流模式。
对于在流动模式下运行的非管道可读Lob,其中Lob通过事件处理程序读取,可以通过调用pause()将Lob对象切换到暂停模式。一旦Lob处于暂停模式,它将停止发送数据事件。
类似地,在暂停模式下运行的可读Lob可以通过调用resume()切换到流动模式。然后它将再次开始发送数据事件。
写入LOB数据
LOB通过pipe()写入。或者可以依次调用write()方法,最后一段由end()方法编写。必须调用end()方法,因为它释放了资源。如果Lob通过管道传输到,则会自动调用write()和end()方法。
可写LOB也有事件,请参见Node.js流文档。
在流式传输到可写Lob的结束时,将发生finish事件。建议在此事件中(或在此事件发生后)放置提交和释放连接等逻辑。请参见lobinsert2.js。
Returning返回LOB¶
如果字符串或缓冲区太大,无法直接插入数据库(请参见简单的Lob插入),请使用RETURNING into子句检索表项的Lob。然后,数据可以流式传输到Lob并直接提交到表:
const result = await connection.execute(
`INSERT INTO mylobs (id, c) VALUES (:id, EMPTY_CLOB()) RETURNING c INTO :lobbv`,
{ id: 4,
lobbv: {type: shentongdb.CLOB, dir: shentongdb.BIND_OUT} },
{ autoCommit: false } // a transaction needs to span the INSERT and pipe());
if (result.rowsAffected != 1 || result.outBinds.lobbv.length != 1) {
throw new Error('Error getting a LOB locator');
}
const doInsert = new Promise((resolve, reject) => {
const lob = result.outBinds.lobbv[0];
lob.on('finish', async () => {
await connection.commit(); // all data is loaded so we can commit it
});
lob.on('error', async (err) => {
await connection.close();
reject(err);
});
const inStream = fs.createReadStream('example.txt'); // open the file to read from
inStream.on('error', (err) => {
reject(err);
});
inStream.pipe(lob); // copies the text to the LOB
});
await doInsert;
此示例将文件流式传输到表中。数据流传输完成后,Lob将自动关闭,并触发关闭事件。此时可以提交数据。
从数据库中获取大对象流¶
默认情况下,当SELECT子句包含LOB列或PL/SQL OUT参数返回LOB时,将创建LOB实例。(这是可以更改的,请参见简单LOB查询和PL/SQL外绑定。)
对于每个Lob实例,Lob.type属性将是shentongdb.BLOB或shentongdb.CLOB,具体取决于列或PL/SQL参数类型。
返回的LOB可以用作可读流。数据可以从每个Lob流式传输,例如传输到文件。流结束时,持久LOB将自动关闭。
从数据库返回的未流化的LOB可以传递回数据库,如PL/SQL块、INSERT或UPDATE语句的绑定。然后应使用lob.destroy()关闭lob。如果它们作为输入-输出绑定传递,它们将自动关闭,并且执行-输出绑定属性将包含更新的Lob。
LOB查询
表:
CREATE TABLE mylobs (id NUMBER, c CLOB, b BLOB);
获取CLOB如下:
const result = await connection.execute(`SELECT c FROM mylobs WHERE id = 1`);
if (result.rows.length === 1) {
const clob = result.rows[0][0]; // Instance of a node-shentongdb Lob
// console.log(clob.type); // -> 2017 aka shentongdb.CLOB
. . . // do something with the Lob}
PL/SQL参数返回LOB
存储过程:
PROCEDURE lobs_out (id IN NUMBER, clob_out OUT CLOB, blob_out OUT BLOB) . . .
获取:
const result = await connection.execute(
`BEGIN lobs_out(:id, :c, :b); END;`,
{ id: 1,
c: {type: shentongdb.CLOB, dir: shentongdb.BIND_OUT},
b: {type: shentongdb.BLOB, dir: shentongdb.BIND_OUT}
}
);
const clob = result.outBinds.c;
const blob = result.outBinds.b;
. . . // do something with the Lobs
流方式返回LOB
从查询或PL/SQL外绑定获得Lob后,可以将其流式输出:
if (lob === null) {
// . . . do special handling such as create an empty file or throw an error
}
if (lob.type === shentongdb.CLOB) {
lob.setEncoding('utf8'); // set the encoding so we get a 'string' not a 'buffer'
}
lob.on('error', function(err) { cb(err); });
lob.on('end', function() { cb(null); }); // all done. The Lob is automatically closed.
const outStream = fs.createWriteStream('myoutput.txt');
outStream.on('error', function(err) { cb(err); });
// switch into flowing mode and push the LOB to myoutput.txt
lob.pipe(outStream);
注意,Lob在流结束时自动关闭。
lob的替代方案。pipe()调用在Lob流上具有一个数据事件,该事件分别处理每个Lob数据块。可以构建字符串或缓冲区,或者如果LOB很大,可以将每个块写入另一个流或文件:
if (lob === null) {
// . . . do special handling such as create an empty file or throw an error
}
let str = "";
lob.setEncoding('utf8'); // set the encoding so we get a 'string' not a 'buffer'lob.on('error', function(err) { cb(err); });
lob.on('end', function() { cb(null); }); // all done. The Lob is automatically closed.
lob.on('data', function(chunk) {
str += chunk; // or use Buffer.concat() for BLOBS
});
lob.on('end', function() {
fs.writeFile(..., str, ...);});
lob分段大小可用于控制为每个可读数据事件检索的字节数。这将设置字节数(对于BLOB)或字符数(对于CLOB)。默认值为lob.chunkSize。建议使用chunkSize的倍数。
创建临时大对象并绑定¶
应用程序可以通过调用connection.createLob()创建数据库“temp LOB”。这些是Lob类的实例。它们可以填充数据并传递给PL/SQL块。如果数据大于直接绑定的可行性(请参见LOB的简单插入),这将非常有用。这些LOB也可以用于绑定中的SQL语句,但是上面显示的返回到方法将更有效。
createLob()中的lob将使用临时表空间中的空间,直到调用lob.destroy()。
将临时大对象传入PL/SQL
下面的插入示例基于lobplsqltemp.js。它创建一个空LOB,填充它,然后将其传递给PL/SQL过程。
可以使用connection.createLob()创建临时LOB:
const templob = await connection.createLob(shentongdb.CLOB);
创建后,可以将数据插入其中。例如,要读取文本文件:
templob.on('error', function(err) { somecallback(err); });
// The data was loaded into the temporary LOB, so use it
templob.on('finish', function() { somecallback(null, templob); });
// copies the text from 'example.txt' to the temporary LOB
const inStream = fs.createReadStream('example.txt');
inStream.on('error', function(err) { . . . });
inStream.pipe(templob);
现在LOB已经填充完毕,可以在somecallback()中将其绑定到PL/SQL in参数:
// For PROCEDURE lobs_in (p_id IN NUMBER, c_in IN CLOB, b_in IN BLOB)
const result = await connection.execute(
`BEGIN lobs_in(:id, :c, null); END;`,
{ id: 3,
c: templob // type and direction are optional for IN binds
}
);
当不再需要临时LOB时,必须使用LOB.destroy()将其关闭:
await templob.destroy();
大对象关闭¶
关闭Lob可以释放资源。特别是,临时LOB使用的临时表空间存储被释放。Lob一旦关闭,就不能再绑定或用于流。
使用createLob()创建的lob应使用lob.destroy()显式关闭。
从数据库返回的持久或临时lob应调用lob.destroy()销毁,除非它们已自动关闭。在以下情况下,将自动关闭返回的LOB:
- 流传输已完成
- 出现流错误
- Lob被用作IN-OUT绑定的源
批量执行¶
connection.executeMany()方法允许将多组数据值绑定到一个DML或PL/SQL语句以执行。这就像呼叫连接。多次执行execute(),但需要较少的往返。这是处理批处理更改的一种有效方法,例如,在执行批量插入时,或在更新多行时。该方法不能用于查询。
executeMany()方法支持大多数数据类型的IN、IN、OUT和OUT绑定,PL/SQL集合关联数组除外。
例如,要在数据库中插入三条记录:
const sql = `INSERT INTO mytab VALUES (:a, :b)`;
const binds = [
{ a: 1, b: "One" },
{ a: 2, b: "Two" },
{ a: 3, b: "Three" }];
const options = {
autoCommit: true,
bindDefs: {
a: { type: shentongdb.NUMBER },
b: { type: shentongdb.STRING, maxSize: 5 }
}};
const result = await connection.executeMany(sql, binds, options);
console.log(result.rowsAffected); // 3
字符串和缓冲区需要bindDefs中的maxSize值。它必须是最长数据值的长度(或更大)。为了提高效率,请将尺寸保持尽可能小。
选项参数是可选的。
如果未设置bindDefs,则假定绑定方向为,绑定数据用于确定绑定变量类型、名称和最大大小。通常建议使用bindDefs,因为它消除了扫描所有记录的开销。
绑定定义bindDefs还可以使用“按位置绑定”语法,请参见下面的示例。
除了使用executeMany(),还可以通过查看模式设计及其触发器、序列、索引、分区和重做生成来优化数据负载。
尝试使用非常大的数据集可能会产生错误DPI-1015:数组大小太大。为了避免这种情况,请使用数据子集重复调用executeMany()。
返回受影响的行¶
执行DML语句时,可以通过获取dmlRowCounts来显示每个输入记录所影响的数据库行数。例如,删除行时:
const sql = `DELETE FROM tab WHERE id = :1`;
const binds = [
[20],
[30],
[40]];
const options = { dmlRowCounts: true };
const result = await connection.executeMany(sql, binds, options);
console.log(result.dmlRowCounts);
如果表最初包含id为20的3行、id为30的5行和id为40的6行,则输出为:
[ 3, 5, 6 ]
获取批量错误¶
对于大型数据集,不要在第一个数据错误时中止处理,而是在以后继续处理并解决错误。
当batchErrors为true时,即使某些记录中存在数据错误,处理仍将继续。未设置executeMany()回调错误参数。相反,包含每个错误的数组将在回调结果参数中返回。将处理所有有效的数据记录,并启动事务,但不提交,即使autoCommit为true。应用程序可以根据需要检查错误、采取操作并显式提交或回滚。
举例:
const sql = `INSERT INTO childtab VALUES (:1, :2, :3)`;
const binds = [
[1016, 10, "Child 2 of Parent A"],
[1017, 10, "Child 3 of Parent A"],
[1018, 20, "Child 4 of Parent B"],
[1018, 20, "Child 4 of Parent B"], // duplicate key
[1019, 30, "Child 3 of Parent C"],
[1020, 40, "Child 4 of Parent D"],
[1021, 75, "Child 1 of Parent F"], // parent does not exist
[1022, 40, "Child 6 of Parent D"]
];
const options = {
autoCommit: true,
batchErrors: true,
bindDefs: [
{ type: shentongdb.NUMBER },
{ type: shentongdb.NUMBER },
{ type: shentongdb.STRING, maxSize: 20 }
]
};
const result = await connection.executeMany(sql, binds, options);
console.log(result.batchErrors);
获取处理Returning¶
可以使用DML返回语法返回值:
const sql = `INSERT INTO tab VALUES (:1) RETURNING ROWID INTO :2`;
const binds = [
["One"],
["Two"],
["Three"]
];
const options = {
bindDefs: [
{ type: shentongdb.STRING, maxSize: 5 },
{ type: shentongdb.STRING, maxSize: 18, dir: shentongdb.BIND_OUT },
]
};
const result = await connection.executeMany(sql, binds, options);
console.log(result.outBinds);
输出:
- [ [ [ 'AAAmI9AAMAAAAnVAAA' ] ],
- [ [ 'AAAmI9AAMAAAAnVAAB' ] ], [ [ 'AAAmI9AAMAAAAnVAAC' ] ] ]
调用PL/SQL¶
executeMany()方法可用于使用不同的输入值多次执行PL/SQL语句。例如,以下PL/SQL过程:
CREATE PROCEDURE testproc (
a_num IN NUMBER,
a_outnum OUT NUMBER,
a_outstr OUT VARCHAR2)ASBEGIN
a_outnum := a_num * 2;
FOR i IN 1..a_num LOOP
a_outstr := a_outstr || 'X';
END LOOP;
END;
/
调用方法如下:
const sql = `BEGIN testproc(:1, :2, :3); END;`;
binds = [
[1],
[2],
[3],
[4]];
const options = {
bindDefs: [
{ type: shentongdb.NUMBER },
{ type: shentongdb.NUMBER, dir: shentongdb.BIND_OUT },
{ type: shentongdb.STRING, dir: shentongdb.BIND_OUT, maxSize: 20 }
]};
const result = await connection.executeMany(sql, binds, options);
console.log(result.outBinds);
输出:
- [ [ 2, 'X' ],
- [ 4, 'XX' ], [ 6, 'XXX' ], [ 8, 'XXXX' ] ]
当没有绑定值或只有外绑定值时,接受多次迭代的executeMany()变体非常有用。此示例八次调用PL/SQL块:
const plsql = `DECLARE
t_id NUMBER;
BEGIN
SELECT NVL(COUNT(*), 0) + 1 INTO t_id FROM testtable;
INSERT INTO testtable VALUES (t_id, 'Test String ' || t_id);
SELECT SUM(id) INTO :1 FROM testtable;
END;`
const options = {
bindDefs: [
{ type : shentongdb.NUMBER, dir : shentongdb.BIND_OUT }
]};
const numIterations = 8;
const result = await connection.executeMany(plsql, numIterations, options);
console.log(result.outBinds);
执行输出:
[ [ 6 ], [ 10 ], [ 15 ], [ 21 ], [ 28 ], [ 36 ], [ 45 ], [ 55 ] ]
注解
神通数据库目前不支持存储过程或匿名块支持批量绑定操作,以上内容执行会失败,后续数据库支持后开放使用。
处理错误¶
每个方法的最后一个参数是回调,除非使用Promises或Async/Await。回调的第一个参数是一个错误对象,如果调用失败,该对象包含错误信息。如果调用成功,则对象为空。
使用Promises或Async/Await时,发生故障时,catch()错误对象将包含错误信息。
如果为属性设置了无效值,则会发生错误。对于只读或只读属性上的无效操作也是如此。如果使用了无法识别的属性名称,则将忽略该名称。
捕捉错误的一个示例是:
async function run(connection) {
try {
const sql = `SELECT * FROM DOESNOTEXIST`;
result = await connection.execute(sql);
return result;
} catch (err) {
console.error(err);
}}
错误属性¶
Error对象包含errorNum、message、offset和stack属性。
errorNum
错误号,对于非数据库错误和前缀为NJS或DPI的消息,此值未定义。
message
详细错误信息,可能有多行。
offset
通常,偏移量是导致数据库错误的SQL文本中的字符偏移量。在非SQL上下文中,该值可以是0。对于非数据库错误和前缀为NJS或DPI的消息,此值未定义。
stack
使用Promises或Async/Await时,错误对象包括堆栈跟踪,例如:
- Error: 00942: table or view does not exist
- at async Object.myDoQuery (/Users/cjones/db.js:5:20) at async run (/Users/cjones/test.js:51:14)}
优化调整¶
一些通用调整如下:
调整应用程序架构。
一般应用程序的目标是减少shentongdb和数据库之间的往返次数。
对于多用户应用程序,使用连接池。在应用程序初始化期间创建一次池。
利用高效的shentongdb功能。例如,要插入多行,请使用connection.executeMany()而不是connection.execute()方法。
调整SQL语句。请参阅SQL调优指南。
使用绑定变量可避免语句重分类。
调整fetchArraySize和预取每个查询的行。
进行简单的优化,如限制行数和避免选择应用程序中未使用的列。
使用简单的标量关系值可能比使用数据库命名对象或集合更快。
充分利用PL/SQL避免从shentongdb执行许多单独的语句。
调整语句缓存。
为小型查找表启用客户端结果缓存。
调整数据库。请参阅《数据库性能调优指南》。
不要不必要地提交或回滚。对DML语句序列的最后一个使用自动提交。
优化结果集获取¶
要优化查询,您可以调整node-shentongdb的内部缓冲区大小,以提高跨网络从数据库中获取行的速度,并优化内存使用。无论使用哪个node-shentongdb函数获取查询结果,在内部,所有行都会从数据库中批量获取,并在返回到应用程序之前进行缓冲。内部缓冲区大小可能会对性能产生重大影响。大小不影响行返回到应用程序的方式或时间。它们不影响查询返回的最小或最大行数。
为了获得最佳性能,请在在connection.execute()或connection.queryStream()调用时使用fetchArraySize调优“数组提取”,并使用prefetchRows调优“行预取”。注意:当使用getRows(numRows)时,其中NumRow大于0,则“数组提取”的调整基于numRows值,而不是fetchArraySize。还请注意,返回LOB和类似类型的查询永远不会预取行,因此在这些情况下会忽略prefetchRows值。
常见的查询调优场景适用于通过慢速网络返回大量行的SELECT语句。增加fetchArraySize可以通过减少到数据库的往返次数来提高性能。但是,增加此值会增加所需的内存量。调整预取行也会影响性能和内存使用。
行预取和数组获取都是内部缓冲技术,用于减少到数据库的往返。不同之处在于进行缓冲的代码层以及缓冲发生的时间。node-shentongdb使用的数据库客户端库有单独的“执行SQL语句”和“获取数据”调用。当从数据库返回成功的语句执行确认时,预取允许将查询结果返回给应用程序。这意味着后续的内部“获取数据”操作并不总是需要往返于数据库,因为行已经缓冲在数据库客户端库中。减少往返有助于提高性能和可扩展性。预取的开销是需要从数据库客户端的预取缓冲区进行额外的数据拷贝。
设置fetchArraySize和prefetchRows
通过在正常应用程序使用的预期负载下对应用程序进行实验,可以找到最佳的fetchArraySize和prefetchRows值。这是因为,在快速网络上获取大量行或非常“宽”的行时,从预取缓冲区进行额外内存拷贝的成本可能超过单node-shentongdb用户的往返成本。但是,在生产应用程序负载下,减少往返可能有助于提高性能和系统的整体可扩展性。数据库往返行程中的文档显示了如何测量往返行程。
以下是一些关于开始调整的起点的建议:
- 要优化返回未知行数的查询,请估计返回的行数并为fetchArraySize选择适当的值。然后将预取行设置为相同的值。例如:
const sql = `SELECT *
FROM very_big_table`;
const binds = [];
const options = { prefetchRows: 1000, fetchArraySize: 1000, resultSet: true };
const result = await connection.execute(sql, binds, options);
默认的fetchArraySize为100,默认的prefetchRows为2。根据性能、内存和往返使用情况的需要调整这些值。使用的大小将影响缓冲区的内存分配,因此不要使大小过大。例如,如果查询总是返回500行以下,请避免将fetchArraySize设置为10000。
在这种情况下,目标是保持fetchArraySize等于prefetchRows。但是,对于快速网络上的大量行或非常“宽”的行,您可能更喜欢将预取行保留为其默认值。
如果要提取固定数量的行,请通过将fetchArraySize设置为预期行数,并将prefetchRows设置为大于此值的一个值来开始调整。添加一个可以消除检查提取结束的往返需要。
例如,如果要查询20行,可能是为了显示一页数据,则将prefetchRows设置为21,并·将fetchArraySize设置为20:
const myoffset = 0; // do not skip any rows (start at row 1)
const mymaxnumrows = 20; // get 20 rows
const sql = `SELECT last_name
FROM employees
ORDER BY last_name
OFFSET :offset ROWS FETCH NEXT :maxnumrows ROWS ONLY`;
const binds = { offset: myoffset, maxnumrows: mymaxnumrows };
const options = { prefetchRows: mymaxnumrows + 1, fetchArraySize: mymaxnumrows };
const result = await connection.execute(sql, binds, options);
这将在一次往返中返回查询的所有行。
- 如果知道查询只返回一行,则将fetchArraySize设置为1以最小化内存使用。默认预取值2允许单行查询的最小往返次数:
const sql = `SELECT last_name
FROM employees
WHERE employee_id = :bv`;
const binds = [100];
const options = { fetchArraySize: 1 };
const result = await connection.execute(sql, binds, options);
通过将预取行设置为0禁用行预取,有两种情况将受益:
- 当查询返回一个结果集,然后将其传递到PL/SQL中。在初始查询期间将prefetchRows设置为0,以便node-shentongdb不会过早地(静默地)获取光标的第一行。这使所有行都可供稍后接收PL/SQL代码的用户使用。请参见参考游标绑定参数。
- 查询使用管道行以间歇间隔发出行的PL/SQL函数时。默认情况下,在node-shentongdb将行返回给应用程序之前,函数需要发出几行。将预取行设置为0有助于为应用程序提供一致的数据流。
优化数据库交互¶
往返定义为从数据库客户端库(由node-shentongdb使用)到数据库的往返。调用node-shentongdb函数或访问每个属性将需要零次或多次往返。除了优化应用程序的体系结构和SQL语句外,一般的性能和可伸缩性目标是最小化往返。
减少往返行程的一些一般提示如下:
- 调整每个查询的fetchArraySize和prefetchRows。
- 使用executeMany()实现最佳DML执行。
- 仅在必要时提交。对事务的最后一条语句使用自动提交。
- 对于连接池,使用回调设置连接状态,请参见连接标记和会话状态
- 使用PL/SQL过程执行多个SQL语句,而不是从node-shentongdb单独执行它们。
- 使用标量类型而不是数据库对象类型。
- 避免过度使用连接。ping(),并避免将poolPingInterval设置得太低。
语句缓存¶
语句缓存本质是执行计划缓存,node-shentongdb的execute()、executeMany()、getStatementInfo()和queryStream()方法使用数据库调用接口语句缓存来提高语句的重新执行效率。语句缓存允许在不重新解析语句的情况下使用游标。每个缓存语句将保留其游标。语句缓存还降低了node-shentongdb和数据库之间的元数据传输成本。提高了性能和可伸缩性。
连接池中的每个非池连接和每个会话都有自己的语句缓存,默认大小为30。缓存键是语句字符串。这意味着当使用不同的绑定变量值重新执行语句时,可以重用单个缓存项。
语句缓存消除了对单独的“prepare”或“parse”方法的需要,这在其他数据库 API中有时会出现:node-shentongdb中没有单独的方法。
设置语句缓存¶
可以使用shentongdb.stmtCacheSize全局设置语句缓存大小:
shentongdb.stmtCacheSize = 40;
该值可以在shentongdb.getConnection()调用中修改,或者在使用shentongdb.createPool()创建池时调用。例如:
await shentongdb.createPool({
user : "sysdba",
password : "szoscar55",
connectString : "localhost:2003/OSRDB",
stmtCacheSize : 50});
禁用语句缓存¶
通过将属性keepinsmtcache设置为false,可以从语句缓存中排除单个语句。这将防止很少执行的语句从完整缓存中刷新可能执行更频繁的语句。例如,如果一条语句只执行一次:
result = await connection.execute(
`SELECT v FROM t WHERE k = 123`,
[],
{ keepInStmtCache: false });
通过将缓存大小设置为0,可以完全禁用语句缓存:
shentongdb.stmtCacheSize = 0;
当语句的数量或顺序导致缓存条目在被重用之前被刷新时,禁用缓存可能是有益的。例如,如果存在比缓存插槽更多的不同语句,并且语句执行顺序会导致旧语句在重新执行之前从缓存中刷新。
禁用语句缓存在测试和开发环境中也可能有帮助。如果连接保持打开并且重新创建数据库架构对象,则语句缓存可能会变得无效。当连接使用具有不同fetchAsString或fetchInfo数据类型的相同查询文本时,也可能发生这种情况。
其他高级功能¶
node-shentongdb回调¶
node-shentongdb支持回调:
const shentongdb = require('shentongdb');
Connection = shentongdb.getConnection(
{
user : "sysdba",
password : "szoscar55"
connectString : "localhost:2003/OSRDB"
},
function(err, connection) {
if (err) {
console.error(err.message);
return;
}
connection.execute(
`SELECT manager_id, department_id, department_name
FROM departments
WHERE manager_id = :id`,
[103], // bind value for :id
function(err, result) {
if (err) {
console.error(err.message);
doRelease(connection);
return;
}
console.log(result.rows);
doRelease(connection);
});
});
function doRelease(connection) {
connection.close(
function(err) {
if (err)
console.error(err.message);
});}
node-shentongdb 承诺¶
node-shentongdb用异步方法支持承诺,使用本机Promise实现。
如果在没有回调的情况下调用异步方法,它将返回一个Promise:
const shentongdb = require('shentongdb');
shentongdb.getConnection(
{
user : "sysdba",
password : "szoscar55",
connectString : "localhost:2003/OSRDB"
})
.then(function(connection) {
return connection.execute(
`SELECT department_id, department_name
FROM departments
WHERE manager_id < :id`,
[110] // bind value for :id
)
.then(function(result) {
console.log(result.rows);
return connection.close();
})
.catch(function(err) {
console.error(err);
return connection.close();
});
})
.catch(function(err) {
console.error(err);
});
请注意,有两个承诺“链”:一个用于获取连接,另一个用于使用连接。这是必需的,因为它只能引用传递给它的函数中的连接。
node-shentongdb 异步¶
Node.js 7.6支持异步函数,也称为异步/等待。这些可以与node-shentongdb一起使用。例如:
const shentongdb = require('shentongdb');
function getEmployee(empid) {
return new Promise(async function(resolve, reject) {
let connection;
try {
connection = await shentongdb.getConnection({
user : "sysdba",
password : "szoscar55",
connectString : "localhost:2003/OSRDB"
});
const result = await connection.execute(
`SELECT * FROM employees WHERE employee_id = :bv`,
[empid]
);
resolve(result.rows);
} catch (err) { // catches errors in getConnection and the query
reject(err);
} finally {
if (connection) { // the connection assignment worked, must release
try {
await connection.release();
} catch (e) {
console.error(e);
}
}
}
});}
async function run() {
try {
const res = await getEmployee(101);
console.log(res);
} catch (err) {
console.error(err);
}}
run();
如果要将Lob实例用于Lob数据,则必须对Lob进行流式传输,因为没有为其提供的Promisified接口。或者,您可以直接将数据作为字符串或缓冲区处理。