Как Node.js лаконично и элегантно обращается к базе данных MySQL

Node.js

Введение


С момента рождения nodejs появилось большое количество веб-фреймворков, таких как express koa2 egg и т. д. Фронтенд уже не может полагаться на бэкенд и может сам управлять логикой сервера. Сегодня мы поговорим о том, как внешний интерфейс управляет базой данных mysql в nodejs.


2. Работа с базой данных


При непосредственном использовании mysqljs, например при запросе поля, логика кода кажется очень понятной, но слишком громоздко запрашивать только одно поле, чтобы потребовать столько кода:

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-mysql, предоставляемый в egg:

const results = yield app.mysql.select('posts',{
  where: { status: 'draft' },
  orders: [['created_at','desc'], ['id','desc']],
  limit: 10,
  offset: 0
});


Простые сценарии условий запроса могут быть решены, но в наших условиях запроса реальной сцены различные ассоциации таблиц, различные поля, такие как, in, условия сплайсинга findinset, различные подзапросы и другие операции не могут быть удовлетворены, и вы должны написать свой собственный текст SQL.

Например, самостоятельное написание SQL для реализации подкачки на стороне сервера более проблематично:

// 拼接各种条件
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, таких как возражение, sequenceize, knexjs, squel и т. д.


3. Введение в инструмент


Но здесь я хочу представить вам более краткую и простую в использовании библиотеку классов инструментов для nodejs для работы с mysql.ali-mysql-clientЭто инструмент, который реализует идею построителя 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 页


Вот некоторые из его особенностей:


1. Возможности SQL Builder


Предоставляет мощные возможности SQL Builder для выбора, вставки, обновления, удаления.

// 构造查询 
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)


2. Богатая команда


Более удобный доступ к базам данных компаний

// 查询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);


3. Расширение условной инкапсуляции

const result = await db
  .select("*")
  .from("page")
  .where("id", 100) // id = 100
  .where("name", 'test', "like") // name like '%test%'
  .queryList();


Оператор третьего параметра здесь — это инкапсулированная нами условная логика, которую можно передать в строке или функции.

В библиотеку классов встроены следующие операторы:

  • eq (equal)
  • ne (not equal)
  • in (in)
  • gt (greater than)
  • ge (greater than or equal)
  • lt (less than)
  • le (меньше или равно)
  • 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)


Поддержите собственное расширение:

const config = db.config();
// 自定义operator
config.registerOperator('ne', ({ field, value }) => {
  return { sql: '?? <> ?', arg: [ field, value ] };
});


4. Динамические условия


Это параметр, который мы разработали на основе собственного опыта. Ничего подобного в сообществе мы пока не видели. Его функция в основном заключается в упрощении кода, то есть при выполнении условия 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();


Приведенный выше код можно упростить до:

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));
});


5. Поддержка мероприятий


Конечно, нам нужно проверить проблему во время разработки, чтобы увидеть, почему запрошенные данные неверны, поэтому поддерживаются некоторые события, в которых вы можете записывать свой журнал 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);
});


4. Пример использования


Полный пример использования в рамках koa:

├── приложение

│ ├── контроллер

│ │ └── home.js

│ ├── router.js

│ └── сервис

│ ├── 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;
};


Контроллер контроллера/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;
};


сервис сервис/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;
};


больше примеров


4. Адрес с открытым исходным кодом


ali-mysql-clientОн был открыт для github. Цель состоит в том, чтобы предоставить мощную и плавную библиотеку инструментов API для nodejs для доступа к базе данных mysql. Я надеюсь, что логика доступа к базе данных может быть завершена с помощью одной строки кода, что делает доступ к база данных более простая и элегантная. Если у вас есть какие-либо вопросы, пожалуйста, не стесняйтесь обращаться к нам. Обсуждение обратной связи github.