PostgreSQL 使用 node-postgres 在 node.js 中的应用

之前有一篇介绍PostgreSQL在MAC环境下的初体验 ,今天要和大家分享的是 node.js 应用 + PostgreSQL 做数据存储的demo.


node.js 环境使用 PostgreSQL 需要下载编译 node-postgres 

github地址: https://github.com/brianc/node-postgres

使用:

zhangzhi@moke:npm install --save pg

README.md 关于如何使用的api 也比较简单:

var pg = require('pg');
var conString = "postgres://username:password@localhost/database";

pg.connect(conString, function(err, client, done) {  
    if(err) {    
        return console.error('error fetching client from pool', err);
    }
    client.query('SELECT $1::int AS number', ['1'], function(err, result) {
        done();    
        if(err) {      
            return console.error('error running query', err);
    }    
    console.log(result.rows[0].number);    //output: 1
  });
});

上面是来自官方README 文档中的一段 Demo ,几乎描述了 pg 包提供给我们的所有api .

没错,就是 2个. 

pg.connect  得到 client

然后 调用 client.query 得到我们需要的结果.


上面的这一小段调用,需要我们准备哪些东西呢?

  1. 连接字符串 (数据库用户名,密码,数据库所在服务器地址,数据库名称)

  2. 查询 SQL 语句

  3. 所需要的参数

zhangzhi@moke:~$ psql -U postgres //登录postgres 用户
psql (9.4.1)
Type "help" for help.

postgres=#CREATE DATABASE huheren WITH OWNER = postgres //创建huheren数据库

创建成功后,可以直接切换到新建的数据库

postgres=#\c huheren   //切换到 huheren 数据库

你也可以登录数据库的时候直接指定数据库名

zhangzhi@moke:~$ psql -U postgres -d huheren
psql (9.4.1)
Type "help" for help.
huheren=#


下面我们需要创建数据表了

huheren=#CREATE TABLE job
(
  _id serial NOT NULL,
  user_id integer,
  title character varying(512),
  company character varying(512),
  isvip boolean,
  area character varying(512),
  jobyear integer,
  address character varying(512),
  pay character varying(50),
  content text,
  mobile character varying(20),
  email character varying(50),
  ext character varying(512)
);

13个字段的一个表,具体含义不讲了,只是demo 演示.

需要注意的是: _id 字段是一个自增字段  指定了 serial ,并且不为空 NOT NULL 约束.

其他的字段中

 user_id 整形 ,有 integer 约束.

isvip 布尔型

剩下都是 varchar 和 text 类型.


你用下面的语句创建表也是可以的:

huheren=#CREATE TABLE job
(
  _id serial NOT NULL,
  user_id integer,
  title varchar(512),
  ......
  content text,
  mobile varchar(20)
  ......
);


这下我们数据库层面的准备工作先告一个段落.


node.js 层面:

从上面的 node-postgersql 包提供的 api 来看,我们对于每次的请求都需要 connect 数据库,然后得到 client ,再去 query.

那么我们想,connect 和 query 这2个共用方法是不是放在基类类比较好.子类直接继承即可.


我们先来实现一个基类: _Base.js

var config = require("config");
var pg = require("pg");
var Q = require("q");

function _Base() {
    //构造函数
}

_Base.prototype._connect = function (callback) {
    var defer = Q.defer();
    var connectStr = config.pg_connectStr;
    pg.connect(connectStr, function (err, client, done) {
        if (err) {
            defer.reject(err);
        } else {
            defer.resolve({client: client, done: done});
        }
    });
    return defer.promise.nodeify(callback);
};

_Base.prototype._query = function (sql, params, callback) {
    if (typeof params == 'function') {
        callback = params;
        params = [];
    }
    if (!params) {
        params = [];
    }
    if (!sql) {
        var err = new Error("sql is empty!");
        var defer3 = Q.defer();
        return defer3.reject(err).nodeify(callback);
    }

    //是否打印SQL语句
    if (config.debug) {
        console.log('[SQL:]', sql, '[:SQL]');
        console.log('[PARAMS:]', params, '[:PARAMS]');
    }
    return this._connect()
        .then(function (result) {
            var client = result.client;
            var done = result.done;
            var defer = Q.defer();
            client.query(sql, params, function (err, result) {
                done();
                if (err) {
                    defer.reject(err);
                } else {
                    defer.resolve(result);
                }
            })
            return defer.promise;
        })
        .nodeify(callback);
};

module.exports = _Base;

这个基类也非常简单,只有2个方法 _connect 和 _query 

在开始我们引入了 config ,因为我们的PostgreSQL连接字符串写在了配置文件中:

"pg_connectStr":"postgres://postgres:moke@127.0.0.1/huheren",

还引入了 Q ,它是实现了 Promise/A 规范的包,我们这里旨在把 _connect 和 _query 方法也实现 Promise 规范.

之前我们介绍过 Promose/A 规范when ,这个Q相对 when 非常轻量级,想了解的可以去github 搜索.


我们刚才在数据库中创建了 job 的表.那么我们对应的创建一个 jobInfo 的对象,同时让他继承 _Base 基类.

var _Base=require('./_Base');
var util=require('util');
var _=require('lodash');

function JobInfo(obj){
    this.params=obj;
}

