自 SAP HANA SP 11 之后,可以使用 Node.js 作为 Hana 的编程接口。SAP 将 Application server 简称为 XS。现在 XS 已经演化为 Advanced 版本。为了区别,早期的 XS 被称为 XS Classical。
从下图可以看出,和 Hana DB 进行交互的有 HANA XS Classical 、Hana Cloud Platform (HCP) 和 XS Advanced。而能够运行在 HCP 和 XS Advanced 的编程接口包括 XSJS (SAP 推出的服务器端 JavaScript,但目前看,社区并不活跃)、Node.js、Tomcat / TomEE (Java 应用程序编写)等。最近测试了 Node.js 编程接口,感觉还不错。
hdb 模块
Node.js 的编程接口模块是 hdb,可以用 npm install hdb
安装。Github 的源码地址为:。有示例和说明,容易学习。
hdb CRUD
本文打算介绍两个方面:
-
hdb CRUD 的基本方法;
-
以及如何利用 Node.js 的 express 框架实现 REST 风格 API (Restful API)。
先看看基本使用方法:
var hdb = require('hdb')var client = hdb.createClient({ host: '192.168.2.100, port: 30015, user: 'STONE', password: 'pwd'});client.connect(function(err){ if (err){ return console.error('Connect error', err); } var sql = 'SELECT * FROM STONE.EMP_MASTER'; client.exec(sql, function(err, rows){ if (err){ return console.error('Execute error', err); } console.log('Results:', rows); });});
和前几篇一样,仍然使用 STONE.EMP_MASTER
作为数据源。我们注意到,Node.js 广泛使用异步和回调函数。使用异步的原因是 : Node.js 是单线程的,通过异步来避免阻塞 (blocking)。比如,从Hana 数据库查询 employees 表,但不知道需要多久能获得查询结果,通过异步机制,数据查询到之后放在 rows 中。
上面的 SQL 语句没有参数。下面通过 insert
语句来说明带参数 SQL 语句的处理方法。
client.connect(function(err){ if (err){ return console.error('Connect error', err); } var sql = 'INSERT INTO STONE.EMP_MASTER VALUES(?,?,?,?,?,?,?,?)'; client.prepare(sql, function(err, statement){ if (err){ return console.error('Prepare error:', err); } var params = ['9001','Male',18,'test4@qq.com','13800-138000','Bachelor','Married',1]; statement.exec(params, function(err, affectedRows){ if (err){ return console.error('Execute error:', err); } console.log('Affected rows:', affectedRows); }); });});
-
client.prepare()
先处理语句,成功后放在statement
中 -
statement.exec()
语句执行查询,函数的第一个参数是 SQL 语句的参数。注意这个参数是数组类型,即使只有一个参数,也要使用数组。
提供 REST 风格的 Service
使用 Node.js 的 express 框架来实现。网上有非常多使用 express 创建 REST 风格 API 的教程,这里就不细说步骤了。后面会介绍怎样在 OpenUI5 中通过 Rest Service 来对对数据库进行增删改查。
-
安装 Node.js
-
创建一个文件夹,在文件夹中运行
npm init
创建 packages.json 文件。 -
安装 express,这里提供一种方法:
npm install express --save
。--save
参数会修改 packages.json 文件。 -
安装 body-parser。这个模块将处理 post 请求,对 post 请求进行解析。
工程的文件结构如下:
主要文件有:
server.js
: 启动服务dbconfig.js
: hana 数据库连接的配置信息emp.controller.js
: emp_master 表增删改查emp_routes.js
: 路由管理
先说明 package.json
文件,管理 app 依赖的模块:
{ "name": "hana_app", "version": "1.0.0", "description": "hana in nodejs + express", "main": "server.js", "dependencies": { "body-parser": "^1.18.2", "express": "^4.16.2", "hdb": "^0.15.2" }, "devDependencies": {}, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "Stone Wang", "license": "MIT"}
server.js
var express = require('express');var bodyParser = require('body-parser');var app = express();// parse requests of content-type - application/x-www-form-urlencodedapp.use(bodyParser.urlencoded({extended: true}));// parse requests of content-type - application/jsonapp.use(bodyParser.json());// home pageapp.get('/', function(req, res){ res.json('Welcome.');});// register routesvar route = require('./app/routes/emp.routes.js')route(app);// listen on port 3000app.listen(3000, function(){ console.log('Server is running on port 3000.');});
在 server.js
中定义首页的响应,注册路由以及侦听 3000 端口。
dbconfig.js
保存数据库的配置信息,是一个对象:
module.exports = { hana:{ host: '192.168.2.100', port: 30015, user: 'STONE', password: 'pwd' }};
emp.controller.js
var hdb = require("hdb");var dbconfig = require("../../config/dbconfig.js");var client = hdb.createClient(dbconfig.hana);// list allexports.listAll = function(req, res){ var sql = "SELECT * FROM STONE.EMP_MASTER"; client.connect(function(err){ if (err){ res.send({"error": err.message}); } client.exec(sql, function(err, rows){ if (err){ res.send({"error": err.message}); } client.end(); res.send({rows}); }); })};// query by idexports.queryById = function(req, res){ var sql = "SELECT * FROM STONE.EMP_MASTER WHERE EMP_ID=?"; client.connect(function(err){ if (err){ res.send({"error": err.message}); } client.prepare(sql, function(err, statement){ if (err){ res.send({"error": err.message}); } statement.exec([req.params.emp_id], function(err, rows){ if (err){ res.send({"error": err.message}); } client.end(); res.send({rows}); }); }); });};// createexports.create = function(req, res){ var sql = "INSERT INTO STONE.EMP_MASTER VALUES(?,?,?,?,?,?,?,?)"; client.connect(function(err){ if (err){ res.send({"error": err.message}); } client.prepare(sql, function(err, statement){ if (err){ res.send({"error": err.message}); } var params = [ req.body.EMP_ID, req.body.GENDER, req.body.AGE, req.body.EMAIL, req.body.PHONE_NR, req.body.EDUCATION, req.body.MARITAL_STAT, req.body.NR_OF_CHILDREN ]; statement.exec(params, function(err, data){ if (err){ res.send({"error": err.message}); } client.end(); res.sendStatus(200); }); }); });};// updateexports.update = function(req, res){ var sql = "UPDATE STONE.EMP_MASTER SET GENDER=?, AGE=?, EMAIL=?, PHONE_NR=?, EDUCATION=?, MARITAL_STAT=?, NR_OF_CHILDREN=? WHERE EMP_ID=?"; client.connect(function(err){ if (err){ res.send({"error": err.message}); } client.prepare(sql, function(err, statement){ if (err){ res.send({"error": err.message}); } var params = [ req.body.GENDER, req.body.AGE, req.body.EMAIL, req.body.PHONE_NR, req.body.EDUCATION, req.body.MARITAL_STAT, req.body.NR_OF_CHILDREN, req.params.emp_id ]; statement.exec(params, function(err, data){ if (err){ res.send({"error": err.message}); } client.end(); res.sendStatus(200); }); }); });};// deleteexports.delete = function(req, res){ var sql = "DELETE FROM STONE.EMP_MASTER WHERE EMP_ID=?"; client.connect(function(err){ if (err){ res.send({"error": err.message}); } client.prepare(sql, function(err, statement){ if (err){ res.send({"error": err.message}); } statement.exec([req.params.emp_id], function(err, data){ if (err){ res.send({"error": err.message}); } client.end(); res.sendStatus(200); }); }); });};
emp.routes.js
module.exports = function(app){ var empController = require("../controllers/emp.controller.js"); // list all app.get('/employees', empController.listAll); // query by ID app.get('/employee/:emp_id', empController.queryById); // create app.post('/employee/create', empController.create); // update app.put('/employee/:emp_id',empController.update); // delete app.delete('/employee/:emp_id', empController.delete);};
使用 Postman 测试
在项目文件下,通过 node server.js
启动服务。然后打开 Postman 进行测试。以下是部分截图。
- listAll
- create
- update
- delete
- update