bearcat-dao 一個基於 SQL mapping 的 node.js DAO 框架

概述

bearcat-dao 是一個 node.js 基於 SQL mapping 的 DAO 框架。實現了基於 SQL mapping 來對數據結果集進行映射,是一種半自動化的模式,相比較於 O/R mapping 全自動化的模式。 所以,在 bearcat-dao 裏,開發者可以對SQL進行徹底的控制,經過SQL來與數據庫打交道並進行性能優化,bearcat-dao 則會把數據結果集映射到 bearcat model 中去。html

SQL mapping vs O/R mapping

結構化查詢語言(SQL)已經存在了很是久的時間。自從 Edgar F.Codd 第一次提出「數據能夠被規範化爲一組相互關聯的表」這樣的思想以來,已經超過35年了。不多有哪種軟件技術敢聲稱本身像關係數據庫和SQL那樣經受住了時間的考驗。所以,關係數據庫和SQL仍然頗有價值,咱們可能都曾有這樣的經歷,應用程序的源代碼(經歷了不少版本)隨着時間的流逝最終仍是過期了(沒法維護下去),但它的數據庫甚至是SQL自己卻仍然頗有價值。java

O/R mapping 被設計爲用來簡化對象持久化工做的,它經過將SQL徹底從開發人員的職責中排除來達到這個目的。在O/R mapping中,SQL是給予應用程序中的類與關係數據庫表之間的映射關係而生成的。除了不用寫SQL語句,使用O/R mapping的API一般也比典型的SQL API要簡單不少,可是O/R mapping仍然不是一顆「銀彈」,它並不是適用於全部的場景。
一個最主要的問題就是O/R mapping它須要假設數據庫是被恰當的規範化了,若是沒有被恰當規範,這就會給映射帶來許多麻煩,甚至須要繞些彎路,或者在設計時對效率作些折衷。同時,沒有哪個對象/關係解決方案能夠支持每一種數據庫的每一種特性、每一種能力以及設計上固有的缺陷,它們僅僅能作到一個子集,而能作到全集的偏偏則是SQL這個專爲數據庫設計的結構化查詢語言node

SQL mapping 與 O/R mapping 不一樣,它不是直接把類映射爲數據庫表或者說把類的字段映射爲數據庫列,而是把SQL語句與結果(也即輸入和輸出)映射爲類。bearcat-dao 在類(model)和數據庫之間創建了一個額外的中間層,這就爲如何在類和數據庫表之間創建映射關係帶來了更大的靈活性,使得在不用改變數據模型或者對象模型的狀況下改變它們的映射關係成爲可能。這個中間層其實就是SQL,經過SQL能夠將類(model)與數據庫表之間的關係降到最低。開發者只須要編寫SQL,bearcat-dao 負責在類(model)屬性與數據庫表的列之間映射參數和結果mysql

Model

model 定義使用 bearcat model
所以,能夠很是容易的就設置映射關係、約束、relation關係git

例如,咱們有一個 test 表,它只有一個 id 主鍵字段github

