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实例daoCtx4.1);并将调用stdDaoMgr.addContext方法将daoCtx添加到stdDaoMgr中(4.2):
4.1 解析dao.xml文件的<context>元素得到daoCtx的过程为:
实例化一个DaoContext对象daoCtx
将其daoManager字段设为我们的stdDaoMgr
<context>id属性,则将daoCtxid字段取为此属性的值;
解析<context>的子元素:
4.1.1解析<txMgr>子元素,得到DaoTxMgr接口实例txMgr,设为daoCtx的相应字段,解析过程为:
根据<txMgr>子元素的type属性,实例化一个相应的DaoTxMgr实例txMgr
解析<txMgr><property>子元素,将所得property添加入properties
根据propertiestxMgr进行配置(即调用txMgr.configure方法);
4.1.2解析<dao>子元素,得到一个DaoImpl类实例daoImpl,然后将其加入daoCtx
4.1.2.1解析过程为:
4.1.2.1.1实例化一个DaoImpl类实例daoImpl
4.1.2.1.2daoImpldaoMgr字段设为我们的stdDaoMgr
4.1.2.1.3daoImpldaoCtx字段设为我们的daoCtx
4.1.2.1.4daoImpldaoIface字段设为<dao>iface属性值对应的class
4.1.2.1.5daoImpldaoImplementation字段设为<dao>implementation属性值对应的class
4.1.2.1.6根据implementation属性实例化一个DAO实现类,设为daoInstance字段值,注意,该实例一定是一个Dao接口实例,因为任何一个都继承自DaoTemplate,而DaoTemplate实现了Dao接口;
4.1.2.1.7创建一个当前DAO实现类的代理,设为daoImplproxy字段值,该代理在启用显式事务时会在调用委托方法前调用daoCtx.startTx方法;在使用隐式事务时则在调用委托方法的前后分别调用daoCtx.startTx方法和commitTx方法(在finally块中还调用daoCtx.endTx方法)。
4.1.2.2daoImpl加入daoCtx的过程为:以当前daoImpl填充一张从daoIfaceDaoImpl实例的表;
4.2 调用stdDaoMgr.addContext方法将daoCtx添加到stdDaoMgr中的过程为:
4.2.1以当前daoCtx填充一张由idDaoCtx实例的表;
4.2.2遍历daoCtx中存放的所有daoImpl,填充一张从daoIfacedaoCtx的表和一张从Dao接口实例(即daoImpl中的proxydaoInstance)到daoCtx的表;
5. 客户以某DaoIface调用DaoMgr.getDao方法得到一个DaoIface实现类实例xxxYyyDao的过程为:
stdDaoMgr查找其daoIfacedaoCtx的表,得到当前daoIface所在daoCtx,然后调用daoCtx.getDao方法:
    daoCtx查找其从daoIfaceDaoImpl实例的表,得到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实例daoTxdaoCtx将它放入一个线程变量中;
6.2 DaoIface实现类中,由于其一定继承自某个DaoTemplate,以调用其中的数据库访问方法,而这些数据库访问方法都会以自己作为参数调用daoMgrgetTx方法;该方法查找4.2.2中提到的从Dao接口实例到daoCtx的表,得到一个daoCtx,然后调用daoCtx.getTxdaoCtx.getTx将存储在线程变量中的daoTx实例返回;
6.3 daoTx实例包含数据库操作所需的关键元素,例如对于SqlMapDaoTx,其中就包含一个SqlMapClient实例,SqlMapDaoTemplate中的数据库访问方法(如insertqueryForList等)都是先调用daoMgr.getTx,得到daoTx实例,将其强制转化为SqlMapDaoTx实例,然后调用其getSqlMap方法得到SqlMapClient实例,再调用SqlMapClient实例中的相应方法;又如对于JDBC的情况,对应DaoTxConnectionDaoTx,该类包含一个,每次调用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、付费专栏及课程。

余额充值