util.inherits(JobInfo,_Base);

JobInfo.prototype.sql_option={
    "select_all":"SELECT * FROM job",
    "insert":"INSERT INTO job(user_id,title,company,isvip,area,jobyear,address,pay,content,mobile,email,ext) VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12)"
};

JobInfo.prototype.save=function(){
    return this._query(this.sql_option.insert,_.values(this.params)).then(function(result){
        return result;
    }).catch(function(err){
        return err;
    })
}

JobInfo.prototype.selectAll=function(){
    return this._query(this.sql_option.select_all,[]).then(function(result){
        return result;
    });
}

module.exports = JobInfo;

上面调用构造函数时传入了一个对象,最后赋值给 this.params ,那么实例化后的对象方法就可以共用这个变量了.

jobInfo 当前有2个方法+1个对象

save 方法,存储一个jobInfo 对象.

selectAll 方法,读取所有的 jobInfo 对象.

sql_option 对象,其实是维护当前对象的一些sql 语句. 你可以看到 select_all 就是 select_all 方法要使用的sql , insert 就是 save方法使用的.

如果你需要一个 delete 方法,那么jobInfo 添加一个 delete 方法,同时 sql_option 中添加一对键值来表示 delete 的SQL语句即可.维护起来也非常方便.


上面的Inser 看上去很长,感觉很"傻"

其实你看到的我也看到了.之前我用T-SQL语法 INSERT INTO job VALUES(......) 结果出错,

因为我们在建表的时候第一个字段 _id 是自增的,所以我的 VALUES 中传入12个参数,理想的结果应该系统跳过 _id ,把对应的12个参数插入到12个字段里,但是现实不是这样的,它总是把第一个参数插入 _id 中,这样屡次报错,我搞不清是我哪里写错了.或者建表结构出了什么问题,如果你有更聪明的写法,麻烦告知我,关于我 里面有我的联系方式,Thx!


由于上面的原因,我不得不用标准的SQL 语句,就是 INSERT INTO job(这里指定要插入的字段名) VALUES(这里写上对应的字段值)

当然我们上面用了格式化字符串,这样的优点是防止sql注入 .


如果你对安全性要求极高,你甚至可以在语句中指定类型 INSERT INTO job(user_id .......)  VALUES ($1::int  .........) 指定格式化占位符$1同时也指定此值的类型.


我们上面的 save 和 selectAll 方法里面都直接调用了 基类 _Base 中的 _query 方法,非常方便.


其他实体类如上方式创建.


然后逻辑层中调用Model层:

var JobInfo=require('../models').JobInfo; //引入刚才定义的 jobInfo 实体类.


exports.job_save=function(callback){
    //实例化时传入一个对象
    var jobInfo=new JobInfo({
        user_id:'11111',
        title:"测试1",
        company:"aaa",
        isvip:false,
        area:"玉泉",
        jobyear:5,
        address:"bbbbb",
        pay:"10",
        content:"cccccc",
        mobile:"13800138000",
        email:"dd@ee.com",
        ext:""
    });
    //调用 jobInfo 对象上的 save 方法.
    jobInfo.save().then(function(result){
        //callback 直接返回给了控制层
        callback(null,result);
    }).catch(function(err){
        //异常返回
        callback(err,null)
    });
};


exports.job_select_all=function(callback){
    var jobInfo=new JobInfo();
    jobInfo.selectAll().then(function(result){
        callback(null,result);
    }).catch(function(err){
        //异常返回
        callback(err,null)
    });
}

上面首先引入jobInfo 实体类.

然后调用 jobInfo 构造函数 new 一个 jobInfo 对象.

接着调用 jobInfo 上实现的2个方法,一个插入数据,一个查询数据.


最后我们看下控制层,就是路由直接打交道的这一层,它负责http 的接收和回发.

var job=require('../job');//调用业务逻辑层


exports.page_add_job = function (req, res) {
    job.job_save(function(err,result){
        console.log('**********',result);
        res.send(result);
    });
}

exports.page_job = function (req, res) {
    job.job_select_all(function(err,result){
        console.log('**********',result);
        res.send(result);
    })
};

路由指定到上面这2个方法我们就省略了.....

看下请求结果:


http://localhost:8887/addJob
调用 page_add_job  --> 调用业务逻辑层  job_save  --> 调用实体层 jobInfo 的  save 方法, save 得到参数,拿到sql ,然后请求自己的 _query 方法 ( _query 方法是 jobInfo 继承基类 _Base 的)

我们把 _query 直接返回的结果 send 出去,最后渲染在浏览器上,结果告诉我们插入成功.


经过多次的插入后.我们看下数据库中job 表内所有的数据:

http://localhost:8887/job

调用 page_job  --> 调用业务逻辑层 job_select_all  --> 调用实体层的 selectAll  方法, selectAll 无需参数,拿到 sql 后直接请求自己的 _query 方法即可,最后我们把请求结果直接 send 到浏览器上:

结果显示数据库里一共有8条数据. 这8个对象在 rows 属性下.我们清楚看到 _id 自增字段是系统自动维护的.但是上面的 INSERT INTO job VALUES(........) 时,在不指定要插入的字段是, _id 默认也会被我们传入的参数填充,非常奇怪...好了,睡觉,晚安!



***end***

回到顶部