Node.js 连接金仓数据库踩坑记(上篇):环境搭建与基础操作

Node.js 连接金仓数据库踩坑记(上篇):环境搭建与基础操作

开篇:一个让我熬夜到凌晨两点的坑

去年做一个内部管理系统,后端用的是Node.js。客户要求数据库必须用金仓,我当时心里还挺有底的------Node.js生态这么丰富,连个数据库驱动还不简单?

结果一搜,发现金仓的Node.js驱动不是我熟悉的那种npm install就能用的方式。下载、拷贝、版本匹配、回调嵌套...折腾了一整晚才跑通。

这篇文章分上下两篇。上篇讲环境搭建和基础CRUD,下篇讲连接池、事务、预处理语句这些稍微高级点的用法。今天先说说怎么把环境跑起来。

一、驱动配置

1.1 下载驱动包

金仓的Node.js驱动不是通过npm发布的,需要去官网下载。下载下来是一个压缩包,解压后里面有个node_modules文件夹,还有一个测试用例。

我一开始还纳闷:怎么不是.tgz文件?后来才知道,这个驱动需要手动放置,不能直接npm install

1.2 放置驱动

把你项目里的node_modules文件夹和驱动里的node_modules合并一下就行。

项目的目录结构看起来像这样:

复制代码
my-kingbase-project/
├── node_modules/
│   └── kb/                    # 从驱动包拷贝过来的
│       ├── index.js
│       ├── lib/
│       └── ...
├── app.js
└── package.json

1.3 版本兼容性

这个驱动是基于Node.js 10.19.0开发的。官方文档说支持Node.js 8、10、12。我一开始用的是Node.js 14,结果require('kb')的时候报了一堆莫名其妙的错,换成Node.js 12就好了。

如果你用高版本Node跑不起来,降级试试。这个坑我花了两个小时才爬出来。

建议先用nvm切到Node.js 12,跑通之后再考虑要不要升。

二、连接数据库

2.1 基本连接方式

驱动提供了一个Client类,通过它来连接数据库。这个设计跟pg模块很像,用过的人应该不陌生。

javascript 复制代码
const { Client } = require('kb');

const client = new Client({
    user: 'SYSTEM',
    host: '127.0.0.1',
    database: 'TEST',
    password: '123456',
    port: 54321,
    ssl: false
});

client.connect(err => {
    if (err) {
        console.error('连接失败', err.stack);
    } else {
        console.log('连接成功');
    }
});

参数说明:

参数名 说明
user 数据库用户名,默认SYSTEM
host 数据库IP地址
database 要连接的数据库名
password 密码
port 端口号,默认54321
ssl 是否启用SSL,默认false

2.2 异步/等待模式

回调写起来有点繁琐,可以包装成Promise。我用util.promisify处理了一下:

javascript 复制代码
const { Client } = require('kb');
const { promisify } = require('util');

async function connectDB() {
    const client = new Client({
        user: 'SYSTEM',
        host: '127.0.0.1',
        database: 'TEST',
        password: '123456',
        port: 54321
    });
    
    const connect = promisify(client.connect).bind(client);
    await connect();
    
    return client;
}

// 使用
const client = await connectDB();

三、执行SQL语句

3.1 不带参数的查询

连接成功后,可以用query方法执行SQL。这个方法是异步的,需要传入回调函数。

javascript 复制代码
client.query('SELECT * FROM users', (err, res) => {
    if (err) {
        console.error('查询失败', err.stack);
        return;
    }
    console.log(res.rows);
});

返回的res对象里,rows是结果集数组,每行数据就是一个对象,字段名对应键名。

3.2 带参数的查询

参数占位符用$1, $2,不需要手动拼接字符串,能防止SQL注入。

方式一:分开传参

javascript 复制代码
const text = 'INSERT INTO users(name, email) VALUES($1, $2) RETURNING *';
const values = ['张三', 'zhangsan@test.com'];

client.query(text, values, (err, res) => {
    if (err) {
        console.log(err.stack);
    } else {
        console.log('插入成功', res.rows[0]);
    }
});

方式二:用对象传参

javascript 复制代码
const query = {
    text: 'INSERT INTO users(name, email) VALUES($1, $2)',
    values: ['李四', 'lisi@test.com']
};

client.query(query, (err, res) => {
    if (err) {
        console.log(err.stack);
    } else {
        console.log('插入成功');
    }
});

RETURNING *很实用,能直接拿到刚插入的数据,省得再查一次。

3.3 预处理语句

如果要多次执行同一条SQL,可以用预处理语句(Prepared Statement)。它会提前解析SQL模板,多次执行时只需要传参数,效率更高。

javascript 复制代码
const query = {
    name: 'insert-user',  // 语句名称
    text: 'INSERT INTO users(name, email) VALUES($1, $2)',
    values: ['王五', 'wangwu@test.com']
};

client.query(query, (err, res) => {
    if (err) {
        console.log(err.stack);
    } else {
        console.log('预处理语句执行成功');
    }
});

给了name参数后,驱动会把这个查询存起来,下次同样name的查询可以直接复用。适合高频执行、结构固定的SQL。

3.4 参数传递的安全性

所有参数都要通过values数组传递,不要拼字符串。

javascript 复制代码
// 正确写法
client.query('SELECT * FROM users WHERE name = $1', [userInput]);

// 错误写法
client.query(`SELECT * FROM users WHERE name = '${userInput}'`);

下面那种写法有SQL注入风险。userInput如果传'; DROP TABLE users; --,后果你懂的。

3.5 处理查询结果

res.rows是结果集数组,数组长度就是行数。

