BearcatJS Dao插件文档

首先说通过 BearcatJSDaoBearcatModel 进行持久化操作是非常方便的.

插件基于 BearcatDao 做了部分修改, 校正一些 Bug.

整体工作流程就是创建数据库,
编写对应的 Model, 同时创建约束类型 Constraint 类(如果内置约束不能满足情况下),
最后依据接口需求编写 SQLDao 文件.

Quick Start

配置数据库, 依赖注入连接参数

{
  "name": "bearcatjs-proj",
  "scan": [
    "app"
  ],
  "dependencies": {
    "bearcatjs-dao": "*"
  },
  "beans": [
    {
      "id": "mysqlConnectionManager",
      "func": "node_modules.bearcatjs-dao.lib.connection.sql.mysqlConnectionManager",
      "props": [
        {
          "name": "port",
          "value": "${mysql.port}"
        },
        {
          "name": "host",
          "value": "${mysql.host}"
        },
        {
          "name": "user",
          "value": "${mysql.user}"
        },
        {
          "name": "password",
          "value": "${mysql.password}"
        },
        {
          "name": "database",
          "value": "${mysql.database}"
        }
      ]
    }
  ]
}

配置具体的 mysql 配置文件

以依赖注入的方式放在 config/${ver}/mysql.json 中即可, 例如 config/dev/mysql.json

{
  "mysql.port": 3306,
  "mysql.host": "localhost",
  "mysql.user": "root",
  "mysql.password": "123456",
  "mysql.database": "bearcatjs"
}

启动前加载 SQL 模板

确认 SQL 模板文件存放目录, 例如 app/sql 目录, 那么在启动 bearcatjs 前使用以下代码加载 sql 模板:

const Path    = require('path');
const dao     = require('bearcatjs-dao');

let sqlPaths = [Path.join(__dirname, './app/sql/')];
dao.loadSQL(sqlPaths);

关于 SQL 模板的编写, 参考 如何编写 SQL 模板

如何创建数据库和数据表

BearcatJS Dao 基于 MYSQL 和 redis, 这一部分主要介绍如何创建 MySQL 数据库和数据表.

一般情况, 我们使用图形化工具, 或者比较熟悉的玩家可用使用命令行进行创建.

例如:

CREATE DATABASE `bearcatjs`;

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(64) NOT NULL,
  `gender` smallint(6) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `location` varchar(128) DEFAULT NULL,
  `addr` varchar(128) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;


LOCK TABLES `user` WRITE;
INSERT INTO `user` VALUES (1,'完颜婉清',1,23,'chaoyang, beijing','tieling, liaoning'),(2,'顾晓峰',0,21,'haidian, beijing','shenyang, liaoning');
UNLOCK TABLES;

