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 反饋討論。