From f2880548ad780514cd4f47868581f2bd15996bff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E4=BC=9F?= Date: Mon, 23 Apr 2018 16:41:35 +0800 Subject: [PATCH] =?UTF-8?q?v2.0.0=E7=89=88=E6=9C=AC=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重大版本更新 --- README.md | 1058 ++++++---------------------- lib/BaseActionManager.js | 12 + lib/OFInstance.js | 3 +- lib/OFramework.js | 3 +- lib/grammar/mysql.js | 495 +++++++++++++ lib/instance/MySQLActionManager.js | 260 +++++++ lib/onela.js | 281 ++++++++ package.json | 4 +- tests/test.query.js | 152 ++++ 9 files changed, 1432 insertions(+), 836 deletions(-) create mode 100644 lib/BaseActionManager.js create mode 100644 lib/grammar/mysql.js create mode 100644 lib/instance/MySQLActionManager.js create mode 100644 lib/onela.js create mode 100644 tests/test.query.js diff --git a/README.md b/README.md index 1441fda..d6648a7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Onela is an open source object-relational mapping framework(Onela是一个开源对象关系映射框架) +# Onela一个Node.js开源的ORM对象关系映射框架 > Onela is an object-based mapping framework based on node.js open source, supporting a variety of relational database data infrastructure. At the same time support a variety of database object read and write separation, the database instance vertical split. On top of the onela architecture you can experience the fun of programming without SQL, and you only need to focus on the business logic code section. And, I will be in the later version of the support to join the distributed cache to achieve the front and back end with node.js program to challenge the case of large-scale applications. > @@ -6,693 +6,236 @@ -### step 1 npm install node_modules(第一步:安装node模块) +### 重大更新:v2.0.0版本发布 -npm install onela +此版本重大更新,原有如果使用了V2.0.0之前的版本请注意,升级到最新版,最原有代码也需要微调。 +特别感谢Hugh-won在v2.0.0版本改进提供帮助~ +~~~~~ +在就版本中模块引用需要批量调整下 +const onela = require('onela'); +更改为: +const onela = require('onela').old; +~~~~~ -### step 2 Mapping data sources(第二步:配置数据源) +此版本文档已经更新为最新文档,老版本文档请查看:[老版本文档](https://github.com/zouwei/onela/wiki/v1.*%E7%89%88%E6%9C%AC%E6%96%87%E6%A1%A3%EF%BC%88%E6%97%A7%E7%89%88%EF%BC%89) -Create the oodbc.js file in the “common” folder in the project root directory. The reference is as follows: -``` -/** - * OODBC - * 数据源(这个是自定义的) - * Node.js 创建数据连接的实例(如下所示mysql的初始化实例,pool为数据源实例,绑定到配置即可) - * 读写分离,或者分库读写分离,甚至是多种类型的数据库需要分别实例化配置 - * var mysql = require("mysql"); - * var pool = mysql.createPool(config.get('global.mysql')); - * 另外需要注意的是,数据库实例化需要在app.js启动的时候预先加载,所以我这里放到一个单独文件进行进行初始化的。 - */ -var db = require("../service/dbconnect"); - -/** - * 数据源配置 - * 注意:配置的数据源必须是预先初始化好的 - */ -var dataSource = { - /** - * 默认是MYSQL - * 配置属性名称统一采用大写 - */ - "MYSQL": { - /** - * 默认数据库的配置实例 - * 大多数情况下,大多数据表放到同一个数据库的情况下 - */ - "DEFAULT": { - "READER": db.db01, - "WRITER": db.db02 //读写没有分离的情况,绑定到同一个数据源即可 - }, - /** - * 如果存在数据库拆分的实例(多个库组成一套系统的情况) - * 如果不存在多个数据库的实例,OTHER节点可以删除 - */ - "CENTRAL": { - "READER": db.db10, //Authority system - "WRITER": db.db10 //Authority system - } - }, - /** - * 如果不存在ORACLE的数据库的访问实例可以删除节点 - */ - "ORACLE": { - "DEFAULT": { - "READER": null, - "WRITER": null - } - } -}; - - -module.exports = dataSource; -``` +### 步骤一:安装node模块(step 1 npm install node_modules) +npm install onela -### step 3 Initialize the object relationship configuration file(第三步:初始化对象关系配置文件) -Automatically initialize the object relationship configuration file, of course, you can also add it manually. -**You need to write a way to implement the ona configuration file initialization, of course, you can directly copy the following code to achieve.** +### 步骤二:配置数据源(step 2 Mapping data sources) -Call initConfigFile () to complete the initialization of the onelaInstanceConfig.json file +数据库的配置可以配置在config全局配置中,在初始化调用取出来就可以了了 ``` /** - * 工具方法,用来创建onela数据库表映射的初始配置文件 - */ -var path = require("path"); -/** - * OFramework框架测试 - * 实例化实体对象 + * 数据库配置,可以初始化多个数据库实例 */ -var onela = require('onela'); -//数据源 -var oodbc = require('../core/oodbc'); -//onela-tools,项目已经开源:(下载地址:https://github.com/zouwei/onela-tools) -let onelaTools = require('./onela-tools.js'); +let dbconfig = [{ + "name": "default", // 数据库实例名称 + "type": "mysql", // 数据库类型(目前只支持mysql) + "value": { + "connectionLimit": 5, + "host": "localhost", + "user": "", + "password": "", + "database": "todo" + } +}]; -var m = {}; +``` -/** - * 初始化OFramework配置文件 - * @param paras.tableName 数据表名称 - * @param cb 回调函数 - * author:zack zou - * create time:2017-04-08 - */ -m.initConfigFile = function () { - return new Promise(function (resolve, reject) { - let tools = new onelaTools(oodbc, { - "database": "MySQL", - "instance": "DEFAULT" - }); - /** - * 根据表字段获取数据表字段名称数据 - * 需要指定配置文件输出路径 - */ - let config_path = path.resolve('config') + "\\onelaInstanceConfig.json"; - //生成数据对象实例的配置 - tools.initConfigFile(config_path) - .then((result) => { - resolve(result); - }) - .catch((err) => { - reject(err); - }); - }); -} -/** - * 自动化生产Control后台管理方法(常用的增删改查) - * @param paras.tableName 数据表名称 - * author:zack zou - * create time:2017-04-18 - */ -m.initManagementMethodFile = function () { - return new Promise(function (resolve, reject) { - //实例初始化 - let tools = new onelaTools(oodbc, { - "database": "MySQL", - "instance": "DEFAULT" - }); - /** - * 根据表字段获取数据表字段名称数据 - * 需要指定配置文件输出路径 - */ - let paras = { - "path": "./ws_test/", //输出文件路径,指向到目录即可,结尾“/” - //可选参数,否则会填写默认值 - "directory": "core", //数据层输入目录名称 - "protocol": "http", - "host": "localhost:8090", - "author": "zack zou" - } - //工具方法 - 创建后台管理请求Control方法 - tools.initManagementMethodFile(paras) - .then((result) => { - resolve(result); - }) - .catch((err) => { - reject(err); - }); - }); -} +###步骤三:Onela ORM对象初始化(step 3 Onela ORM object initialization) +~~~~~~ +const {Onela, OnelaBaseModel} = require("onela"); +// 初始化Onela模块(建议全局初始化) +Onela.init(dbconfig); +~~~~~~ -module.exports = exports = m; -``` +### 步骤四:单例(数据表)对象配置以及方法的扩展封装 -### onelaInstanceConfig.json configuration file structure(onela-tools工具方法生成的配置文件结构实例如下) - -You can configure it manually,If it is distributed data deployment, need to control odbc.js configuration. - -~~~~ -{ - "tables": { - "user_info": { - "database": "MYSQL", - "instance": "DEFAULT", - "tableName": "user_info", - "tableFiles": [ - "id", - "name", - "moblie", - "email", - "password", - "remark", - "created_time", - "created_id", - "update_time", - "update_id", - "valid" - ] - } - }, - "proc": { - "usp_user_info_getUserInfoList": { - "database": "MYSQL", - "instance": "DEFAULT", - "proc_name": "usp_user_info_getUserInfoList", - "parameter": [ - { - "type": "in", - "name": "_start", - "mandatory": true, - "dataType": "INT(10)", - "notes": "数据分页开始位置" - }, - { - "type": "in", - "name": "_length", - "mandatory": false, - "defaultValue": 5, - "dataType": "INT(10)", - "notes": "数据分页每页取值的记录数" - }, - { - "type": "out", - "name": "_totalCount", - "mandatory": false, - "dataType": "INT(10)", - "notes": "记录总数" - } - ] - } - } +~~~~~ +// 在OnelaBaseModel类中封装了常用的ORM方法 +class ToDoManager extends OnelaBaseModel { + // 可以在此自定义扩展方法(默认封装没有的方法) } -~~~~ +/** + * 【重要】单例模式,数据表配置 + * tableName:数据表名 + * engine:数据库引擎名称,需要和dbconfig配置的名称对应起来 + * fields[n].name:数据表字段名 + * fields[n].type:数据表字段类型 + * fields[n].default:默认值 + * */ +ToDoManager.configs = { + fields: [ + {name: "id", type: "int", default: null}, + {name: "content", type: "varchar"}, + {name: "is_done", type: "int", default: 0}, + { + name: "create_time", type: "datetime", default: () => { + return new Date() + } + }, + {name: "finish_time", type: "datetime", default: null} + ], + tableName: "todos", + engine: "default" +}; +~~~~~ -### step 4 Method of calling(第四步:方法的调用) -Here are the sample code for all methods +###步骤五:常用CRUD操作代码示例(step 5 Examples of common CRUD operation code) -*Data cache is not currently implemented* +到这一步骤,可以直接使用ORM的方法了,增删改查,包含事务处理。 ``` -/** - * 实体对象模型 - * author:zack zou - * create time:2017-10-23 - */ - -//onela ORM框架 -const onela = require('onela'); -//数据源 -const oodbc = require('../oodbc'); -//配置文件 -const onelaInstanceConfig = require("../../config/onelaInstanceConfig.json"); +// 【重要】单例模式,数据表配置 +ToDoManager.configs = { + fields: [ + {name: "id", type: "int", default: null}, + {name: "content", type: "varchar"}, + {name: "is_done", type: "int", default: 0}, + { + name: "create_time", type: "datetime", default: () => { + return new Date() + } + }, + {name: "finish_time", type: "datetime", default: null} + ], + tableName: "todos", + engine: "default" +}; /** - * onela框架使用实例(此类可以整体复用,采用class的写法,使用时new对象即可) - * 实例化基础实例onela基础方法的通用ORM底层方法 - * 一般情况下不要在基础方法实例里面修改,如需新增方法,请在扩展方法体中进行编写 + * 事务 */ -class base { - - /** - * 构造函数 - */ - constructor() { - //初始化实例对象,根据实例名称指定不同的数据库实例 - this.db_instance = onela(oodbc, onelaInstanceConfig.tables["entity_name"]); - - } - - /** - * 分页查询实体模型信息 - * @param paras 参数集合,任意参数的组合 - * @param use_cache 是否使用缓存;true表示使用缓存 - * 参数 结构 - * { - * start: 开始数据索引 - * length: 获取数据行数 - * orderBy:{"字段名":"规则(ASC|DESC)" 字段名:默认为 createtime 规则:默认为 DESC } - * keyword: - * [ - * {"key": "字段名", "value":"值", "logic": "连接联符 (默认为and 非必须 )", operator: "关联符号 (默认为: = 可以为空)"}, - * {"key": "valid", "value": "1"} - * ] - * } - * 返回:{ - * data: [], //数据列表 - * recordsTotal: 0, //查询记录总数 - * start: 0, //当前页索引 - * length: 10 //页大小 - } - * author:zack zou - * create time:2017-10-23 - */ - getEntityList(paras, use_cache) { - let self = this; - //定义结构 - return new Promise(function (resolve, reject) { - /** - * 数据缓存暂时做方案预留 - */ - self.db_instance.getEntityList(paras) - .then(function (data) { - resolve(data); - }) - .catch(function (err) { - reject(err); - }); - }); - } - - /** - * 查询瀑布数据列表,不返回记录总数,当返回的isLastPage=true,查询到了末尾 - * 查询记录length,查询可以length+1,前台数据只显示length的记录,判断尾页,利用length+1判断,小于等于length的记录到了末尾页面 - * @param paras 关键参数:paras.command、paras.keyword、paras.orderBy - * @param use_cache 是否使用缓存;true表示使用缓存 - * 参数 结构 - * { - * start: 开始数据索引 - * length: 获取数据行数 - * orderBy:{"字段名":"规则(ASC|DESC)" 字段名:默认为 createtime 规则:默认为 DESC } - * keyword: - * [ - * {"key": "字段名", "value":"值", "logic": "连接联符 (默认为and 非必须 )", operator: "关联符号 (默认为: = 可以为空)"}, - * {"key": "valid", "value": "1"} - * ] - * } - * 返回:{ - * data: [], //数据列表 - * isLastPage:false, //当前页是否为最后页 - * start: 0, //当前页索引 - * length: 10 //页大小 - * } - * author:zack zou - * create time:2017-10-23 - */ - getEntityListByWaterfall(paras, use_cache) { - let self = this; - //定义结构 - return new Promise(function (resolve, reject) { - /** - * 数据缓存暂时做方案预留 - */ - self.db_instance.getEntityListByWaterfall(paras) - .then(function (data) { - resolve(data); - }) - .catch(function (err) { - reject(err); - }); - }); - } - - /** - * 根据动态参数条件查询数据实体对象 - * @param paras 参数模型 - * @param use_cache 是否使用缓存;true表示使用缓存 - * 参数 结构 - * { - * orderBy:{"字段名":"规则(ASC|DESC)" 字段名:默认为 createtime 规则:默认为 DESC } - * keyword: - * [ - * {"key": "字段名", "value":"值", "logic": "连接联符 (默认为and 非必须 )", operator: "关联符号 (默认为: = 可以为空)"}, - * {"key": "valid", "value": "1"} - * ] - * } - * 返回:[] - * author:zack zou - * create time:2017-10-23 - */ - getEntity(paras, use_cache) { - let self = this; - //定义结构 - return new Promise(function (resolve, reject) { - /** - * 数据缓存暂时做方案预留 - */ - self.db_instance.getEntity(paras) - .then(function (data) { - resolve(data); - }) - .catch(function (err) { - reject(err); - }); - }); - } - - /** - * 新增实体模型信息 - * @param paras 参数 - * @param use_cache 是否使用缓存;true表示使用缓存 - * 参数 结构 - * { - * //新增主键id,可缺省,自动赋值,也可以补全id值 - * “字段1”:"值" - * “字段1”:"值" - * ... - * } - * author:zack zou - * create time:2017-10-23 - */ - insertEntity(paras, use_cache) { - let self = this; - //定义结构 - return new Promise(function (resolve, reject) { - /** - * 数据缓存暂时做方案预留 - */ - self.db_instance.insertEntity(paras) - .then(function (data) { - resolve(data); - }) - .catch(function (err) { - reject(err); - }); - }); - } - - /** - * 批量新增实体模型数据 - * @param data 实体列表数组 - * @param use_cache 是否使用缓存;true表示使用缓存 - * 参数结构 - * [{ - * //新增主键id,可缺省,自动赋值,也可以补全id值 - * “字段1”:"值" - * “字段1”:"值" - * ... - * }] - * author:zack zou - * create time:2017-10-23 - */ - insertBatch(paras, use_cache) { - let self = this; - //定义结构 - return new Promise(function (resolve, reject) { - /** - * 数据缓存暂时做方案预留 - */ - self.db_instance.insertBatch(paras) - .then(function (data) { - resolve(data); - }) - .catch(function (err) { - reject(err); - }); - - }); - } - - /** - * 修改实体信息,可以进行运算的字段的更新(加减) - * @param paras 参数(限制字段类型为数字类型) - * @param use_cache 是否使用缓存;true表示使用缓存 - * 参数结构 - *{ - * "update": [ - * //字段:替换更新 - * {"key": "字段1", "value": '值', "operator": "replace"}, - * //字段:累加(数值类型) - * {"key": "字段2", "value": 1, "operator": "plus"}, - * //字段:累减(数值类型) - * {"key": "字段3", "value": 1, "operator": "reduce"} - * ], - * "keyword": [ - * //where条件:一般以主键id作为更新条件,支持多条件组合语句 - * {"key": "条件字段1", "value": '值', "logic": "and", "operator": "="} - * ] - *} - * author:zack zou - * create time:2017-10-23 - */ - updateBySenior(paras, use_cache) { - let self = this; - //定义结构 - return new Promise(function (resolve, reject) { - /** - * 数据缓存暂时做方案预留 - */ - self.db_instance.updateBySenior(paras) - .then(function (data) { - resolve(data); - }) - .catch(function (err) { - reject(err); - }); - }); - } - - /** - * 批量更新数据 - * @param data 实体列表数组 - * @param use_cache 是否使用缓存;true表示使用缓存 - * 参数结构 - * { - * "keyword": [ - * {"key": "查询条件1", "value": "值", "logic": "and", "operator": "="}, - * {"key": "查询条件2", "value": "值", "logic": "and", "operator": "="}, - * {"key": "查询条件3", "value": "值", "logic": "and", "operator": "="} - * ], - * "orderBy": {"排序字段(created_time)": "DESC"}, - * "sumField": "sum求和字段(数值类型)"; - * } - * author:zack zou - * create time:2017-10-23 - */ - updateBatchBySenior(paras, use_cache) { - let self = this; - //定义结构 - return new Promise(function (resolve, reject) { - /** - * 数据缓存暂时做方案预留 - * 批量更新,这个方式是采用遍历数据单条记录更新的方式(不合适大批量的数据更新) - */ - self.db_instance.updateBatchBySenior(paras) - .then(function (data) { - resolve(data); - }) - .catch(function (err) { - reject(err); - }); - }); - } - - /** - * 获取统计信息,count统计 - * @param paras 参数集合,任意参数的组合 - * @param use_cache 是否使用缓存;true表示使用缓存 - * 参数结构 - *{ - * "keyword": [ - * {"key": "查询条件1", "value": "值", "logic": "and", "operator": "="}, - * {"key": "查询条件2", "value": "值", "logic": "and", "operator": "="}, - * {"key": "查询条件3", "value": "值", "logic": "and", "operator": "="} - * ], - * "orderBy": {"排序字段(created_time)": "DESC"}, - * "aggregate":[{"count": "money"}] - *} - * author:zack zou - * create time:2017-10-23 - */ - getEntityByAggregate(paras, use_cache) { - let self = this; - //定义结构 - return new Promise(function (resolve, reject) { - /** - * 数据缓存暂时做方案预留 - */ - self.db_instance.getEntityByAggregate(paras) - .then(function (data) { - resolve(data); - }) - .catch(function (err) { - reject(err); - }); +ToDoManager.transaction().then(t => { + // 先新增一条记录 + ToDoManager.insertEntity({ + "content": "测试" + }, {transaction: t}) + .then(data => { + // 再对新增的记录执行修改 + return ToDoManager.updateEntity({ + "update": [ + {"key": "content", "value": "执行修改测试", "operator": "replace"} // 修改了content字段 + ], + "where": [ + {"logic": "and", "key": "id", operator: "=", "value": data.insertId} + ] + }, {transaction: t}); + }) + .then(data => { + console.log('执行结果', data); + // 事务提交 + t.commit(() => { + t.release(); + }); + }) + .catch(ex => { + console.log('事务异常回滚', ex.message); + // 事务回滚 + t.rollback(() => { + t.release(); + }); }); - } +}); - /** - * 物理删除数据实体对象 - * @param paras 删除条件模型 - * 参数 结构 - * { - * keyword: - * [ - * {"key": "字段名", "value":"值", "logic": "连接联符 (默认为and 非必须 )", operator: "关联符号 (默认为: = 可以为空)"} - * ] - * } - * author:zack zou - * create time:2017-10-23 - */ - deleteEntity(paras) { - let self = this; - //定义结构 - return new Promise(function (resolve, reject) { - /** - * 数据缓存暂时做方案预留 - */ - self.db_instance.deleteEntity(paras) - .then(function (data) { - resolve(data); - }) - .catch(function (err) { - reject(err); - }); - }); - } -} /** - * 方法块:指向具体的数据库实例对象 - * 假如存在方法扩展,直接在这里写入新的方法 + * 单例模式:数据查询 */ -class entity_name extends base { - - - /** - * 根据id获取实体对象 - * @param id 主键id - * @param use_cache 是否使用缓存;true表示使用缓存 - * 参数 结构 - * { - * orderBy:{"字段名":"规则(ASC|DESC)" 字段名:默认为 createtime 规则:默认为 DESC } - * keyword: - * [ - * {"key": "字段名", "value":"值", "logic": "连接联符 (默认为and 非必须 )", operator: "关联符号 (默认为: = 可以为空)"}, - * {"key": "valid", "value": "1"} - * ] - * } - * 返回:{...}实体对象 - * author:zack zou - * create time:2017-10-23 - */ - getEntityById(id, use_cache) { - let self = this; - //定义结构 - return new Promise(function (resolve, reject) { - /** - * 数据缓存暂时做方案预留 - */ - let condition = { - "keyword": [ - {"logic": "and", "key": 'id', "operator": "=", "value": id} - ] - } - self.db_instance.getEntity(condition) - .then(function (data) { - if (data && data.length > 0) - resolve(data[0]); - else { - //Not find - resolve(null); - } - }) - .catch(function (err) { - reject(err); - }); - }); - } - - /** - * 根据ids获取实体对象 - * @param id 主键ids - * @param use_cache 是否使用缓存;true表示使用缓存,单个数组数量不宜太多 - * 参数 结构 - * [ - * "id1", - * "id2" - * ] - * 返回:{...}实体对象 - * author:zack zou - * create time:2017-10-23 - */ - getEntityByIds(ids, use_cache) { - let self = this; - //定义结构 - return new Promise(function (resolve, reject) { - /** - * 数据缓存暂时做方案预留 - */ - let condition = { - "keyword": [ - {"logic": "and", "key": 'id', "operator": "in", "value": ids} - ] - } - self.db_instance.getEntity(condition) - .then(function (data) { - if (data && data.length > 0) - resolve(data); - else { - //Not find - resolve(null); - } - }) - .catch(function (err) { - reject(err); - }); - }); - } +ToDoManager.getEntity({ + where: [ + //{"logic": "and", "key": "id", "operator": "=", "value": 1} + ] +}, null).then(data => { + console.log('查询结果', data) +}).then(); +/** + * 单例模式:新增 + */ +ToDoManager.insertEntity({ + "content":"测试" +}).then(data=>{console.log('查询结果',data)}); +/** + * 单例模式:分页查询 + */ +ToDoManager.getEntityList({ + "where": [ + //{"logic": "and", "key": "id", "operator": "=", "value": 1} + ] +}).then(console.log); +/** + * 单例模式:新增 + */ +ToDoManager.insertEntity({ + content: "设计智能保险顾问的用户体系" +}).then(console.log); +/** + * 单例模式:批量新增 + */ +ToDoManager.insertBatch([ + {content: "测试1"}, + {content: "测试2"}, + {content: "测试3"} +]).then(console.log); +/** + * 单例模式:删除(物理删除,不推荐使用) + */ +ToDoManager.deleteEntity({ + "where": [ + {"key": "id", operator: "in", value: [12360,12361], logic: "and"}, + // {"key": "is_done", operator: "=", value: 1, logic: "and"} + ] +}).then(console.log); -} +/** + * 单例模式:更新(对于删除,建议使用逻辑删除) + */ +ToDoManager.updateEntity({ + update: [ + {key: "is_done", value: 1, operator: "replace"} + ], + where: [ + {"key": "id", operator: "in", value: [12362], logic: "and"}, + ] +}).then(console.log); -module.exports = entity_name; +/** + * 单例模式:实时统计 + */ +ToDoManager.getEntityByAggregate({ + // where: + "aggregate":[ + {"function": "count", "field": "is_done", "name": "undone_tasks"}, + ] +}).then(console.log); ``` Ok, you can now play happily~ - - ### Use instance to show(方法使用示例) #### Query example(示例:查询) @@ -701,9 +244,9 @@ There are several ways to apply the query to different business scenarios. Pagin ~~~~~~ //parameter - var p = { + let p = { "select": ["t.id"], //Specify the output field, query all fields, use t. * Or select attributes by default - "keyword": [ + "where": [ {"logic": "and", "key": 'valid', "operator": "=", "value": 1}, {"logic": "and", "key": 'id', "operator": "=", "value": id} ], @@ -711,7 +254,7 @@ There are several ways to apply the query to different business scenarios. Pagin "limit": [0, 1] //Take the first data of the query results } //execute select - db_instance.getEntity(p) + ToDoManager.getEntity(p) .then(function (data) { resolve(data); }) @@ -728,7 +271,7 @@ There is also a new batch method db_instance.insertBatch(arr),The incoming value ~~~~~~ //parameter - var p = { + let p = { "name":"Sandy", "sex":"female", "email":"sandy@xxx.com" @@ -736,8 +279,8 @@ There is also a new batch method db_instance.insertBatch(arr),The incoming value //Other fields are added in turn } //execute insert - db_instance.insertEntity(p) - .then(function (data) { + ToDoManager.insertEntity(p) + .then((data)=> { resolve(data); }) .catch(function (err) { @@ -763,14 +306,14 @@ There are two main ways to update the field,replace or plus {"key": "score", "value": 1, "operator": "reduce"} ], - "keyword": [ + "where": [ //where条件:一般以主键id作为更新条件,支持多条件组合语 {"logic": "and","key": "id", "operator": "=", "value": 'abc'} ] } //execute update - db_instance.updateBySenior(p) - .then(function (data) { + ToDoManager.updateEntity(p) + .then((data)=> { resolve(data); }) .catch(function (err) { @@ -787,16 +330,16 @@ Physical deletion, generally do not recommend this operation, it is recommended ~~~~~~ //parameter - var p = { - "keyword": [ + let p = { + "where": [ //Allow multiple query conditions //{"key": "字段名1", "value": "值", "logic": "连接联符 (默认为and 非必须 )", operator: "关联符号 (默认为: =)"}, {"key": "id", "value": "abc", "logic": "and", operator: "="} ] } //execute delete - db_instance.deleteEntity(p) - .then(function (data) { + ToDoManager.deleteEntity(p) + .then((data=>) { resolve(data); }) .catch(function (err) { @@ -814,199 +357,50 @@ Can only achieve local Transaction ~~~~~~ /** - * 新增附件关联信息(单文件关联) - * 基于node.js事务(mysql)事务执行的案例,基于onela架构实现 - * @param 参数结构如下 - * { - * "record_id":"", //关联记录id - * "files_id":"", //附件id - * "sign":"业务标识", //业务标识 - * "oper_code":"" //操作代码 - * } - * author:zack zou - * create time:2017-04-27 + * 事务 */ -m.addFileRelation = function (paras) { - //业务执行 - return new Promise(function (resolve, reject) { - /** - * 数据源对象,从数据库实例对象中获取,oodbc是标准数据源配置文件 - * oodbc结构里面是允许配置不同种类的数据(Mysql、Oracle等),同时允许数据库的多实例垂直拆分(解决高IO问题) - * 所以onela在执行本地事务是有限制的,注意onela执行本地事务只能同一个实例连接池里面执行,对于已经垂直拆分数据库多实例的情况在这个方案中无法实施 - * 下面的事务在同一个数据库实例的例子。 - */ - var db = oodbc.MYSQL.DEFAULT.WRITER; //假如读写分离的情况,直接使用读写库的实例 - /** - * 验证事务处理 - * 本地事务,必须保证操作对象在同一个数据服务实例里面,否则不能实现事务 - * 创建事务连接对象 - */ - db.getConnection(function (err, connection) { - if (err) { - throw err; - } - //需要执行事务的实例必须保证在同一个connection池里面,所以必须重新新建oodbc - var proc_oodb = { - "MYSQL": { - "DEFAULT": { - "READER": connection, //本地事务必须在同一个数据实例中执行 - "WRITER": connection //本地事务必须在同一个数据实例中执行 - } - } - } - //数据库对象实例化 - var proc_com_files_rel = onela(proc_oodb, onelaInstanceConfig.tables.com_files_rel); - var proc_com_files = onela(proc_oodb, onelaInstanceConfig.tables.com_files); - console.log('开始执行事务'); - //开始事务:必须在同一个连接池中执行事务才会生效 - connection.beginTransaction(function (err) { - if (err) { - throw err; - } - //先新增附件关联,然后再更新附件状态 - //insert数据库字段默认值处理 - paras.remark = ""; - paras.created_id = ""; - paras.update_id = ""; - //先新增附件关联信息 - proc_com_files_rel.insertEntity(paras, false) - .then(function (data) { - /** - * 新增附件关联信息成功,修改附件的使用状态 - */ - var p = { - "update": [ - {"key": "status", "value": "use", "operator": "replace"} - ], - "keyword": [ - {"logic": "and", "key": "id", operator: "=", "value": paras.files_id} - ] - }; - return proc_com_files.updateBySenior(p, false); - }) - .then(function (data) { - // 提交事务 - connection.commit(function (err) { - if (err) { - //提交事务异常,执行事务回滚 - console.log('提交事务异常,执行事务回滚', err); - connection.rollback(function () { - reject(err); - }); - } - else { - console.log('success!'); - resolve("附件关联更新成功"); - } - }); - }) - .catch(function (err) { - //出现异常,事务回滚 - console.log('出现异常,执行事务回滚', err); - connection.rollback(function (err) { - console.log('事务错误', err); - reject("执行事务提交出错"); - }); - }); +ToDoManager.transaction().then(t => { + // 先新增一条记录 + ToDoManager.insertEntity({ + "content": "测试" + }, {transaction: t}) + .then(data => { + // 再对新增的记录执行修改 + return ToDoManager.updateEntity({ + "update": [ + {"key": "content", "value": "执行修改测试", "operator": "replace"} // 修改了content字段 + ], + "where": [ + {"logic": "and", "key": "id", operator: "=", "value": data.insertId} + ] + }, {transaction: t}); + }) + .then(data => { + console.log('执行结果', data); + // 事务提交 + t.commit(() => { + t.release(); }); - }); - }); -}; -~~~~~~ - - - -#### stored procedure example(示例:存储过程) - -Onela An example of executing a stored procedure - -~~~~~~ -/** - * 初始化实例对象,存储过程列表 - * new 一次和new多次对高并发是有很大影响的,这里采用new一次的做法 - */ -var proc_instance = { - "usp_user_info_getUserInfoList": onela.proc(oodbc, onelaInstanceConfig.proc.usp_user_info_getUserInfoList) -} - -/** - * 存储过程方式:分页查询用户数据 - * @param paras - * { - * "_start":0, - * "_length":10 - * } - */ -m.procGetUserInfoList = function (paras) { - return new Promise(function (resolve, reject) { - //执行存储过程 - proc_instance.usp_user_info_getUserInfoList.call(paras) - .then(function (data) { - resolve(data); - }) - .catch(function (ex) { - reject(ex); + }) + .catch(ex => { + console.log('事务异常回滚', ex.message); + // 事务回滚 + t.rollback(() => { + t.release(); }); - }); -} -~~~~~~ - - - -### Version update log(版本更新日志) - -#### v1.2.0 (2017-05-27 update): Increase the bulk update - -v1.2.0 (2017-05-27 更新):增加批量更新的方式 - -Added updateBySenior () to update the new features of the condition, support the update of the "case when then" - -增加updateBySenior()更新条件的新特性,支持字段的"case when then" 的更新方式 - -~~~~~~ -var p = { - "update": [ - //operator:replace update(替换更新) - {"key": "name", "value": 'Sandy', "operator": "replace"}, - //Field batch update(字段批量更新) - { - "key": "balance", //update field - "case_field": "id", //balance = CASE id - "case_item": [ - {"case_value": "001", "value": 1, "operator": "replace"}, //WHEN '001' THEN 1 - {"case_value": "002", "value": 2, "operator": "replace"} //WHEN '001' THEN balance+2 - ] - } - ], - "keyword": [ - //where条件:一般以主键id作为更新条件,支持多条件组合语 - {"logic": "and","key": "id", "operator": "=", "value": 'abc'} - ] - } + }); +}); ~~~~~~ -#### v1.2.1 (2017-08-22 update): insert data defect repair - -v1.2.1 (2017-08-22 更新):修复新增默认字段的bug修复 - - - -#### v1.3.0 (2017-10-26 update):Separation tool class method - -分离onela中分辅助工具类方法,onela-tools项目是基于onela的代码工具类项目,方便更快更便捷的构建项目代码。分离之后的onela将更加纯净,后续版本升级计划将专注ORM框架本身,敬请期待。 - -onela-tools项目地址:https://github.com/zouwei/onela-tools - - - -#### v1.3.1 (2017-11-08 update):bug repair - -v1.3.1 (2017-11-08 更新):修复bug +#### v2.0.0(2018-04-23):onela新版发布 -#### v1.3.2 (2018-01-03 update):bug repair +* 面向对象编程 +* 加强了数据表配置结构字段、类型、默认值,等配置,语义设计更规范 +* 兼容老版本代码(需要在引用的时候区分下) +* 代码简化,例如事务处理 +* 简化项目初始化配置代码,使用更加方便 -v1.3.2 (2018-01-03 更新):修复新增以及批量新增默认值导致的报错 \ No newline at end of file diff --git a/lib/BaseActionManager.js b/lib/BaseActionManager.js new file mode 100644 index 0000000..a2e862a --- /dev/null +++ b/lib/BaseActionManager.js @@ -0,0 +1,12 @@ +/** + * SQL实例基类 + */ +class BaseActionManager { + + static init(config) { + throw new Error("not implemented"); + } + +} + +module.exports = {BaseActionManager}; \ No newline at end of file diff --git a/lib/OFInstance.js b/lib/OFInstance.js index 6e814f5..d9309b2 100644 --- a/lib/OFInstance.js +++ b/lib/OFInstance.js @@ -1,9 +1,10 @@ /** + * 【老版本兼容,新版本弃用】 * 通用模块-命令行参数处理 * author:zack zou * create time:2016-06-27 */ -var oParameters = require("./oParameters.js"); +var oParameters = require("./grammar/mysql.js"); /** diff --git a/lib/OFramework.js b/lib/OFramework.js index 972e5ad..57ae7e6 100644 --- a/lib/OFramework.js +++ b/lib/OFramework.js @@ -1,10 +1,11 @@ /** + * 【老版本兼容,新版本弃用】 * OFramework 统一底层架构 * author:zack zou * create time:2017-03-23 */ -var ofInstance = require("./OFInstance.js"); +const ofInstance = require("./OFInstance.js"); /** * SQL模型ORM映射对象 diff --git a/lib/grammar/mysql.js b/lib/grammar/mysql.js new file mode 100644 index 0000000..23b9899 --- /dev/null +++ b/lib/grammar/mysql.js @@ -0,0 +1,495 @@ +/** + * 通用模块-命令行参数处理 + * author:zack zou + * create time:2016-06-27 + */ + +let m = {} + +/** + * 获取分页参数,封装成执行SQL参数化的对象 + * @param paras 原始参数集合 + * { + * "select":[], //需要查询的字段,可缺省,即表示“*” + * "keyword":[] + * "orderBy":{} + * } + * author:zack zou + * create time:2016-06-27 + */ +m.getParameters = function (paras) { + //返回的参数集合 + let _self = {'select': [], 'where': ' where 1=1 ', 'orderBy': '', 'parameters': [], "limit": ""}; + + /** + * 指定字段查询,可以包含聚合函数 + * paras.select是数组对象 + */ + if (paras.select && typeof paras.select === "object") { + if (paras.select.length == 0) + _self.select = 't.*'; + else if (paras.select.length == 1) + _self.select = paras.select[0]; + else + _self.select = paras.select.join(','); + } + else { + //如果没有包含这个参数,默认查询全部数据 + _self.select = 't.*'; + } + + /** + * 排序 + */ + if (paras.hasOwnProperty('orderBy') && paras.orderBy != null) { + /** + * 遍历排序数组 + * 支持多字段排序 + * @orderBy {"order":"ASC"} + */ + for (let i in paras.orderBy) { + if (_self.orderBy === '') { + _self.orderBy += ' order by ' + i + ' ' + paras.orderBy[i] + ' '; + } + else { + _self.orderBy += ',' + i + ' ' + paras.orderBy[i] + ' '; + } + } + } + + /** + * where条件以及参数处理 + * keyword:查询条件 + */ + paras.keyword = paras.keyword || paras.where; + //遍历查询条件参数 + for (let i in paras.keyword) { + /** + * keyword里面是对象数组{"key":"","value":"","logic":"and","operator:"="} + */ + if (paras.keyword[i] != '') { + /** + * 默认逻辑处理,允许部分参数不填写 + */ + if (!paras.keyword[i].hasOwnProperty('logic')) { + paras.keyword[i].logic = "and"; + } + _self.where += " " + paras.keyword[i].logic + " " + paras.keyword[i].key + " "; + + //逻辑运算 + if (paras.keyword[i].hasOwnProperty('operator')) { + let oper = paras.keyword[i].operator; + switch (oper) { + case '=': + case ">": + case "<": + case "<>": + case ">=": + case "<=": + _self.where += (oper + "?"); + //参数化 + _self.parameters.push(paras.keyword[i].value); + break; + case "in": + // //包含查询,利用数据遍历的方式实现 + // let p = []; + // for (let c in paras.keyword[i].value) { + // //参数化 + // _self.parameters.push(paras.keyword[i].value[c]); + // p.push('?'); + // } + // _self.where += "in (" + p.join(',') + ")"; + // break; + case "not in": + //包含查询,利用数据遍历的方式实现 + let p = []; + for (let c in paras.keyword[i].value) { + //参数化 + _self.parameters.push(paras.keyword[i].value[c]); + p.push('?'); + } + _self.where += " " + oper + " (" + p.join(',') + ")"; + break; + case '%': + //模糊查询,logic需要指定link逻辑运算 + //左侧模糊匹配查询 + _self.where += "like ?"; + //参数化 + _self.parameters.push('%' + paras.keyword[i].value); + break; + case 'x%': + //模糊查询,logic需要指定link逻辑运算 where f like ? + //右侧模糊匹配查询 + _self.where += "like ?"; + //参数化 + _self.parameters.push(paras.keyword[i].value + '%'); + break; + case '%%': + //模糊查询,logic需要指定link逻辑运算 + _self.where += "like ?"; + //参数化 + _self.parameters.push('%' + paras.keyword[i].value + '%'); + break; + case 'is': + _self.where += "is " + paras.keyword[i].value; + break; + default: + _self.where += ""; + break; + } + } + else { + //运算符 + _self.where += "=?" + //参数化 + _self.parameters.push(paras.keyword[i].value); + } + + + } + } + /** + * 检测是否存在limit + */ + if (paras.limit && paras.limit.length > 1) { + _self.limit = " limit ?,?"; + _self.parameters.push(paras.limit[0]); + _self.parameters.push(paras.limit[1]); + } + /** + * 返回结果 + */ + return _self; +}; + + +/** + * 获取更新参数 + * @param paras 原始参数集合 + * author:zack zou + * create time:2016-06-27 + */ +m.getUpdateParameters = function (paras) { + //返回的参数集合 + let _self = {"set": [], "where": [], "parameters": []}; + + /** + * 更新字段 + * update:需要更新的字段 + */ + //遍历查询条件参数 + for (let i in paras.update) { + /** + * update里面是对象数组{"key":"","value":"","operator":"replace"} + * + { + "key": "payment_no", + "case_field": "id", + "case_item": [{"case_value": "123", "value": "1", "operator": "replace"}] + } + */ + + + if (paras.update[i] == '') { + continue; + } + + + //判断字段更新模式,常规更新还是case when then更新方式 + if (paras.update[i].hasOwnProperty('case_field')) { + //遍历节点 + let item = paras.update[i]; + console.log('传入参数', item); + // let kkk = { + // "key": "payment_no", + // "case_field": "id", + // "case_item": [ + // {"case_value": "123", "value": "1", "operator": "replace"} + // ] + // } + + //条件判断 + if (!(item.case_item instanceof Array) || item.case_item.length == 0) { + //条件不符合 + continue; + } + + console.log('开始执行111'); + // balance = CASE id + // WHEN '1' THEN balance+2 + // WHEN '2' THEN balance+20 + // END + + + //开头 + let case_str = []; + case_str.push(item.key + '= (CASE ' + item.case_field); + //循环case_item分支 + for (let cw in item.case_item) { + /** + * 默认逻辑处理,默认替换更新 + */ + if (!item.case_item[cw].hasOwnProperty('operator')) { + item.case_item[cw].operator = "replace"; + } + //更新参数处理 + let oper = item.case_item[cw].operator; + switch (oper) { + case 'replace': + /** + * 值替换 // WHEN '1' THEN balance+2 + */ + case_str.push("WHEN ? THEN ?"); + _self.parameters.push(item.case_item[cw].case_value); + _self.parameters.push(item.case_item[cw].value); + break; + case "plus": + /** + * 值累加 + */ + case_str.push("WHEN ? THEN " + item.key + " + ?"); + _self.parameters.push(item.case_item[cw].case_value); + _self.parameters.push(item.case_item[cw].value); + break; + case "reduce": + /** + * 值累减 + */ + case_str.push("WHEN ? THEN " + item.key + " - ?"); + _self.parameters.push(item.case_item[cw].case_value); + _self.parameters.push(item.case_item[cw].value); + break; + default: + /** + * 其他默认为值替换更新 + */ + case_str.push("WHEN ? THEN ? "); + _self.parameters.push(item.case_item[cw].case_value); + _self.parameters.push(item.case_item[cw].value); + break; + } + } + //结尾 + case_str.push("END) "); + //追加到参数模型 + _self.set.push(case_str.join(' ')); + + } + else { + + /** + * 默认逻辑处理,默认替换更新 + */ + if (!paras.update[i].hasOwnProperty('operator')) { + paras.update[i].operator = "replace"; + } + //更新参数处理 + let oper = paras.update[i].operator; + switch (oper) { + case 'replace': + /** + * 值替换 + */ + _self.set.push(paras.update[i].key + '=?'); + _self.parameters.push(paras.update[i].value); + break; + case "plus": + /** + * 值累加 + */ + _self.set.push(paras.update[i].key + '=' + paras.update[i].key + "+ ?"); + _self.parameters.push(paras.update[i].value); + break; + case "reduce": + /** + * 值累减 + */ + _self.set.push(paras.update[i].key + '=' + paras.update[i].key + "- ?"); + _self.parameters.push(paras.update[i].value); + break; + default: + /** + * 其他默认为值替换更新 + */ + _self.set.push(paras.update[i].key + '=?'); + _self.parameters.push(paras.update[i].value); + break; + } + } + } + + /** + * where条件以及参数处理 + * keyword:查询条件 + */ + paras.keyword = paras.keyword || paras.where; + //遍历查询条件参数 + for (let i in paras.keyword) { + /** + * keyword里面是对象数组{"key":"","value":"","logic":"and","operator:"="} + */ + if (paras.keyword[i] != '') { + /** + * 默认逻辑处理,允许部分参数不填写 + */ + if (!paras.keyword[i].hasOwnProperty('logic')) { + paras.keyword[i].logic = "and"; + } + + //逻辑运算 + if (paras.keyword[i].hasOwnProperty('operator')) { + let oper = paras.keyword[i].operator; + switch (oper) { + case '=': + case ">": + case "<": + case "<>": + case ">=": + case "<=": + _self.where.push(' ' + paras.keyword[i].logic + ' ' + paras.keyword[i].key + oper + "?"); + //参数化 + _self.parameters.push(paras.keyword[i].value); + break; + case '%': + //模糊查询,logic需要指定link逻辑运算 + //左侧模糊匹配查询 + _self.where.push(' ' + paras.keyword[i].logic + ' ' + paras.keyword[i].key + oper + "?"); + //参数化 + _self.parameters.push('%' + paras.keyword[i].value); + break; + case 'x%': + //模糊查询,logic需要指定link逻辑运算 + //右侧模糊匹配查询 + _self.where.push(' ' + paras.keyword[i].logic + ' ' + paras.keyword[i].key + oper + "?"); + //参数化 + _self.parameters.push(paras.keyword[i].value + '%'); + break; + case '%%': + //模糊查询,logic需要指定link逻辑运算 + _self.where.push(' ' + paras.keyword[i].logic + ' ' + paras.keyword[i].key + oper + "?"); + //参数化 + _self.parameters.push('%' + paras.keyword[i].value + '%'); + break; + case "in": + case "not in": + let _item = ' ' + paras.keyword[i].logic + ' ' + paras.keyword[i].key + " " + oper + " (" + //包含查询,利用数据遍历的方式实现 + let p = []; + for (let c in paras.keyword[i].value) { + //参数化 + _self.parameters.push(paras.keyword[i].value[c]); + p.push('?'); + } + _item += p.join(',') + ")"; //sql语句 + _self.where.push(_item); + break; + } + } + + } + } + /** + * where条件字符串组装 + */ + _self.where = ' 1=1 ' + _self.where.join(''); + /** + * 返回结果 + */ + return _self; +} + +/** + * 获取实例删除参数 + * 注意,一般情况下不推荐直接物理删除 + * @param paras 原始参数集合 + * { + * "keyword":[] + * } + * author:zack zou + * create time:2017-04-06 + */ +m.getDeleteParameters = function (paras) { + //返回的参数集合 + let _self = {'where': [], 'parameters': []}; + + /** + * where条件以及参数处理 + * keyword:查询条件 + */ + paras.keyword = paras.keyword || paras.where; + //遍历查询条件参数 + for (let i in paras.keyword) { + /** + * keyword里面是对象数组{"key":"","value":"","logic":"and","operator:"="} + */ + if (paras.keyword[i] != '') { + /** + * 默认逻辑处理,允许部分参数不填写 + */ + if (!paras.keyword[i].hasOwnProperty('logic')) { + paras.keyword[i].logic = "and"; + } + + //逻辑运算 + if (paras.keyword[i].hasOwnProperty('operator')) { + let oper = paras.keyword[i].operator; + switch (oper) { + case '=': + case ">": + case "<": + case "<>": + case ">=": + case "<=": + _self.where.push(' ' + paras.keyword[i].logic + ' ' + paras.keyword[i].key + oper + "?"); + //参数化 + _self.parameters.push(paras.keyword[i].value); + break; + case '%': + //模糊查询,logic需要指定link逻辑运算 + //左侧模糊匹配查询 + _self.where.push(' ' + paras.keyword[i].logic + ' ' + paras.keyword[i].key + oper + "?"); + //参数化 + _self.parameters.push('%' + paras.keyword[i].value); + break; + case 'x%': + //模糊查询,logic需要指定link逻辑运算 + //右侧模糊匹配查询 + _self.where.push(' ' + paras.keyword[i].logic + ' ' + paras.keyword[i].key + oper + "?"); + //参数化 + _self.parameters.push(paras.keyword[i].value + '%'); + break; + case '%%': + //模糊查询,logic需要指定link逻辑运算 + _self.where.push(' ' + paras.keyword[i].logic + ' ' + paras.keyword[i].key + oper + "?"); + //参数化 + _self.parameters.push('%' + paras.keyword[i].value + '%'); + break; + case "in": + case "not in": + let _item = ' ' + paras.keyword[i].logic + ' ' + paras.keyword[i].key + " " + oper + " (" + //包含查询,利用数据遍历的方式实现 + let p = []; + for (let c in paras.keyword[i].value) { + //参数化 + _self.parameters.push(paras.keyword[i].value[c]); + p.push('?'); + } + _item += p.join(',') + ")"; //sql语句 + _self.where.push(_item); + break; + } + } + + } + } + /** + * where条件字符串组装 + */ + _self.where = ' 1=1 ' + _self.where.join(''); + /** + * 返回结果 + */ + return _self; +}; + +module.exports = m; \ No newline at end of file diff --git a/lib/instance/MySQLActionManager.js b/lib/instance/MySQLActionManager.js new file mode 100644 index 0000000..bb7ec20 --- /dev/null +++ b/lib/instance/MySQLActionManager.js @@ -0,0 +1,260 @@ +/** + * MySql对象关系实例 + */ +const {BaseActionManager} = require('../BaseActionManager'); +// 语法处理 +const GrammarMysql = require("../grammar/mysql.js"); +/** + * MYSQL + * 单例的数据库操作管理者,负责这个数据库的基本crud,负责全局的一个连接; + */ +class MySQLActionManager extends BaseActionManager { + + /** + * 数据库初始化 + * @param config + */ + static init(config) { + const mysql = require("mysql"); + let connPool = mysql.createPool(config); + this.conn = connPool; + } + + // 创建事务连接 + static createTransaction() { + let self = this; + return new Promise(function (resolve, reject) { + self.conn.getConnection(function (err, connection) { + if (err) { + console.log('创建事务连接出现异常', err); + reject(new Error('创建事务连接出现异常')); + } else { + resolve(connection); + } + }); + }); + } + + // 执行SQL + static execute(sql, parameters) { + let self = this; + return new Promise(function (resolve, reject) { + if (self.conn) { + //console.time("【onela】执行SQL时间"); + self.conn.query(sql, parameters, function (err, doc) { + //console.timeEnd("【onela】执行SQL时间"); + if (err) { + reject(err); + } + else { + resolve(doc); + } + }); + } + else { + reject(new Error("数据库实例engine实例未正确指向,请检查单例configs配置是否跟dbconfig配置的engine一致")); + } + }); + } + + // 执行带事务的实例对象 + static executeTransaction(sql, parameters, transaction) { + + return new Promise((resolve, reject) => { + // console.log(sql); + if (transaction) { + //console.time("【onela】执行SQL时间"); + // 事务连接池 + transaction.query(sql, parameters, function (err, doc) { + //console.timeEnd("【onela】执行SQL时间"); + if (err) { + reject(err); + } + else { + resolve(doc); + } + }); + } + else { + reject(new Error("数据库实例engine实例未正确指向,请检查单例configs配置是否跟dbconfig配置的engine一致")); + } + }); + } + + static queryEntity(params, option = {"transaction": null}) { + let self = this; + let p = GrammarMysql.getParameters(params); + console.log('参数》》', params.configs) + let sql = "select " + p["select"] + " from " + params.configs.tableName + " as t " + p.where + p.orderBy + p.limit + ";"; + // 执行SQL + if (option && option.transaction ) { + // 事务连接池 + return self.executeTransaction(sql, p.parameters, option.transaction) + .catch(err => { + console.log('执行execute查询数据列表出错', err); + return Promise.reject(err); + }); + } + else { + return self.execute(sql, p.parameters) + .catch(err => { + console.log('执行execute查询数据列表出错', err); + return Promise.reject(err); + }); + } + } + + static queryEntityList(params, option = {"transaction": null}) { + let self = this; + let p = GrammarMysql.getParameters(params); + //变量定义 + let result = { + "data": [], //数据列表 + "recordsTotal": 0 //查询记录总数 + }; + /** + * 分页数据查询 + */ + let sql = "select " + p["select"] + " from " + params.configs.tableName + " t " + p.where + " " + p.orderBy + p.limit + ";"; + let count_sql = "select count(0) total from " + params.configs.tableName + " t " + p.where; + console.log('事务对象',option.transaction) + //执行SQL + if (option && option.transaction ) { + return Promise.all([ + self.executeTransaction(sql, p.parameters, option.transaction), + self.executeTransaction(count_sql, p.parameters, option.transaction) + ]).then(result => { + return Promise.resolve({data: result[0], recordsTotal: result[1]}); + }).catch(ex => { + console.log('执行execute查询数据列表出错', ex); + return Promise.reject(ex); + }); + + } + else { + return Promise.all([ + self.execute(sql, p.parameters), + self.execute(count_sql, p.parameters) + ]).then(result => { + return Promise.resolve({data: result[0], recordsTotal: result[1]}); + }).catch(ex => { + console.log('执行execute查询数据列表出错', ex); + return Promise.reject(ex); + }); + } + + } + + static insert(params, option = {"transaction": null}) { + let p = [], f = [], s = []; + for (let i in params.insertion) { + //参数值 + p.push(params.insertion[i]); + //字段名称集合 + f.push(i); + //sql参数化处理符合 + s.push('?'); + } + let sql = "insert into " + params.configs.tableName + "(" + f.join(',') + ") values(" + s.join(',') + ");"; + + // 执行SQL + if (option && option.transaction ) { + return this.executeTransaction(sql, p, option.transaction); + } + else { + return this.execute(sql, p); + } + } + + static insertBatch(params, option = {"transaction": null}) { + let p = [], f = [], s = []; + for (let i in params.insertion) { + let s2 = []; + for (let j in params.insertion[i]) { + if (i == 0) { + //字段名称集合,大于0就不需要继续了 + f.push("`" + j + "`"); + } + //参数值 + p.push(params.insertion[i][j]); + //sql参数化处理符合 + s2.push('?'); + } + //置入 + s.push('(' + s2.join(',') + ')'); + } + //SQL执行 + let sql = "insert into " + params.configs.tableName + "(" + f.join(',') + ") values" + s.join(',') + ";"; + // 执行SQL + if (option && option.transaction ) { + return this.executeTransaction(sql, p, option.transaction); + } + else { + return this.execute(sql, p); + } + } + + static deleteEntity(params, option = {"transaction": null}) { + if ((!params.hasOwnProperty('keyword') || params.keyword.length == 0) && (!params.hasOwnProperty('where') || params.where.length == 0)) { + return Promise.reject('需要指定删除条件,防止整表数据误删除'); + } + let p = GrammarMysql.getDeleteParameters(params); + let sql = "delete from " + params.configs.tableName + " where " + p.where + ";"; + + // 执行SQL + if (option && option.transaction ) { + return this.executeTransaction(sql, p.parameters, option.transaction); + } + else { + return this.execute(sql, p.parameters); + } + } + + static updateEntity(params, option = {"transaction": null}) { + let p = GrammarMysql.getUpdateParameters(params); + let _limit = ""; + if (params.limit && params.hasOwnProperty('limit')) { + _limit = " limit ?"; + p.parameters.push(params.limit); + } + let sql = "update " + params.configs.tableName + " set " + p.set.join(',') + " where " + p.where + _limit + ";"; + // 执行SQL + if (option && option.transaction ) { + return this.executeTransaction(sql, p.parameters, option.transaction); + } + else { + return this.execute(sql, p.parameters); + } + } + + static statsByAggregate(params, option = {"transaction": null}) { + let p = GrammarMysql.getParameters(params); + let check = { + "count": "COUNT", + "sum": "SUM", + "max": "MAX", + "min": "MIN", + "abs": "ABS", + "avg": "AVG" + }; + let show = []; + for (let i in params.aggregate) { + let c = params.aggregate[i]; + let item = check[c.function.toLowerCase()]; + if (item) { + show.push(item + "(" + c.field + ") as " + c.name); + } + } + //sql + let sql = "select " + show.join(',') + " from " + params.configs.tableName + " " + p.where + p.limit + ";"; + // 执行SQL + if (option && option.transaction ) { + return this.executeTransaction(sql, p.parameters, option.transaction); + } + else { + return this.execute(sql, p.parameters); + } + } +} + +module.exports = {MySQLActionManager}; \ No newline at end of file diff --git a/lib/onela.js b/lib/onela.js new file mode 100644 index 0000000..5106d18 --- /dev/null +++ b/lib/onela.js @@ -0,0 +1,281 @@ +const {MySQLActionManager} = require("./instance/MySQLActionManager"); + + +/** + * 负责多个database的管理,能够初始化数据库连接 + */ +class Onela { + + /** + * 数据库实例对象识别 + * @param db_type 数据类型 + * @returns {MySQLActionManager} + */ + static getActionManagerClass(db_type) { + switch (db_type) { + case "mysql": + return MySQLActionManager; + default: + return MySQLActionManager; + } + } + + /** + * 数据库实例初始化(多类型多实例) + * @param config_list + */ + static init(config_list) { + let self = this; + for (let tempConfig of config_list) { + let temp_am = self.getActionManagerClass(tempConfig.type); + temp_am.init(tempConfig.value); + self._connections[tempConfig.engine] = temp_am; + + } + } + + static getActionManager(engine) { + let self = this; + if (!(engine in this._connections)) { + throw new Error(`invalid engine: ${engine}`); + } + return Promise.resolve(self._connections[engine]); + } + + /** + * 获取事务实例对象 + * @param name + */ + static getActionTransaction(engine) { + // 连接对象 + let self = this; + // 检测链接池是否存在 + if (!(engine in this._connections)) { + throw new Error(`invalid engine: ${engine}`); + } + + // 获取事务对象 + return self._connections[engine].createTransaction() + .then(connection => { + return Promise.resolve(connection); + }) + .catch(ex => { + return Promise.reject(ex); + }); + + } +} + +/** + * 连接对象 + * 一般情况下需要一个数据库连接对象即可,onela在框架设计上直接支持多个不同类型的数据库同时创建连接 + * 事务只能在同一个connection对象里面才回生效 + */ +Onela._connections = {}; + +/** + * 模型的基类,负责该模型的crud基本操作 + */ +class OnelaBaseModel { + + static getActionManager() { + if (!(this.action_manager)) { + this.action_manager = Onela.getActionManager(this.configs.engine); + } + + return this.action_manager; + } + + /** + * 获取事务连接对象 + * @returns {*} + */ + static transaction() { + + return new Promise((resolve, reject) => { + Onela.getActionTransaction(this.configs.engine).then(connection => { + + // 直接开始事务 + connection.beginTransaction(function (err) { + if (err) { + throw err; + } + + // 直接返回已经开始事务的连接池 + resolve(connection); + }); + }); + }); + + } + + /** + * 查询实体对象 + * @param params + * @param option + * @returns {Promise.} + */ + static getEntity(params, option) { + // let options = Object.assign({}, params); + params.configs = this.configs; + // 返回执行结果 + return this.getActionManager().then(_connection => { + return _connection.queryEntity(params, option) + }); + } + + /** + * 获取实体对象列表 + * @param params + * @param option + * @returns {Promise.} + */ + static getEntityList(params, option) { + params.configs = this.configs; + // 返回执行结果 + return this.getActionManager() + .then(_connection => { + return _connection.queryEntityList(params, option) + }); + } + + /** + * 新增 + * @param entity + * @param option + * @returns {Promise.} + */ + static insertEntity(entity, option) { + let p = {}; + entity.configs = this.configs; + for (let field of this.configs.fields) { + if (field.name in entity) { + p[field.name] = entity[field.name]; + } else { + let default_value = null; + if (field.default === undefined) { + throw new Error(`field:${field.name} required`); + } + if (field.default instanceof Function) { + default_value = field.default(); + } else { + default_value = field.default; + } + p[field.name] = default_value; + } + } + // 返回执行结果 + return this.getActionManager() + .then(_connection => { + return _connection.insert({insertion: p, configs: this.configs}, option) + }); + } + + /** + * 批量新增 + * @param entity_list + * @param option + * @returns {Promise.} + */ + static insertBatch(entity_list, option) { + let insert_list = []; + for (let entity of entity_list) { + let insert_obj = {}; + for (let field of this.configs.fields) { + if (field.name in entity) { + insert_obj[field.name] = entity[field.name]; + } else { + let default_value = null; + if (field.default === undefined) { + throw new Error(`field:${field.name} required`); + } + if (field.default instanceof Function) { + default_value = field.default(); + } else { + default_value = field.default; + } + insert_obj[field.name] = default_value; + } + } + insert_list.push(insert_obj); + } + // 返回执行结果 + return this.getActionManager() + .then(_connection => { + return _connection.insertBatch({insertion: insert_list, configs: this.configs}, option) + }); + } + + /** + * 物理删除 + * @param params + * @param option + * @returns {Promise.} + */ + static deleteEntity(params, option) { + params.configs = this.configs; + // 返回执行结果 + return this.getActionManager() + .then(_connection => { + return _connection.deleteEntity(params, option) + }); + } + + + /** + * 实体对象更新 + * @param params + * @param option + * @returns {*} + */ + static updateEntity(params, option) { + if ((!params.hasOwnProperty('keyword') || params.keyword.length == 0) && (!params.hasOwnProperty('where') || params.where.length == 0)) { + return Promise.reject(new Error('paras.where更新条件(数组)必须存在条件')); + } + params.configs = this.configs; + // 返回执行结果 + return this.getActionManager() + .then(_connection => { + return _connection.updateEntity(params, option) + }); + } + + /** + * 批量更新 + * [{update: {}, keyword: []] + * @param {Array} update_list + */ + static updateBatch(update_list, option) { + var self = this; + let p = Promise.resolve(); + for (let update_info of update_list) { + p.then(self.updateEntity(update_info, option)); + } + } + + /** + * 统计查询 + * @param params + * @param option + * @returns {Promise.} + */ + static getEntityByAggregate(params, option) { + params.configs = this.configs; + // 返回执行结果 + return this.getActionManager() + .then(_connection => { + return _connection.statsByAggregate(params, option) + }); + } +} + +OnelaBaseModel.action_manager = null; // 连接初始化后绑定到这里来 +OnelaBaseModel.configs = { + fields: [], + tableName: '', + engine: "default" +}; + +// module.exports = BaseModelManager; + +module.exports = {Onela, OnelaBaseModel}; \ No newline at end of file diff --git a/package.json b/package.json index 91110ef..f48c94a 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "onela", - "version": "1.3.4", + "version": "2.0.0", "description": "Onela is an object-based mapping framework based on node.js open source, supporting a variety of relational database data infrastructure. At the same time support a variety of database object read and write separation, the database instance vertical split. On top of the onela architecture you can experience the fun of programming without SQL, and you only need to focus on the business logic code section. And, I will be in the later version of the support to join the distributed cache to achieve the front and back end with node.js program to challenge the case of large-scale applications.", - "main": "lib/OFramework.js", + "main": "lib/onela.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, diff --git a/tests/test.query.js b/tests/test.query.js new file mode 100644 index 0000000..26608a2 --- /dev/null +++ b/tests/test.query.js @@ -0,0 +1,152 @@ +/** + * 数据库配置,可以初始化多个数据库实例 + */ +let dbconfig = [{ + "engine": "default", // 数据库实例名称 + "type": "mysql", // 数据库类型(目前只支持mysql) + "value": { + "connectionLimit": 5, // 连接池限制 + "host": "localhost", // 数据库地址 + "user": "", // 用户名 + "password": "", // 密码 + "database": "todo" // 数据名称 + } +}]; + +const {Onela, OnelaBaseModel} = require("../lib/onela"); +// 初始化Onela模块 +Onela.init(dbconfig); +// 已经在OnelaBaseModel封装的常用方法,可以在此基础自行扩展 + +class ToDoManager extends OnelaBaseModel { + // 可以在此自定义扩展方法(默认封装没有的方法) +} + +// 【重要】单例模式,数据表配置 +ToDoManager.configs = { + fields: [ + {name: "id", type: "int", default: null}, + {name: "content", type: "varchar"}, + {name: "is_done", type: "int", default: 0}, + { + name: "create_time", type: "datetime", default: () => { + return new Date() + } + }, + {name: "finish_time", type: "datetime", default: null} + ], + tableName: "todos", + engine: "default" +}; + +/** + * 事务 + */ +ToDoManager.transaction().then(t => { + // 先新增一条记录 + ToDoManager.insertEntity({ + "content": "测试" + }, {transaction: t}) + .then(data => { + // 再对新增的记录执行修改 + return ToDoManager.updateEntity({ + "update": [ + {"key": "content", "value": "执行修改测试", "operator": "replace"} // 修改了content字段 + ], + "where": [ + {"logic": "and", "key": "id", operator: "=", "value": data.insertId} + ] + }, {transaction: t}); + }) + .then(data => { + console.log('执行结果', data); + // 事务提交 + t.commit(() => { + t.release(); + }); + }) + .catch(ex => { + console.log('事务异常回滚', ex.message); + // 事务回滚 + t.rollback(() => { + t.release(); + }); + }); +}); + + +/** + * 单例模式:数据查询 + */ +ToDoManager.getEntity({ + where: [ + //{"logic": "and", "key": "id", "operator": "=", "value": 1} + ] +}, null).then(data => { + console.log('查询结果', data) +}).then(); + +/** + * 单例模式:新增 + */ +ToDoManager.insertEntity({ + "content":"测试" +}).then(data=>{console.log('查询结果',data)}); + +/** + * 单例模式:分页查询 + */ +ToDoManager.getEntityList({ + "where": [ + //{"logic": "and", "key": "id", "operator": "=", "value": 1} + ] +}).then(console.log); + +/** + * 单例模式:新增 + */ +ToDoManager.insertEntity({ + content: "设计智能保险顾问的用户体系" +}).then(console.log); + +/** + * 单例模式:批量新增 + */ +ToDoManager.insertBatch([ + {content: "测试1"}, + {content: "测试2"}, + {content: "测试3"} +]).then(console.log); + +/** + * 单例模式:删除(物理删除,不推荐使用) + */ +ToDoManager.deleteEntity({ + "where": [ + {"key": "id", operator: "in", value: [12360,12361], logic: "and"}, + // {"key": "is_done", operator: "=", value: 1, logic: "and"} + ] +}).then(console.log); + +/** + * 单例模式:更新(对于删除,建议使用逻辑删除) + */ +ToDoManager.updateEntity({ + update: [ + {key: "is_done", value: 1, operator: "replace"} + ], + where: [ + {"key": "id", operator: "in", value: [12362], logic: "and"}, + ] +}).then(console.log); + +/** + * 单例模式:实时统计 + */ +ToDoManager.getEntityByAggregate({ + // where: + "aggregate":[ + {"function": "count", "field": "is_done", "name": "undone_tasks"}, + ] +}).then(console.log); +