iBATIS DAO框架分析




 为书写方便,本文采用如下简写约定:
       Transaction:Tx
       Manager:Mgr
       Context:Ctx
       Interface:Iface

 iBATIS DAO框架如图:

 

DAO的核心在于DaoManager,DaoManager的创建代码如下:

          Reader reader = Resources.getResourceAsReader("dao.xml");
          DaoManager daoMngr = DaoManagerBuilder.buildDaoManager(reader);

DaoManager 是接口,查看 DaoManagerBuilder 源代码可发现,其 buildDaoManager 方法返回的是一个 StandardDaoManager 实例。 buildDaoManager 方法调用了XmlDaoManagerBuilder类的buildDaoManager方法,该方法完成如下工作:
1. 创建一个 StandardDaoManager 实例 stdDaoMgr
2.  创建一个用于全局收集各种 property (来自 <properties> 元素指向的资源文件或来自当前 dao.xml 中的各级 <property> 元素)的 Properties 对象
3.  解析 dao.xml 文件(建议阅读本文时参考一份dao.xml文件,如JGameStore应用中给出的dao.xml)中的 <properties> 元素,将相应 property 加入;
4.  解析 dao.xml 文件中的 <context> 元素,得到一个 DaoContext 实例 daoCtx 4.1 );并将调用 stdDaoMgr.addContext 方法将 daoCtx 添加到 stdDaoMgr 中( 4.2 ):
4.1 解析 dao.xml 文件的 <context> 元素得到 daoCtx 的过程为:
实例化一个 DaoContext 对象 daoCtx
将其 daoManager 字段设为我们的 stdDaoMgr
<context> id 属性,则将 daoCtx id 字段取为此属性的值;
解析 <context> 的子元素:
4.1.1 解析 <txMgr> 子元素,得到 DaoTxMgr 接口实例 txMgr ,设为 daoCtx 的相应字段,解析过程为:
根据 <txMgr> 子元素的 type 属性,实例化一个相应的 DaoTxMgr 实例 txMgr
解析 <txMgr> <property> 子元素,将所得 property 添加入 properties
根据 properties txMgr 进行配置(即调用 txMgr.configure 方法);
4.1.2 解析 <dao> 子元素,得到一个 DaoImpl 类实例 daoImpl ,然后将其加入 daoCtx
4.1.2 .1 解析过程为:
4.1.2 .1.1 实例化一个 DaoImpl 类实例 daoImpl
4.1.2 .1.2 daoImpl daoMgr 字段设为我们的 stdDaoMgr
4.1.2 .1.3 daoImpl daoCtx 字段设为我们的 daoCtx
4.1.2 .1.4 daoImpl daoIface 字段设为 <dao> iface 属性值对应的 class
4.1.2 .1.5 daoImpl daoImplementation 字段设为 <dao> implementation 属性值对应的 class
4.1.2 .1.6 根据 implementation 属性实例化一个 DAO 实现类,设为 daoInstance 字段值,注意,该实例一定是一个 Dao 接口实例,因为任何一个都继承自 DaoTemplate ,而 DaoTemplate 实现了 Dao 接口;
4.1.2 .1.7 创建一个当前 DAO 实现类的代理,设为 daoImpl proxy 字段值,该代理在启用显式事务时会在调用委托方法前调用 daoCtx.startTx 方法;在使用隐式事务时则在调用委托方法的前后分别调用 daoCtx.startTx 方法和 commitTx 方法(在 finally 块中还调用 daoCtx.endTx 方法)。
4.1.2 .2 daoImpl 加入 daoCtx 的过程为:以当前 daoImpl 填充一张从 daoIface DaoImpl 实例的表;
4.2 调用 stdDaoMgr.addContext 方法将 daoCtx 添加到 stdDaoMgr 中的过程为:
4.2.1 以当前 daoCtx 填充一张由 id DaoCtx 实例的表;
4.2.2遍历 daoCtx 中存放的所有 daoImpl ,填充一张从 daoIface daoCtx 的表和一张从 Dao 接口实例(即 daoImpl 中的 proxy daoInstance )到 daoCtx 的表;
5.  客户以某 DaoIface 调用 DaoMgr.getDao 方法得到一个 DaoIface 实现类实例 xxxYyyDao 的过程为:
stdDaoMgr查找其 daoIface daoCtx 的表,得到当前 daoIface 所在 daoCtx ,然后调用 daoCtx.getDao 方法:
    daoCtx 查找其从 daoIface DaoImpl 实例的表,得到 daoImpl ,返回其 proxy 字段;
