一、背景问题

自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. spring.net的简单使用(一)入门

    Spring.net是一个非常强大的框架和工具,下面是百度百科对它的介绍. Spring.NET为建立企业级应用提供了一套轻量级的解决方案.通过Spring.NET,我们可以用统一且透明的方式来配置应 ...

  2. Mongodb Compile C++ Driver

    之前发现直接编译mongo源码中的驱动,静态库的驱动会很大,在链接使用的时候会报很多链接错误. 转而直接编译单独提供驱动源码,同样vc2008的版本也要做我的另一篇博文中修改,在这不多说,具体参见: ...

  3. Web 高效开发必备的 PHP 框架

    摘要: 每周为您推送最有价值的开源技术内参! 码云项目推荐 1.项目名称:多功能 THinkPHP 开源框架 项目简介:使用 THinkPHP 开发项目的过程中把一些常用的功能或者第三方 sdk 整合 ...

  4. ZooKeeper+Dubbo+SpringBoot 微服务Demo搭建

    1. 首先创建springBoot项目,springBoot是一堆组件的集合,在pom文件中对需要的组件进行配置.生成如下目录结构 创建test项目,同步在test创建dubbo-api,dubbo- ...

  5. C# — WinForm TCP连接IPv4和IPv6的判断

    大家都知道, XP系统默认使用的是IPv4格式的IP地址, 而Win7系统默认使用的是IPv6格式的IP地址. 所以有些关于TCP连接的WinForm系统,在XP下可能运行正常,但在Win7下却不能运 ...

  6. php 的mvc开发

    至于什么MVC结构,其实就是三个Model,Contraller,View单词的简称,,Model,主要任务就是把数据库或者其他文件系统的数据按 照我们需要的方式读取出来.View,主要负责页面的,把 ...

  7. 302Java_前定义

    第零章 前定义 1 介绍 1.1 简介 Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承.指针等概念,因此Java语言具有功能强大和简单易用两个特征. ...

  8. RocketMQ(2)---Docker集群部署RocketMQ

    RocketMQ(2)-Docker集群部署RocketMQ =前言= 1.因为自己只买了一台阿里云服务器,所以RocketMQ集群都部署在单台服务器上只是端口不同,如果实际开发,可以分别部署在多台服 ...

  9. spring boot 2.x 系列 —— actuator 服务监控与管理

    文章目录 一.概念综述 1.1 端点 1.2 启用端点 1.3 暴露端点 1.4 健康检查信息 二.项目说明 1.1 项目结构说明 1.2 主要依赖 1.3 项目配置 1.4 查看监控状态 三.自定义 ...

  10. SCIgen与野鸡期刊的梗

    layout: post title: "SCIgen与野鸡期刊的梗" date: 2019-04-28 19:06:21 +0800 --- 作者:吴甜甜 个人博客网站: wut ...