nodejs诞生以来出现了一大批的web框架如express koa2 egg等等,前端能够再也不依赖后端能够本身控制服务端的逻辑。今天咱们就来讲说前端在nodejs中如何操做mysql数据库。前端
直接使用mysqljs,好比查询一个字段,代码逻辑看起来是很清晰的,可是仅查询一个字段就须要这么多代码实在是过于麻烦:node
var mysql = require('mysql'); var connection = mysql.createConnection(mysqlConfig); connection.connect(); connection.query('SELECT 1 + 1 AS solution', function (error, results, fields) { if (error) throw error; console.log('The solution is: ', results[0].solution); }); connection.end();复制代码
一些框架都提供了一些本身的接口去简化CRUD操做,好比egg中提供了egg-mysql:mysql
const results = yield app.mysql.select('posts',{ where: { status: 'draft' }, orders: [['created_at','desc'], ['id','desc']], limit: 10, offset: 0 });复制代码
简单查询条件场景能够解决,可是咱们的真实场景的查询条件中各类表关联、各类字段like、in、findinset拼接条件、各类子查询等等操做都知足不了,必需要本身写SQL文。git
例如本身写SQL去实现一个服务端分页,实现起来也是比较麻烦的:github
// 拼接各类条件 let whereSql = 'where online_version is not null and state <> 1'; if (scope == 'only') { whereSql += ' and use_scope like "%' + query.use_scope + '%"'; } whereSql += handleIn(query) + handleEqual(query) + handleLike(query); // 取得所有数据条数 const sqlTotal = 'select count(*) as total from component' + whereSql; const resultTotal = yield this.app.mysql.query(sqlTotal, values); // 取得当前页数据 let sqlSelect = 'select * from component' sqlSelect += whereSql; sqlSelect += ' order by modified_time desc, id desc limit '; sqlSelect += (pageIndex - 1) * pageSize + ',' + pageSize; const resultList = yield this.app.mysql.query(sqlSelect, values); // 返回分页结果 const result = { list: resultList, total: resultTotal[0].total, }; return result;复制代码
那有没有更简洁的方法去操做数据库呢,答案是确定的社区有不少优秀的orm或sql builder的类库好比objection、sequelize、knexjs、squel等。web
但在这里要向你们介绍一咱们本身的内部的一个更加简洁易用的的nodejs操做mysql的工具类库 ali-mysql-client 它是一个sql builder思路的实现的工具,无需你额外再去定义数据模型更加轻量简洁。sql
先看一个查询示例,是否是看起来简洁易懂:数据库
// 查询单个值,好比下面例子返回的是数字51,知足条件的数据条数 const result = await db .select("count(1)") .from("page") .where("name", "测试", "like") .queryValue(); // 查询多条数据(服务端分页) 返回的是 ressult = {total: 100, rows:[{...}, {...}]}; const result = await db .select("*") .from("page") .where("id", 100, "lt") // id < 100 .queryListWithPaging(3, 20); //每页 20 条,取第 3 页复制代码
下面介绍下它的一些特色:express
提供了select insert update delete的强大的SQL Builder能力json
// 构造查询 const query = db .select("a.a1, b.b1, count(a.c) as count") .from("table as a") .join("table2 as b") .where("a.date", db.literals.now, "lt") // date < now() .where("a.creator", "huisheng.lhs") // creator = 'huisheng.lhs" .groupby("a.a1, b.b1") .having("count(a.category) > 10") .orderby("a.id desc"); // 构造插入 const tasks = [ task1, taks2, task3 ]; const insert = db .insert("task", tasks) .column('create_time', db.literals.now) // 循环赋值给每一行数据 .column('create_user', 'huisheng.lhs'); // 构造更新 const update = db .update("task", task) .column("create_time", db.literals.now) //支持增长字段 .where('id', 2) // 构造删除 const delet = db .delete("task") .where("id", 1)复制代码
提供了丰富的数据库command更方便的访问数据库
// 查询command const select = builderSelect(); // 查询一个字段值 value const result1 = await select.queryValue(); // 查询单行数据 {id:12, name: '测试页面', ....} const result2 = await select.queryRow(); // 查询数据列表 [{...}, {...}]; const result3 = await select.queryList(); // 服务端分页查询 {total: 100, rows:[{...}, {...}]}; const result4 = await select.queryListWithPaging(); // 执行插入更新删除 const result5 = await insert.execute(); const result6 = await update.execute(); const result7 = await delete.execute(); // 也支持直接传入sql const result8 = await db.sql(sql, values);复制代码
const result = await db .select("*") .from("page") .where("id", 100) // id = 100 .where("name", 'test', "like") // name like '%test%' .queryList();复制代码
这里的第三个参数operator就是咱们封装的条件逻辑,可传入字符串或函数,不传时默认是equal,
在类库中内置了如下操做符:
支持本身拓展:
const config = db.config(); // 自定义operator config.registerOperator('ne', ({ field, value }) => { return { sql: '?? <> ?', arg: [ field, value ] }; });复制代码
这个是咱们根据咱们本身的经验设计的一个参数,在社区目前还没看到过相似的,它的做用主要是用来简化代码,也就是当知足xx条件时则忽略该查询条件,设计的初衷是为了简化代码,好比如下代码是很常见的,界面上有输入值则查询,没有输入值时不作为查询条件
好比界面上有输入值时则看成查询条件,这是很常见的
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('ifNumber', ({ value }) => { return !isNaN(Number(value)); });复制代码
固然咱们开发时须要查问题看看为何查询出来的数据不对,因此支持了一些事件,在这些事件中你能够记录你的sql日志或作一些其它的事件
const config = db.config(); // 监听事件 执行前 config.onBeforeExecute(function({ sql }) { console.log(sql); }); // 监听事件 执行后 config.onAfterExecute(function({ sql, result }) { console.log(result); }); // 监听事件 执行出错 config.onExecuteError(function({ sql, error }) { console.log(error); });复制代码
在koa框架中完整的使用示例:
├── app
│ ├── controller
│ │ └── home.js
│ ├── router.js
│ └── service
│ ├── bar.js
│ └── foo.js
├── app.js
├── config.js
└── package.json
配置文件config.js
'use strict'; module.exports = { port: 7001, mysqlClient: { mysql: { // 数据库存链接配置 // host host: '127.0.0.1', // 端口号 port: '3306', // 用户名 user: 'root', // 密码 password: 'mypassword', // 数据库名 database: 'information_schema', }, config: 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); }); }, }, };复制代码
入口文件app.js
'use strict'; const Koa = require('koa'); const app = module.exports = new Koa(); // 加载控制器 const HomeController = require('./app/controller/home')(app); app.controller = { home: new HomeController(), }; // 加载服务 const FooService = require('./app/service/foo')(app); const BarService = require('./app/service/bar')(app); app.service = { foo: new FooService(), bar: new BarService(), }; // 初始化路由 app.router = require('./app/router')(app); app.use(app.router.routes()); // 获取配置信息 const config = app.config = require('./config'); const { mysqlClient, port } = config; // 初始化数据库 const DbClient = require('ali-mysql-client'); app.db = new DbClient(mysqlClient); // 启动服务 if (!module.parent) { app.listen(port); console.log('$ open http://127.0.0.1:' + port); }复制代码
路由配置router.js
'use strict'; const Router = require('koa-router'); module.exports = app => { const router = new Router(); router.get('/', app.controller.home.index); router.get('/foo', app.controller.home.foo); return router; };复制代码
控制器controller/home.js
'use strict'; module.exports = app => { class HomeController { async index(ctx, next) { const result = await app.service.foo.getDetail(); ctx.body = '表信息' + JSON.stringify(result); } async foo(ctx, next) { const result = await app.service.foo.getCount(); ctx.body = '表数量:' + result; } } return HomeController; };复制代码
服务service/foo.js
'use strict'; module.exports = app => { class FooService { async getDetail() { const result = await app.db .select('*') .from('tables') .where('table_name', 'tables') .queryRow(); return result; } async getCount() { const result = await app.db .select('count(*)') .from('tables') .queryValue(); return result; } } return FooService; };复制代码
更多示例
ali-mysql-client 已经开源到了 github 上,目标是为nodejs访问mysql数据库提供强大流畅的api的工具类库,但愿访问数据库逻辑都能使用一行代码完成,让访问数据库变得更加简单优雅,你们使用有问题欢迎你们在 github 反馈讨论。