ThinkJs(https://thinkjs.org/)是个非常不错的NodeJs的MVC开发框架,它本身提供了操作多种数据库的方法,但目前还不支持MSSQL数据库,而我在做开源项目CmPage(http://git.oschina.net/defans/cmpage)的时候,为了提高一点开发效率,觉得还是用熟悉一点的数据库较好,而且很多场景的数据处理用SQL语言的话可以很简练,因此问题就来了,在thinkjs中如何操作mssql 并且和 thinkjs 本身的数据库操作方法在一定程度上兼容呢?
网上查了一下,目前较好地用Promise方式来实现MSSQL连接的ORM框架是 Sequelize,虽然我没有用到ORM的特性,数据操作还是SQL语句拼接后用 query 方法执行,但难保以后不会改变主意,因此基本思路就有了: 从 think.base 继承类,然后加入 sequelize ,实现 think.model.base 的一些方法,如 add,update,where,find,select等等,为了可以链式调用,在 field 和 where 方法中返回自身(return this;),而为了实现子类中能够这样调用:this.model('t_emp').xxx,也实现了model方法,以上方法的实现逻辑很简单,就是定义一些成员变量表示SQL语句的各部分组成,比如:tableName,fields,_where,pk等;然后在相应方法中赋值后返回自身(this)。具体请参见代码: http://git.oschina.net/defans/cmpage/blob/master/src/cmpage/model/base.js?dir=0&filepath=src%2Fcmpage%2Fmodel%2Fbase.js&oid=10eba62a6f3ab3de95d3aeb8530a5fedc3ad518e&sha=10eb12139d697c1d4f25125e64966b029c2cf97e
如果以后 thinkjs 实现了mssql想换回来的话也很简单,把 page.js 改为从 think.model.base 继承就可以了。
还是在这里贴一下代码吧:
'use strict';
// +----------------------------------------------------------------------
// | CmPage [ 通用页面框架 ]
// +----------------------------------------------------------------------
// | Licensed under the Apache License, Version 2.0
// +----------------------------------------------------------------------
// | Author: defans
// +----------------------------------------------------------------------
/**
@module cmpage.model
*/
/**
* 业务模块的基类,通过ORM框架 Sequelize 连接各个业务数据库
* 连接参数配置于各个module的配置文件中,xxxx/config/db.config
* 为了和thinkjs.model的常用方法名保持一致,以下实现了相关方法,通过转换成sql语句用 sequelize.query(sql) 执行
* 如果想使用 sequelize 的ORM方法,可以在子类中通过 this.sequelize.xxxx 来调用
* 子类中用think.model,或者cmpage.model来实例化其他类其他模块,本类中的 model 用于设置表名以便生成SQL语句,也为了兼容 thinkjs.model(表名).xxx 的调用方式
* 如果以后版本的 ThinkJS能支持mssql,以上兼容可以使得将 page.js 改回从 think.model.base 继承的时候不需要做太大改动
* @class cmpage.model.base
*/
import Sequelize from 'Sequelize';
export default class extends think.base {
sequelize = null;
_model = null; //thinkjs.model.base , 目前暂时不用
_field = '';
_where = '';
_tableName = '';
pk = 'id';
/**
* constructor
* @param {[type]} name [description]
* @param {Object} config [description]
* @return {[type]} [description]
*/
constructor(name, config = {}) {
super();
if (think.isObject(name)) {
config = name;
name = "";
}
this._tableName = name;
this.config = think.parseConfig(config);
//debug(this.config, 'base.constructor - this.config');
//debug(this.name, 'base.constructor - this.name');
}
/**
* 创建连接
* @return {[type]} [description]
*/
getConnection() {
this.sequelize = new Sequelize( this.config.database, this.config.user, this.config.password, {
host:this.config.host || "127.0.0.1",
port:this.config.port || 3306,
dialect: this.config.type || 'mysql',
benchmark:true,
logging:this.log,
pool: {
max: 5,
min: 0,
idle: 10000
}
});
return this.sequelize;
}
log(msg){
cmpage.debug(msg,'SQL');
}
setTableName(name){
this._tableName = name;
return this;
}
model(name){
this.setTableName(name);
// if(this.config.type != 'mssql' && (name.indexOf('t_') ===0 || name.indexOf('vw_') ===0 || name.indexOf('fw_') ===0) ){
// this._model = new think.model.base(name,this.config);
// }
return this;
}
setPk(pk){
this.pk = pk;
if(this._model) this._model.pk = pk;
return this;
}
field(fields){
this._field = fields;
if(this._model) this._model.field(fields);
return this;
}
where(where){
if(this._model){
this._model.where(where);
return this;
}
this._where = '';
if(think.isObject(where)){
let arr = [];
for(let p in where){
arr.push(p+'='+this.parseValue(where[p]));
}
if(arr.length >0) this._where = `where ${arr.join(' and ')}`;
}else {
this._where = 'where '+ where;
}
return this;
}
/**
* 执行原生SQL语句,取结果集返回
* @return {array} 查询结果集
* @param {string} sql
* @param {object} options 参数设置
*/
async query(sql,options) {
if(this._model){
return await this._model.query(sql);
}
if (!this.sequelize) {
this.getConnection();
}
let list = await this.sequelize.query(sql,options);
if(list.length >0) return list[0];
return [];
}
async select(){
if(this._model){
return await this._model.select();
}
let sql = `select ${think.isEmpty(this._field) ? '*': this._field} from ${this._tableName} ${this._where} `;
return await this.query(sql);
}
async find(){
if(this._model){
return await this._model.find();
}
let list = await this.select();
if(list.length >0) return list[0];
return {};
}
async delete(){
//为了避免误操作,条件语句不能为空,当然,可以用 where 1=1
if(think.isEmpty(this._where)) return;
if(this._model){
return await this._model.delete();
}
let sql = `delete from ${this._tableName} ${this._where}`;
let ret = await this.query(sql);
//debug(ret,'base.delete - ret');
}
async count(){
if(this._model){
return await this._model.count();
}
let sql = `select count(*) as cnt from ${this._tableName} ${this._where}`;
let list = await this.query(sql);
return list[0]['cnt'];
}
async add(rec){
if(this._model){
return await this._model.add(rec);
}
let values = [];
let _field = [];
for(let key in rec){
if(/^c_\w+/.test(key) && key !=this.pk) {
let val = rec[key];
val = this.parseValue(val);
values.push(val);
_field.push(key);
}
}
let sql = `INSERT INTO ${this._tableName}( ${_field.join(',')} ) VALUES( ${values.join(',')} ); `;
let list = await this.query(sql);
list = await this.query('select @@IDENTITY as id;');
// debug(list);
if(list.length >0){
return list[0]['id'];
}
return 0;
}
async update(rec){
if(this._model){
return await this._model.update(rec);
}
let _field = [];
for (let key in rec) {
if (/^c_\w+/.test(key) && key != this.pk) {
let val = rec[key];
val = this.parseValue(val);
_field.push(key + '=' + val);
}
}
if(think.isEmpty(this._where)) this._where = ` where ${this.pk}=${rec[this.pk]}`;
let sql = `UPDATE ${this._tableName} SET ${_field.join(',')} ${this._where}`;
//debug(sql,'base.update - sql');
await this.query(sql);
}
parseValue(value){
if (think.isString(value)) {
value = `'${value.replace(/\'/g,'\\\'')}'`;
}else if(think.isArray(value)){
if (/^exp$/.test(value[0])) {
value = value[1];
}else{
value = value.map(item => this.parseValue(item));
}
}else if(think.isBoolean(value)){
value = value ? 'TRUE' : 'FALSE';
}else if (value === null) {
value = 'null';
}
return value;
}
}