DROP TABLE IF EXISTS `IDGenerator`;
CREATE TABLE `IDGenerator` (
  `name` varchar(50) NOT NULL,
  `id` bigint(20) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

LOCK TABLES `IDGenerator` WRITE;
INSERT INTO `IDGenerator` VALUES ('user',1);
UNLOCK TABLES;

可用看到创建好正常的数据表 user 之后, 又创建了 IDGenerator 表, 并且将数据表 user 名写入其中, 用于记录该表自增 id 值.

如何编写 Model

Model 的编写, 详细可以参考 BearcatJS 的非官方文档, 如何使用 Model 定义数据对象, 这里只做简单介绍:

let Util = require('util');

let UserModel = function() {
    this.$mid     = 'userModel';
    this.$table   = 'user';
    this.$prefix  = 'user_';  // 当数据库中表已经有前缀的时候

    this.id       = "$primary;type:Number";
    this.name     = "$type:String;notNull";
    this.gender   = "$type:Number:default:1";
    this.age      = "$type:Number;max:99;min:18;notNull;default:18";
    this.location = "$pattern(regexp=beijing);size(max=64,min=10)";
    this.addr     = "$pattern(regexp=liaoning);size(max=25,min=12)";
};
UserModel.prototype.checkNum = function(k, v) {
    if (typeof v !== "number") {
        return new Error(Util.format('invalid number [ %s ]', v));
    }
};
UserModel.prototype.fixAge = function() {
    this.age = Math.floor(this.age);
    if (this.age < 0) {this.age = 0;}
};
UserModel.prototype.log = function() {
    console.log('value changed ready to emit sth.');
};
UserModel.prototype.shareLocation = function () {
    console.log('My Location Is %s', this.location);
};

bearcat.module(UserModel, typeof module !== 'undefined' ? module : {});

上面的例子就定义了简单的 Model, 其中 checkNum, fixAge, log 等方法, 用于$set 方法的前后注入使用, 例如

let um = bearcat.getModel('userModel');
let err = um.$before(['checkNum']).$after(['fixAge','log']).$set('age', 12);
if (err) { console.log(err.stack); }

如何创建约束类型

注意观察上面 model, 约束类型有 max:99, min:18, notNull, size(), pattern()….

这些约束都是 BearcatModel 自带的内置约束类型, 如何创建自定义约束呢?

let LevelConstraint = function LevelConstraint() {
    this.$cid    = 'characterLevel';
    this.$scope  = 'prototype';
    this.$order  = 1;

    this.$constraint = '$notNull';
    this.msg = 'level %s invalid';
    this.max = null;
};

LevelConstraint.prototype.validate = function (key, value) {
    if (!value || typeof value !== 'number') {
        return new Error('level must be a number');
    }

    if (value < 0 || value > this.max) {
        return new Error('level ' + value + ' invalid');
    }

    if (parseInt(value) !== value) {
        return new Error('level must be an int');
    }
};

bearcat.module(LevelConstraint, typeof module !== 'undefined' ? module : {});

这里定义了玩家游戏角色属性的等级约束, 使用方法如下:

this.level = "$type:Number;characterLevel(max=60)"

这里设置 60 级为满级.

如何编写 SQL 模板

SQL 模板, 在原有 Bearcat-DAO 里面, 使用的是 sql/end 进行标记 SQL 模板的起始位置和终止位置, 实际使用过程中, 发现一些问题, 所有在其基础之上更新了本项目 BearcatJS-DAO.

BearcatJS-DAO, 使用 SQLStart/SQLEnd 来标记模板的起始位置, 这也意味着, SQLStart/SQLEnd 字样不能出现在您的模板内容当中, 这里需要注意, 尽管如此, 相信很少有人会在 SQL 语句中出现类似字样.

SQLStart getUserList
// this is template contents
SELECT ${userKeys} FROM user AS u WHERE u.id = ?;
SQLEnd

SQLStart userKeys
u.id AS user_id,
u.name AS user_name,
u.gender AS user_gender,
u.age AS user_age,
u.location AS user_location,
u.addr AS user_addr
SQLEnd

如何编写 Dao 文件

let UserDao = function UserDao() {
    this.$id = 'userDao';
    this.$init = 'init';
    this.$domainDaoSupport = null;
};
UserDao.prototype.init = function () {
    // initConfig with user defined in ../model/userModel.js
    this.$domainDaoSupport.initConfig('userModel');
};
UserDao.prototype.transaction = function (txStatus) {
    this.$domainDaoSupport.transaction(txStatus);
    return this;
};
UserDao.prototype.add = function (obj, cb) {
    this.$domainDaoSupport.add(obj, cb);
};
UserDao.prototype.getUserById = function (uid, cb) {
    this.$domainDaoSupport.getList('$getUserList', [uid], cb);
};
bearcat.module(UserDao, typeof module !== 'undefined' ? module : {});

上面 dao 文件也是一个普通的 bean, 唯一需要注意的就是需要有 init 方法, 还有依赖注入 $domainDaoSupport 对象.

init 方法里面, 将 dao 对应的 Model 对象 id 传入进去就可以了.

getUserById 方法是通过 getList 调用上面的 SQL 模板, 通过数组传入参数 uid 解析到 SQL 模板对应的位置.

其中 getList 方法是 $domainDaoSupport 对象的内置方法, 文档可以参考$domainDaoSupport 的可用方法部分.

调用 Dao 方法时候可以参考下面的内容:

let userDao = bearcat.getBean('userDao');
userDao.getUserById(1, function(err, userModels) {
    // handle err
    if (userModels.length === 1) {
        let user = userModel[0];
        user.shareLocation();
    }
});

通过正常 getBean 获取 dao 对象, 之后调用其 prototype 方法, 得到对应的数据.

$domainDaoSupport 的可用方法

对象属性:

this.tableConfig = null; - init 时会自动根据meta信息和model对象属性创建 TableConfig 对象
this.sqlTemplate = null; - 注入 mysqlTemplate
this.cacheTemplate = null; - 注入 redisTemplate
this.domainMap = {}; 缓存多个model对应的 TableConfig 数据, 包含 this.tableConfig
this.modelId = null; - 通过 string 初始化的会有该ID

配置方法:

// 在Dao中初始化 init 方法中调用
function initConfig(domainConfig) {} domainConfig 是 model 对象, 如果是 Model 对象对应的 mid, 那么将会转到 initModelConfig.
function initModelConfig(modelId) {} modelId 是 model 对象对应的 mid

MySQL:

  • transaction(transactionStatus)

    事务需要在同一个 connection 中完成,这点由 transactionStatus 来保证, dao 需要实现 transaction 方法,来把 transactionStatus 表示的事务状态传给底层的 sqlTemplate

  • deleteByPrimaryKey(array<number|string>: params, function: cb)

    通过主键/联合主键值列表删除

    params: 参数为主键/联合主键的值列表, 顺序必须和 domain 里面主键定义的顺序相对应

    cb(err, ?)

  • deleteById(number|string: id, function: cb) - deleteByPrimaryKey

    通过主键值删除 - 主键名可以为 id, 也可以是其他名字均支持.

    id: 必须是唯一主键

    cb(err, ?)

  • batchAdd(array: objects, function: cb)

    批量添加对象到数据库

    objects: model mapping 对应的 domain 对象实例数组, 如果主键是唯一的类型为 Long 的, 通常情况下这个是一个自增的字段, 这时 batchAdd 的 domain 对象可以不设置该字段的值, bearcat-dao 会为这些 domain 对象生成唯一的 id 值

    cb(err, ?)

  • batchUpdate(array: objects, function: cb)

    披露更新数据到数据库, 当 batchUpdate 的时候,domain 对象的所有属性必须都有值, 一个好的实践就是更新你所查询的 domain 对象

    objects: model mapping 对应的 domain 对象实例数组, 一般为通过数据库查询出的 domain 对象的数组

    cb(err, ?)

  • batchDelete(array: objects, function cb) - batchDeleteByPrimaryKey

    批量删除数据库数据

    objects: model mapping 对应的 domain 对象实例数组, 其中 domain 对象的主键字段(一个主键单值/联合主键多个值)对应的值必须要有

    cb(err, ?)

  • batchDeleteByPrimaryKey(array: objects, function: cb)

    实际执行批量删除的方法, 参数参考 batchDelete

    cb(err, ?)

  • updateColumnValue(string: columnName, string|number: newValue, array: conditionColumns, array<string|number>: conditionValues, function: cb)

    以查询条件字段作为查询条件来更新某个字段的值

    columnName: 更新字段名
    newValue: 更新字段值
    conditionColumns: 查询字段列表
    conditionValues: 查询字段值列表

    cb(err, ?)

  • updateColumn(string: columnName, string|number: newValue, array<string|number>: primarysValue, cb) - updateColumnValue

    以主键作为查询条件来更新某个字段的值

    columnName: 更新字段名
    newValue: 更新字段值
    primarysValue: 主键/联合主键值的列表, 单一主键也需要传递数组

    cb(err, ?)

  • getList(string: sql, array<string|array>: params, object|string: options, function: cb)

    根据 sql,params 来进行查询,查询结果会 model mapping 到 domain 对象

    sql: 查询字符串, 可以是以 $ 开头的 sql 模板 id, 比如 “$testResult”
    params: 注入到 sql 模板中 ? 部位的参数列表
    options: (object 类型)附加参数, 你可以传入 order, limit, offset; (string 类型)将结果解析成其他 model mapping 对应的 domain, 可以传入其他 model 的 mid.

    cb(err, array<array|object>: modelList), 1 对 1 映射为对应 model 对象的数组, 1 对多映射为对应 model 对象(部分值为数组).

  • getByPrimary(array<string|number>: params, function: cb)

    根据唯一主键/联合主键值列表获取对象

    params: 参数的顺序必须和 domain 里面主键定义的顺序相对应, 根据主键来进行查询列表

    cb(err, array: modelList) 虽然是数组, 但是应该只取首值.

  • getById(string|number: id, function: cb) - getByPrimary

    根据唯一主键获取对象

    id: 必须是唯一主键

    cb(err, array: modelList) 虽然是数组, 但是应该只取首值.

  • get(string: sql, array<string|number>: params, function: cb)

    根据 sql,params 来进行查询,查询结果会 model mapping 到 domain 对象

    sql: sql 语句, 可以带有注入点 ?
    params: 参数值, 注入到 sql 语句 ? 位置.

    cb(err, ?)

  • getCount(string: sql, array<string|number>: params, function: cb)

    根据 sql,params 来查询数量

  • add(sql, params, cb)

    通过 sql,params 来添加数据 或者 通过 model mapping 的 domain object 对象来添加数据

  • delete(sql, params, cb)

    通过 sql,params 来删除数据 或者 通过 model mapping 的 domain object 对象来删除数据

  • update(sql, params, cb)

    通过 sql,params 来更新数据 或者 通过 model mapping 的 domain object 对象来更新数据

  • exists(sql, params, cb)

    如果存在,cb 结果是 true,否则是 false

  • allocateRecordId(tableName, cb)

    为表申请主键 id 的值

  • getByWhere(where, args, cb) - get

    • getListByWhere(where, args, options, cb) - getList
    • getCountByWhere(where, args, cb) - getCount
    • removeByWhere(where, args, cb) - delete

    Redis:

    addToCache(key, value, expire)
    getStringFromCache(key, cb)
    setCounter(key, initCount, expire)
    getCounter(key, cb)
    incr(key)
    incrBy(key, increment)
    removeFromCache(key)

    内置其他方法:

    getConfig(domainConfig)
    
    setTableConfig(tableConfig)   - 设置 this.tableConfig 值 TableConfig
    getTableConfig()   - 获取 TableConfig (当前Dao对应的, 未初始化执行默认初始化空值)
    
    setSqlTemplate(sqlTemplate)
    getSqlTemplate()   - 获取MysqlTemplate对象
    
    setCacheTemplate(cacheTemplate)
    getCacheTemplate()   - 获取RedisTemplate对象
    
    getDomainMap(key)   - 根据 modelId 获取对应 TableConfig
    setDomainMap(key, tableConfig)   - 设置DomainMap {modelId => TableConfig}

    Bearcat Dao 的基本 API

    version

    获取当前版本

    beardaojs.version;

    getSQL(sqlId): string SQL

    通过 SQL 模板的 ID 来获取 SQL 模板内容, 一般无需在应用层调用.

    loadSQL(configLocations): null

    配置 SQL 模板目录, 传入通过 path.join 后的路径, 例如:

    beardaojs.loadSQL([path.join(__dirname, './sql/')]);

    需要在 bearcat 启动前进行配置.

    addConfigLocations(locations): null

    将 SQL 模板目录信息配置到加载路径中, 一般无需在应用层调用, 已经在 loadSQL 中自动调用.

    beardaojs.addConfigLocations([path.join(__dirname, './sql/')]);

    calDestDB(table, field, value): string name

    calDestDBs(table, field, values): array names

    getDestDBAll

    getShardingUtil

    Donate - Support to make this site better.
    捐助 - 支持我让我做得更好.