6.  隐式事务:
隐式事务中,客户每调用一个 xxxYyyDao 中方法时,都是一次完整的事务,因为 xxxYyyDao 是调用 DaoMgr.getDao 方法得到的,而根据 5 ,其实 xxxYyyDao 是一个代理,又根据 4.1.2 .1.7 ,该代理会“在调用其委托方法前后分别调用 daoCtx.startTx 方法和 commitTx 方法(在 finally 块中还调用 daoCtx.endTx 方法)”。
6.1 daoCtx.startTx 方法调用其 txMgr 字段的 txMgr.startTx 方法,该方法返回一个 DaoTx 实例 daoTx daoCtx 将它放入一个线程变量中;
6.2 DaoIface 实现类中,由于其一定继承自某个 DaoTemplate ,以调用其中的数据库访问方法,而这些数据库访问方法都会以自己作为参数调用 daoMgr getTx 方法;该方法查找 4.2.2 中提到的从 Dao 接口实例到 daoCtx 的表,得到一个 daoCtx ,然后调用 daoCtx.getTx daoCtx.getTx 将存储在线程变量中的 daoTx 实例返回;
6.3 daoTx 实例包含数据库操作所需的关键元素,例如对于 SqlMapDaoTx ,其中就包含一个 SqlMapClient 实例, SqlMapDaoTemplate 中的数据库访问方法(如 insert queryForList 等)都是先调用 daoMgr.getTx ,得到 daoTx 实例,将其强制转化为 SqlMapDaoTx 实例,然后调用其 getSqlMap 方法得到 SqlMapClient 实例,再调用 SqlMapClient 实例中的相应方法;又如对于 JDBC 的情况,对应 DaoTx ConnectionDaoTx ,该类包含一个,每次调用 JdbcDaoTemplate 方法的 getConnection 方法时,该方法都先调用 daoMgr.getTx ,得到 daoTx 实例,将其强制转化为 ConnectionDaoTx 实例,然后调用其 getConnection 方法得到其中的 Connection 实例,然后调用其中的相应方法。
6.4 daoCtx.commitTx 方法调用其 txMgr 字段的 txMgr.commitTx(daoTx) 方法完成事务的提交。
6.5 daoCtx.endTx 方法调用其 txMgr 字段的 txMgr.endTx(daoTx) 方法结束事务。
7.  显式事务:
显式事务通常包括三个步骤:首先,调用 daoMgr.startTx ,然后调用 xxxYyyDao 中的方法,最后调用 daoMgr.commitTx
7.1 daoMgr.startTx 的工作非常简单,只是设置 stdDaoMgr 中标记显式事务的字段;
7.2 调用 xxxYyyDao 中的方法时,由于代理,将先调用 daoCtx.startTx ,此过程同 6.1
7.3 调用 daoMgr.commitTx 时,该方法最终调用的也是 daoCtx.commitTx ,请参考 6.4
 
下面以一个问题的实现来完成本文的总结工作:如果要由我来实现iBATIS的DAO框架对于Hibernate的支持,我们应该如何实现?
Hibernate的核心在于Session,所有的数据库操作都可调用Session上的相应方法完成,所有考虑用于支持Hibernate的DaoTx实现应该是对Session的一个包装,该实现中有一个返回当前Session的getSession方法(当然也包括提交和回滚方法)。同样的,DaoTxMgr实现类的configure方法负责完成某个Session实例(session)的配置,startTx方法负责返回一个包装了当前session实例的DaoTx实例,commitTx方法将传入的daoTx实例强制转化后调用daoTx上的commit方法,rollbackTx方法将传入的daoTx实例强制转化后调用daoTx上的rollback方法。而HibernateDaoTemplate类的关键就在于其protected的getSession方法,该方法先调用daoMgr.getTx得到当前daoTx实例,强制转化后调用daoTx上的getSession方法即可。
查询iBATIS的源代码,发现与以上思路完全相同。

 

