首先说通过 BearcatJSDao
对 Bearcat
的 Model
进行持久化操作是非常方便的.
插件基于 BearcatDao 做了部分修改, 校正一些 Bug.
整体工作流程就是创建数据库,
编写对应的 Model, 同时创建约束类型 Constraint 类(如果内置约束不能满足情况下),
最后依据接口需求编写 SQL和Dao 文件.
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: model mapping 对应的 domain 对象实例数组, 如果主键是唯一的类型为 Long 的, 通常情况下这个是一个自增的字段, 这时 batchAdd 的 domain 对象可以不设置该字段的值, bearcat-dao 会为这些 domain 对象生成唯一的 id 值
cb(err, ?)
batchUpdate(array
披露更新数据到数据库, 当 batchUpdate 的时候,domain 对象的所有属性必须都有值, 一个好的实践就是更新你所查询的 domain 对象
objects: model mapping 对应的 domain 对象实例数组, 一般为通过数据库查询出的 domain 对象的数组
cb(err, ?)
batchDelete(array - batchDeleteByPrimaryKey
批量删除数据库数据
objects: model mapping 对应的 domain 对象实例数组, 其中 domain 对象的主键字段(一个主键单值/联合主键多个值)对应的值必须要有
cb(err, ?)
batchDeleteByPrimaryKey(array
实际执行批量删除的方法, 参数参考 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/')]);