一、背景问题

自nodejs诞生以来出现了一大批的web框架如express koa2 egg等等,前端可以不再依赖后端可以自己控制服务端的逻辑。原来的后端开发同学的阵地前端如今同样也写的风生水起,撸起袖子就是干几周一个项目前端、后端都自己搞定了,那叫一个效率。

虽然各框架都提供了一些自己的接口去简化CRUD操作,但是还是没有解决复杂条件查询、服务端分页等等问题,导致开发过程中很多开发者还是直接拼接SQL来访问数据库。于是我们就想如何让访问数据库变得简单易用。

二、类库设计

操作数据库都可以看作跟数据库做一次交互,交互传递的是数据+命令,我们希望能够用尽量简洁的代码来描述每一次访问数据库。所以可以对mysqljs对数据库操作进行封装,重新设计访问数据库的API。为nodejs访问mysql数据库提供强大流畅的api的工具类库,目标是希望访问数据库逻辑都能使用一行代码完成,让访问数据库变得更加简单优雅。开源地址:https://github.com/liuhuisheng/ali-mysql-client

1. 初始化配置

初始化如下

const db = new DbClient({
host : '127.0.0.1',
user : 'root',
password : 'secret',
database : 'my_db'
});

2. 构造查询

  • 2.1 查询单个值
// 查询单个值,比如下面例子返回的是数字51,满足条件的数据条数
const result = await db
.select("count(1)")
.from("page")
.where("name", "测试", "like")
.queryValue();
  • 2.2 查询单条数据
// 查询单条数据,返回的是 result = {id:12, name: '测试页面', ....}
const result = await db
.select("*")
.from("page")
.where("id", 12) // id = 12
.queryRow();
  • 2.3 查询多条数据
// 查询多条数据 返回的是 ressult = [{...}, {...}];
const result = await db
.select("*")
.from("page")
.where("name", "测试页面", 'like') // name like '%测试页面%'
.queryList();
  • 2.4 服务端分页查询
// 查询多条数据(服务端分页) 返回的是 ressult = {total: 100, rows:[{...}, {...}]};
const result = await db
.select("*")
.from("page")
.where("id", 100, "lt") // id < 100
.queryListWithPaging(3, 20); //每页 20 条,取第 3 页
  • 2.5 多表关联查询
// 多表关联查询
const result = await db
.select("a.page_id, a.saga_key")
.from("page_edit_content as a")
.join("left join page as b on b.id = a.page_id")
.where("b.id", 172)
.queryList();
  • 2.6 查询除了支持各种多表join外,当然还支持groupby orderby having等复杂查询操作
const result = await db
.select("a1 as a, b1 as b, count(c) as count")
.from("table")
.where("date", db.literals.now, "lt") // date < now()
.where("creator", "huisheng.lhs") // creator = 'huisheng.lhs"
.groupby("a1, b1")
.having("count(category) > 10")
.orderby("id desc")
.queryListWithPaging(2); //默认每页20条,取第2页

3. 构造插入

const task = {
action: "testA",
description: "desc1",
state: "123",
result: "result1"
}; // 插入一条数据
const result = await db
.insert("task", task)
.execute(); // 也支持直接写字段,支持增加字段
const result = await db
.insert("task")
.column("action", "test")
.column("create_time", db.literals.now)
.execute(); // 插入多条数据
const tasks = [ task1, taks2, task3 ];
const result = await db
.insert("task", tasks)
.execute(); // 支持增加或覆盖字段
const result = await db
.insert("task", tasks)
.column('create_time', db.literals.now) // 循环赋值给每一行数据
.column('create_user', 'huisheng.lhs')
.execute();

4. 构造更新

const task = {
action: "testA",
description: "desc1",
state: "123",
result: "updateResult"
}; //更新数据
const result = await db
.update("task", task)
.where("id", 1)
.execute(); //更新数据,支持增加字段
const result = await db
.update("task")
.column("action", "test-id22")
.column("create_time", db.literals.now)
.where('id', 2)
.execute();

5. 构造删除

