zoukankan      html  css  js  c++  java
  • nodejs访问mysql数据库工具ali-mysql-client

    一、背景问题

    自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,目标是希望访问数据库逻辑都能使用一行代码完成,让访问数据库变得更加简单优雅。有任何意见或建议欢迎大家可以随时反馈给我。

  • 相关阅读:
    JZOJ 3034. 【NOIP2012模拟10.17】独立集
    JZOJ 3035. 【NOIP2012模拟10.17】铁轨
    JZOJ 1259. 牛棚安排
    数位DP JZOJ 3316. 非回文数字
    JZOJ 3046. 游戏
    JZOJ 3013. 填充棋盘
    debian 安装oracle提供的java8
    java 汉字转拼音 PinYin4j
    debian ssh设置root权限登陆 Permission denied, please try again
    java并发下订单生成策略
  • 原文地址:https://www.cnblogs.com/xqin/p/11223813.html
Copyright © 2011-2022 走看看