概述bearcat-dao 是一个 node.js 基于 SQL mapping 的 DAO 框架实现了基于 SQL mapping 来对数据结果集进行映射,是一种半自动化的模式,相比较于 O/R mapping 全自动化的模式。 因此,在 bearcat-dao 里,开发者能够对SQL进行完全的控制,通过SQL来与数据库打交道并进行性能优化,bearcat-dao 则会把数据结果集映射到 bearcat model 中去。SQL mapping vs O/R mapping结构化查询语言(SQL)已经存在了非常久的时间。自从 Edgar F.Codd 第一次提出“数据可以被规范化为一组相互关联的表”这样的思想以来,已经超过35年了。很少有哪一种软件技术敢声称自己像关系数据库和SQL那样经受住了时间的考验。因此,关系数据库和SQL仍然很有价值,我们可能都曾有这样的经历,应用程序的源代码(经历了很多版本)随着时间的流逝最终还是过时了(无法维护下去),但它的数据库甚至是SQL本身却仍然很有价值。O/R mapping 被设计为用来简化对象持久化工作的,它通过将SQL完全从开发人员的职责中排除来达到这个目的。在O/R mapping中,SQL是给予应用程序中的类与关系数据库表之间的映射关系而生成的。除了不用写SQL语句,使用O/R mapping的API通常也比典型的SQL API要简单很多,但是O/R mapping仍然不是一颗“银弹”,它并非适用于所有的场景。一个最主要的问题就是O/R mapping它需要假设数据库是被恰当的规范化了,如果没有被恰当规范,这就会给映射带来许多麻烦,甚至需要绕些弯路,或者在设计时对效率做些折衷。同时,没有哪一个对象/关系解决方案可以支持每一种数据库的每一种特性、每一种能力以及设计上固有的缺陷,它们仅仅能做到一个子集,而能做到全集的恰恰则是SQL这个专为数据库设计的结构化查询语言SQL mapping 与 O/R mapping 不同,它不是直接把类映射为数据库表或者说把类的字段映射为数据库列,而是把SQL语句与结果(也即输入和输出)映射为类。bearcat-dao 在类(model)和数据库之间建立了一个额外的中间层,这就为如何在类和数据库表之间建立映射关系带来了更大的灵活性,使得在不用改变数据模型或者对象模型的情况下改变它们的映射关系成为可能。这个中间层其实就是SQL,通过SQL可以将类(model)与数据库表之间的关系降到最低。开发者只需要编写SQL,bearcat-dao 负责在类(model)属性与数据库表的列之间映射参数和结果Modelmodel 定义使用 bearcat model因此,可以非常容易的就设置映射关系、约束、relation关系例如,我们有一个 test 表,它只有一个 id 主键字段create table test(     id bigint(20) NOT NULL COMMENT 'id',              PRIMARY KEY (id) )ENGINE=InnoDB DEFAULT CHARSET=utf8; 然后,我们可以定义下面的 modelvar TestModel = function() {     this.$mid = "testModel";     this.$table = "test";     this.id = "$primary;type:Number"; }    module.exports = TestModel;在 TestModel 里,我们使用 $table 属性来设置需要映射的表名,对于 id 属性,我们用 primary 表明这是一个主键,并且我们给这个字段添加了一个 type 约束,限定它一定为 Number 类型Relation 在关系型数据库的表与表之间是可以有 relation 的,也即关系,有一对一、一对多、多对多这三种情况一对一 relation一对一关系意味着两张表,一张表有另外一张表的id引用(或者外键)。在model对象里面就是说,两个model,是一对一的比如,我们有两张表,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;然后,我们就可以定义这样的 modelvar 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 属性里的 prefixmodel 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 属性DAODAO 是领域对象模型的缩写,一般用于操作数据库bearcat-dao 提供 domainDaoSupport 对象,封装了基本的sql、cache操作。使用它也非常简单,直接依赖注入,然后通过 init 方法进行初始化simpleDao.jsvar 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为trueBEARCAT_DEBUG=true node xxx.js开启debug模式后,就能看到具体执行SQL的日志例子bearcat-todobearcat-dao example 标签:bearcat
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值