//删除id为1的数据
const result = await db
.delete("task")
.where("id", 1)
.execute();

6. 事务控制

const trans = await db.useTransaction();

try {
// 数据库操作
// await trans.insert(...)
// await trans.update(...)
await trans.commit();
} catch (e) {
await trans.rollback();
}

7. 复杂条件查询设计

7.1 查询条件所有参数说明

// 查询条件所有参数
const result = await db
.where(field, value, operator, ignore, join) // 支持的所有参数
.where({field, value, operator, ignore, join}) //支持对象参数
.queryList(); // 复杂查询条件
const result = await db
.select("*")
.from("page")
.where("id", 100, "gt") // id > 100
.where("tags", "test", "like") //name like '%test%'
.where("tech", tech, "eq", "ifHave") // tech='tech_value' 当 tech 为空时,不做为查询条件
.where("tags", tags, "findinset", "ifHave", "or")
.queryList();
  • field 字段名
  • value 传入值
  • operator 操作符,默认equal4
  • ignore 是否加为条件,返回false时则忽略该条件
  • join 连接符号(and or),默认为and

7.2 操作逻辑定义operator

该参数很好理解,默认值为equal,支持传字符串或传入函数,传入字符串则会匹配到已定义的逻辑,

const result = await db
.select("*")
.from("page");
.where("id", 100, "lt") // id < 100
.where("group_code", "dacu") // group_code = "dacu"
.queryList();

大家能理解operator是为拼接查询条件使用的逻辑封装,复杂条件的拓展能力都可以靠自定义的operator来完成。其函数的形式如下:

const customOperator =  ({ field, value }) => {
if (condition) {
return {
sql: '?? = ?',
arg: [ field, value ],
};
} else {
return {
sql: '?? > ?',
arg: [ field, value ],
};
}
}; // 可直接使用也可注册到全局
const config = db.config();
config.registerOperator("customOperator", customOperator);

7.3 是否加为条件ignore

这个需要解释下,当满足xx条件时则忽略该查询条件,ignore设计的初衷是为了简化代码,比如以下代码是很常见的,界面上有输入值则查询,没有输入值时不做为查询条件:

const query = db
.select("*")
.from("page");
.where("id", 100, "lt"); if (name){
query.where("name", name, 'like');
} if (isNumber(source_id)){
query.where('source_id', source_id)
} const result = await query.queryList();

上面的代码使用ignore时则可简化为:

const result = await db
.select("*")
.from("page")
.where("id", 100, "lt")
.where("name", name, "like", "ifHave") //使用内置 ifHave,如果name为非空值时才加为条件
.where("source_id", tech, "eq", "ifNumber") //使用内置 ifNumber
.queryList();

支持传字符串或传入函数,传入字符串则会匹配到已定义的逻辑,其函数的形式如下:

const customIgnore = ({field, value}) => {
if (...){
return false;
} return true;
}; //也可以注册到全局使用
const config = db.config();
config.registerIgnore("customIgnore", customIgnore);

7.4 查询条件优先级支持

// where a = 1 and (b = 1 or c < 1) and d = 1
const result = await db.select('*')
.from('table')
.where('a', 1)
.where([
{field: 'b', value: '1', operator:'eq'},
{field: 'c', value: '1', operator:'lt', join: 'or'},
])
.where('d', 1)
.queryList();

7.5 真实场景中的复杂查询示例