javascript 复制代码
client.query('SELECT * FROM users WHERE age > $1', [18], (err, res) => {
    if (err) {
        console.error(err);
        return;
    }
    
    // 结果集为空的情况
    if (res.rows.length === 0) {
        console.log('没有找到符合条件的数据');
        return;
    }
    
    // 遍历结果
    res.rows.forEach(row => {
        console.log(`用户名: ${row.name}, 邮箱: ${row.email}`);
    });
});

四、关闭连接

用完数据库要关连接,不然连接会一直占着。

javascript 复制代码
client.end(err => {
    if (err) {
        console.log('关闭连接时出错', err.stack);
    } else {
        console.log('连接已关闭');
    }
});

一个常见错误 :在还没执行完查询的地方调用client.end(),会导致异步操作被中断。一定要等所有query的回调都执行完了再关。

五、完整示例

下面是一个完整的小程序,建表、插入、更新、查询、删除全流程都走一遍。

javascript 复制代码
const { Client } = require('kb');

const client = new Client({
    user: 'SYSTEM',
    host: '127.0.0.1',
    database: 'TEST',
    password: '123456',
    port: 54321,
});

client.connect();

// 建表
client.query('CREATE TABLE test_node (id int, name char(100), address varchar(200), salary real, created_at DATE, description TEXT)', (err, res) => {
    if (err) {
        console.error('建表失败', err.stack);
        return;
    }
    console.log('建表成功');

    // 插入数据
    const now = new Date();
    const text = 'INSERT INTO test_node VALUES($1, $2, $3, $4, $5, $6) RETURNING *';
    const values = [1, '张三', '西湖区', 8000, now, '测试数据'];

    client.query(text, values, (err, res) => {
        if (err) {
            console.error('插入失败', err.stack);
            return;
        }
        console.log('插入成功', res.rows[0]);

        // 更新数据
        const updateQuery = {
            text: 'UPDATE test_node SET name = $1, address = $2 WHERE id = $3 RETURNING *',
            values: ['李四', '滨江区', 1]
        };

        client.query(updateQuery, (err, res) => {
            if (err) {
                console.error('更新失败', err.stack);
                return;
            }
            console.log('更新成功', res.rows[0]);

            // 查询数据
            client.query('SELECT * FROM test_node', (err, res) => {
                if (err) {
                    console.error('查询失败', err.stack);
                    return;
                }
                console.log('查询结果:', res.rows);

                // 删除数据
                client.query('DELETE FROM test_node WHERE id = 1', (err, res) => {
                    if (err) {
                        console.error('删除失败', err.stack);
                        return;
                    }
                    console.log('删除成功,影响行数:', res.rowCount);

                    // 删表
                    client.query('DROP TABLE test_node', (err, res) => {
                        if (err) {
                            console.error('删表失败', err.stack);
                            return;
                        }
                        console.log('删表成功');

                        client.end();
                    });
                });
            });
        });
    });
});

六、常见问题

6.1 模块找不到

Cannot find module 'kb'

检查node_modules目录里有没有kb文件夹,确认拷贝到了项目根目录。

6.2 连接被拒绝

connect ECONNREFUSED 127.0.0.1:54321

ksql命令行测试一下能不能连。如果命令行也连不上,检查IP、端口、用户名、密码。金仓的默认端口是54321,别写成5432。

6.3 Node.js版本不兼容

报一些奇怪的错,比如Class extends value undefined

降Node.js版本到12试试。驱动基于10.19.0开发,12比较稳,再高可能有问题。

6.4 中文乱码

数据库连接参数里没指定编码。建库时用UTF8,或者在连接后执行SET client_encoding TO 'UTF8'

javascript 复制代码
client.query("SET client_encoding TO 'UTF8'", (err) => {
    // 继续后续操作
});

七、小结

上篇主要讲了Node.js连接金仓数据库的基础配置和CRUD操作:

  1. 驱动安装 :下载驱动包,把kb文件夹放到项目的node_modules
  2. 版本要求:Node.js 8-12能跑,高了可能报错
  3. 基本操作Client类建连接,query方法执行SQL
  4. 参数传递 :用$1, $2占位符,不要拼接字符串
  5. 连接管理 :用完要end(),不然连接会一直占着

下篇会讲一些高级用法:连接池、事务、批量插入、流式查询等。希望对你有帮助。

相关推荐
mN9B2uk171 小时前
数据库设计 Step by Step
数据库·oracle·数据库开发
abcy0712131 小时前
oracle配置pdb账号密码图文教程
数据库·oracle
这个DBA有点耶1 小时前
当时间数据不再只是“曲线”:聊聊时序数据库和融合分析
数据库·sql·程序人生·云原生·运维开发·时序数据库·业界资讯
小此方1 小时前
Re:Mysql数据库基础篇(一):CentOS/Linux 环境下的完整安装/运行/登录Mysql流程与首次登录异常处理
linux·数据库·mysql
IvorySQL2 小时前
PostgreSQL 技术日报 (6月4日)|SQL/PGQ 新特性,逻辑复制持续优化
数据库·sql·postgresql
IT空门:门主2 小时前
MySQL MCP Server 从零安装到使用实战,AI 直接查询数据库
数据库·人工智能·mysql
minji...2 小时前
MySQL数据库 (二) 库的操作(增删查改),库的字符集和校验集,数据库的备份与恢复
数据库·mysql数据库·字符集·库的增删查改·校验集·数据的备份·数据的恢复
前端与小赵2 小时前
数据库交互全链路实战:通用封装、批量优化与动态查询三大核心模块
数据库·python·sql
霸道流氓气质2 小时前
异步任务提交 + Redis 状态轮询模式实战指南
数据库·redis·缓存