create table test(
    id bigint(20) NOT NULL COMMENT 'id',    

    PRIMARY KEY (id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

而後,咱們能夠定義下面的 modelredis

var TestModel = function() {
    this.$mid = "testModel";
    this.$table = "test";
    this.id = "$primary;type:Number";
}

module.exports = TestModel;

TestModel 裏,咱們使用 $table 屬性來設置須要映射的表名,對於 id 屬性,咱們用 primary 代表這是一個主鍵,而且咱們給這個字段添加了一個 type 約束,限定它必定爲 Number 類型sql

Relation

在關係型數據庫的表與表之間是能夠有 relation 的,也即關係,有一對1、一對多、多對多這三種狀況數據庫

一對一 relation

一對一關係意味着兩張表,一張表有另一張表的id引用(或者外鍵)。在model對象裏面就是說,兩個model,是一對一的json

好比,咱們有兩張表,test1 表有對 test2 表的 id 引用

create table test1(
    id bigint(20) NOT NULL COMMENT 'id',    
    rid bigint(20) NOT NULL COMMENT 'reference to test2 id',    

    PRIMARY KEY (id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
create table test2(
    id bigint(20) NOT NULL COMMENT 'id',    

    PRIMARY KEY (id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

而後,咱們就能夠定義這樣的model

var Test1Model = function() {
    this.$mid = "test1Model";
    this.$table = "test1";
    this.id = "$primary;type:Number";
    this.test2 = "$type:Object;ref:test2Model"
}

module.exports = Test1Model;
var Test2Model = function() {
    this.$mid = "test2Model";
    this.$table = "test2";
    this.id = "$primary;type:Number";
}

module.exports = Test2Model;

經過用 Test1Model.test2 屬性,咱們使用 ref:test2Model 來設置對 test2Model 的引用

一對多 relation

一對多則意味着,一個model引用着另一個model數組。好比,咱們有一個博客,這個博客裏面的文章有不少評論,這個博客文章與評論之間的關係就是一對多的

var Test1Model = function() {
    this.$mid = "test1Model";
    this.$table = "test1";
    this.id = "$primary;type:Number";
    this.test2 = "$type:Array;ref:test2Model"
}

module.exports = Test1Model;

在上面的model定義中,咱們簡單的把 test2 屬性的 type 改爲 Array 便可,它就變成了一對多的關係

多對多 relation

多對多通常能夠經過中間表,來轉化成兩個一對多的關係

SQL 模板

當編寫複雜sql語句的時候,若是僅僅使用 String 類型的字符串來編寫,確定很是痛苦,更好的方式是用 SQL 模板

編寫SQL模板至關簡單

好比,咱們能夠定義 id 爲 testResultSql 的 SQL 模板

sql testResultSql
select * from test 
end

而後咱們能夠在dao中使用這個 SQL 模板

domainDaoSupport.getList("$testResultSql", null, "testModel", function(err, results) {
     // results is testModel type array
});

第一個參數,開頭帶上 $ 就表面是一個 SQL 模板

同時,因爲是模板,所以能夠包含其餘模板,好比

sql testResultSql
select * from ${testResultTable} 
end

sql testResultTable
test
end

這個結果和上面是同樣的

ResultSet 映射

數據庫結果集是一個由field/value對象組成的數組,所以映射結果集就像用特定key/value對來填充對象。爲了可以作到匹配,咱們使用 model 屬性值裏的 prefix model magic attribute value 或者 model 屬性裏的 prefix model attribute

好比,若是你查詢獲得了以下的 resultSet

[{
    "id": 1,
    "title": "blog_title",
    "content": "blog_content",
    "create_at": 1234567,
    "update_at": 1234567
}]

那麼,映射的model就是這樣的

var BlogModel = function() {
    this.$mid = "blogModel";
    this.$table = "ba_blog";
    this.id = "$primary;type:Number";
    this.aid = "$type:Number";
    this.title = "$type:String";
    this.content = "$type:String";
    this.create_at = "$type:Number";
    this.update_at = "$type:Number";
}

module.exports = BlogModel;

若是結果集字段是已 blog_開頭,好比

[{
    "blog_id": 1,
    "blog_title": "blog_title",
    "blog_content": "blog_content",
    "blog_create_at": 1234567,
    "blog_update_at": 1234567
}]

那麼,映射model就是這樣的

var BlogModel = function() {
    this.$mid = "blogModel";
    this.$table = "ba_blog";
    this.$prefix = "blog_";
    this.id = "$primary;type:Number";
    this.aid = "$type:Number";
    this.title = "$type:String";
    this.content = "$type:String";
    this.create_at = "$type:Number";
    this.update_at = "$type:Number";
}

module.exports = BlogModel;

僅僅須要添加 this.$prefix model 屬性

DAO

DAO 是領域對象模型的縮寫,通常用於操做數據庫

bearcat-dao 提供 domainDaoSupport 對象,封裝了基本的sql、cache操做。使用它也很是簡單,直接依賴注入,而後經過 init 方法進行初始化

simpleDao.js

var SimpleDao = function() {
    this.$id = "simpleDao";
    this.$init = "init";
    this.$domainDaoSupport = null;
}

SimpleDao.prototype.init = function() {
    // init with SimpleModel id to set up model mapping
    this.domainDaoSupport.initConfig("simpleModel");
}

// query list all
// callback return mapped SimpleModel array results
SimpleDao.prototype.getList = function(cb) {
    var sql = ' 1 = 1';
    this.$domainDaoSupport.getListByWhere(sql, null, null, cb);
}

module.exports = SimpleDao;

完整的api能夠參見 domainDaoSupport

配置使用

修改項目中的context.json
placeholds 能夠很方便的在不一樣環境間切換

"dependencies": {
    "bearcat-dao": "*"
},
"beans": [{
    "id": "mysqlConnectionManager",
    "func": "node_modules.bearcat-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}"
    }]
}, {
    "id": "redisConnectionManager",
    "func": "node_modules.bearcat-dao.lib.connection.cache.redisConnectionManager",
    "props": [{
        "name": "port",
        "value": "${redis.port}"
    }, {
        "name": "host",
        "value": "${redis.host}"
    }]
}]

若是你不須要使用redis, 你能夠移除redisConnectionManager定義

事務

bearcat-dao 基於 bearcat AOP 提供了事務支持. aspect 是 transactionAspect , 提供了 around advice, 當目標事務方法調用cb函數的時候傳入了 err, rollback 回滾操做就會被觸發, 相反若是沒有cb(err)的話, 事務就會被提交(commit).
pointcut 定義的是:

"pointcut": "around:.*?Transaction"

所以, 任何已 Transaction 結尾的POJO中的方法都會匹配到 transaction 事務
因爲transaction必須在同一個connection中, 在 bearcat-dao 中是經過 transactionStatus 來保證的, 在同一個事務的 transaction 必須在同一個transactionStatus中

SimpleService.prototype.testMethodTransaction = function(cb, txStatus) {
    var self = this;
    this.simpleDao.transaction(txStatus).addPerson(['aaa'], function(err, results) {
        if (err) {
            return cb(err); // if err occur, rollback will be emited
        }
        self.simpleDao.transaction(txStatus).getList([1, 2], function(err, results) {
            if (err) { 
                return cb(err); // if err occur, rollback will be emited
            }
            cb(null, results); // commit the operations
        });
    });
}

開啓 Debug 模式

跑node應用時帶上BEARCAT_DEBUG爲true

BEARCAT_DEBUG=true node xxx.js

開啓debug模式後,就能看到具體執行SQL的日誌

例子

相關文章
相關標籤/搜索