// 复杂查询,真实场景示例,项目中拓展了keyword、setinset等operator及ignore
const result = await app.db
.select('a.*, b.id as fav_id, c.name as biz_name, d.group_name')
.from('rocms_page as a')
.join(`left join favorite as b on b.object_id = a.id and b.object_type = "rocms_page" and b.create_user = "${this.ctx.user.userid}"`)
.join('left join rocms_biz as c on c.biz = a.biz')
.join('left join rocms_biz_group as d on d.biz = a.biz and d.group_code = a.biz_group')
// 关键字模糊查询
.where('a.name,a.biz,a.biz_group,a.support_clients,a.owner,a.status', query.keywords, 'keywords', 'ifHasValueNotNumber') // 关键字在这些字段中模糊查询
.where('a.id', query.keywords, 'eq', 'ifNumber') // 关键字中输入了数字时当作id查询
// 精确查询
.where('a.id', query.id, 'eq', 'ifHave')
.where('a.name', query.name, 'like', 'ifHave')
.where('a.biz', query.biz, 'eq', 'ifHave')
.where('a.biz_group', query.biz_group, 'eq', 'ifHave')
.where('a.support_clients', query.support_clients, 'setinset', 'ifHave')
.where('a.status', query.status, 'insetfind', 'ifHave')
.where('a.owner', query.owner, 'eq', 'ifHave')
.where('a.offline_time', query.owner, 'eq', 'ifHave')
// TAB类型 我的页面own、我的收藏fav、所有页面all
.where('a.owner', this.ctx.user.userid, 'eq', () => query.queryType === 'own')
.where('b.id', 0, 'isnotnull', () => query.queryType === 'fav')
// 分页查询
.orderby('a.update_time desc, a.id desc')
.queryListWithPaging(query.pageIndex, query.pageSize);

4. 自定义配置

const config = db.config();

// 自定义operator
config.registerOperator('ne', ({ field, value }) => {
return { sql: '?? <> ?', arg: [ field, value ] };
}); // 自定义ignore
config.registerIgnore('ifNumber', ({ value }) => {
return !isNaN(Number(value));
}); // 监听事件 执行前
config.onBeforeExecute(function({ sql }) {
console.log(sql);
}); // 监听事件 执行后
config.onAfterExecute(function({ sql, result }) {
console.log(result);
}); // 监听事件 执行出错
config.onExecuteError(function({ sql, error }) {
console.log(error);
});

5. 内置的operator及ignore

  • 内置的默认operator

    • eq (equal)
    • ne (not equal)
    • in (in)
    • gt (greater than)
    • ge (greater than or equal)
    • lt (less than)
    • le (less than or equal)
    • isnull (is null)
    • isnotnull (is not null)
    • like (like)
    • startwith (start with)
    • endwith (end with)
    • between (between)
    • findinset (find_in_set(value, field))
    • insetfind (find_in_set(field, value))
    • sql (custom sql)
    • keywords (keywords query)
  • 内置的默认ignore

    • ifHave (如果有值则加为条件)
    • ifNumber (如果是数值则加为条件)

三、使用示例

在eggjs中可以和egg-mysql一起使用,初始化时支持直接传入egg-mysql或ali-rds对象,避免重复创建连接池。

// this.app.mysql 为egg-mysql对象
const db = new DbClient(this.app.mysql)

四、项目开源

该项目最初是在自己项目内部使用只发布在内网中,最近整理去除了内网依赖开源到github上,ali-mysql-client:https://github.com/liuhuisheng/ali-mysql-client

该类库旨在为nodejs访问mysql数据库提供强大流畅的api,目标是希望访问数据库逻辑都能使用一行代码完成,让访问数据库变得更加简单优雅。有任何意见或建议欢迎大家可以随时反馈给我。

开源一个好用的nodejs访问mysql类库的更多相关文章

  1. 关于nodejs访问mysql的思考

    nodejs要访问mysql数据库,首先必须要安装包mysql,命令:npm install mysql.安装成功后的访问数据库代码如下: var mysql = require('mysql'); ...

  2. nodejs 访问mysql

    安装 $ npm install mysql 简介 这个一个mysql的nodejs版本的驱动,是用JavaScript来编写的.不需要编译 这儿有个例子来示范如何使用: var mysql = re ...

  3. C#连接、访问MySQL数据库

    一.准备工具 visual stuido(本示例使用visual studio 2010) MySql.Data.dll mysql_installer_community_V5.6.21.1_set ...

  4. vue+nodejs+express+mysql 建立一个在线网盘程序

    vue+nodejs+express+mysql 建立一个在线网盘程序 目录 vue+nodejs+express+mysql 建立一个在线网盘程序 第一章 开发环境准备 1.1 开发所用工具简介 1 ...

  5. 用Nodejs连接MySQL

    转载,原地址:http://blog.fens.me/nodejs-mysql-intro/ 前言 MySQL是一款常用的开源数据库产品,通常也是免费数据库的首选.查了一下NPM列表,发现Nodejs ...

  6. NodeJS+Express+MySQL开发小记(2):服务器部署

    http://borninsummer.com/2015/06/17/notes-on-developing-nodejs-webapp/ NodeJS+Express+MySQL开发小记(1)里讲过 ...

  7. 转】用Nodejs连接MySQL

    原博文出自于: http://blog.fens.me/category/%E6%95%B0%E6%8D%AE%E5%BA%93/page/2/ 感谢! 用Nodejs连接MySQL 从零开始node ...

  8. 本地tomcat访问mysql数据库

    虽然以前经常听人说起过tomcat,但是今天头一次使用tomcat. 1.Tomcat的安装过程: 首先应该从Apache官方网站上下载是用于Windows的.zip压缩包. 下面是相应的下载链接: ...

  9. C#访问MySQL数据库(winform+EF)

    原文:C#访问MySQL数据库(winform+EF) 以前都是C#连接SQLServer,现在MySQL也比较火了,而且是开源跨平台的,这里连接使用一下,主要是体会一下整个流程,这里使用的是winf ...

随机推荐

  1. CopyMemory、FillMemory、MoveMemory、ZeroMemory

    CopyMemory 复制内存,第一个参数为目的地址,第二个参数为源地址,第三个参数为复制数据的大小,单位字节,源内存区域不能重叠,如果重叠,可以使用MoveMemory()函数.函数原型如下: vo ...

  2. Kafka基本概念介绍

    Kafka官方介绍:Kafka是一个分布式的流处理平台(0.10.x版本),在kafka0.8.x版本的时候,kafka主要是作为一个分布式的.可分区的.具有副本数的日志服务系统(Kafka™ is ...

  3. jQuery框架 的四个入口函数

    <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...

  4. Appium+python自动化(十)- 元素定位秘籍助你打通任督二脉 - 上卷(超详解)

    简介 你有道灵光从天灵盖喷出来你知道吗,年纪轻轻就有一身横练的筋骨,简直百年一见的练武奇才啊,如果有一天让你打通任督二脉,那还不飞龙上天啊.正所谓我不入地狱谁入地狱,警恶惩奸维护世界和平这个任务就交个 ...

  5. Loadrunner做性能测试的主要步骤

    Loadrunner做性能测试的主要步骤: Loadrunner将性能测试过程分为计划测试.测试设计.创建VU脚本.创建测试场景.运行测试场景和分析结果6个步骤. 1) 计划测试:主要进行测试需求的收 ...

  6. 【JVM】02垃圾回收机制

    垃圾回收 垃圾回收策略https://blog.csdn.net/u010425776/article/details/51189318 程序计数器.Java虚拟机栈.本地方法栈都是线程私有的,也就是 ...

  7. Shell学习笔记2》转载自runnoob

    学习且转载地址:http://www.runoob.com/linux/linux-shell-passing-arguments.html 这个网站整理的的确不错,看着很清晰,而且内容也很全面,个人 ...

  8. php中使用trait设计单例

    trait Singleton { private static $instace = null; private function __construct() { } private functio ...

  9. DFS(一):深度优先搜索的基本思想

    采用搜索算法解决问题时,需要构造一个表明状态特征和不同状态之间关系的数据结构,这种数据结构称为结点.不同的问题需要用不同的数据结构描述. 根据搜索问题所给定的条件,从一个结点出发,可以生成一个或多个新 ...

  10. kubernetes实战之consul篇及consul在windows下搭建consul简单测试环境

    consul是一款服务发现中间件,1.12版本后增加servicemesh功能.consul是分布式的,可扩展的,高可用的根据官方文档介绍,目前已知最大的consul集群有5000个节点,consul ...