基于iBatis的通用持久层对象.docx
《基于iBatis的通用持久层对象.docx》由会员分享,可在线阅读,更多相关《基于iBatis的通用持久层对象.docx(8页珍藏版)》请在冰豆网上搜索。
基于iBatis的通用持久层对象
ibatis介绍
使用ibatis提供的ORM机制,对业务逻辑实现人员而言,面对的是纯粹的Java对象,这一层与通过Hibernate实现ORM而言基本一致,而对于具体的数据操作,Hibernate会自动生成SQL语句,而ibatis则要求开发者编写具体的SQL语句。
相对Hibernate等“全自动”ORM机制而言,ibatis以SQL开发的工作量和数据库移植性上的让步,为系统设计提供了更大的自由空间。
作为“全自动”ORM实现的一种有益补充,ibatis的出现显得别具意义。
版权声明:
任何获得Matrix授权的网站,转载时请务必保留以下作者信息和链接
作者:
fellowArrayArray
原文:
关键字:
iBatis;ORM
一、为什么要设计“通用”的东西
在大多数时候,我们所需要的持久层对象(PO)大多都是一张表(or视图)对应一个类。
按照Hibernate的思想,就是抛开数据库的束缚,把焦点集中到业务对象中。
而很多自动化工具的确让做到了通过表结构生成对应的对象,or通过对象自动生成表。
对于小项目来说,一切都是简单的;对于有规范设计的项目来说,PO的设计也不是一件困难的工作。
但是对于那些业务变动频繁的项目来说,改动PO可能成了一件很繁重的工作。
试想一下,假设某个表需要增加一个字段:
对于Hibernate(oriBaits),首先要改配置文件,然后PO,然后DAO(也许没有),然后业务逻辑,然后JO,然后界面,etc,贯通了全部层次。
恩,写程序的都不喜欢这些重复劳动,但是做企业级应用的谁不是每天在这些工作中打滚。
研究过iBaits以后,发现有些通用的方法可以解决,就是设计一个通用的持久层对象。
二、基于什么技术
iBatis可以使用Map对象作为PO,Hibernate好像也有相关的功能(我没有细看,不确定)。
iBatis执行一条指令的过程大概是这样的:
其中圈圈1、2、3描述了iBatis最重要的三个对象。
圈圈1:
statement简单来说就是存储sql语句的配置信息,一个最简单的statement:
insertintoPRODUCT(PRD_ID,PRD_DESCRIPTION)values(1,“ShihTzu”)
其中id属性是这个statement的唯一标识,全局不能重复。
以上当然是最简单的了,没有参数也不需要返回值,但实际情况下基本都需要传入参数,下面就是介绍参数。
圈圈2:
参数对象主要分两种类型:
parameterMap、parameterClass和InlineParameter。
其中parameterMap是配置文件定义传入参数表,如下:
insertintoPRODUCT(PRD_ID,PRD_DESCRIPTION)values(?
?
);
而parameterClass是传入参数对象(JavaBean),如下:
insertintoPRODUCTvalues(#id#,#description#,#price#)
InlineParameter则是强化版的parameterClass,如下:
insertintoPRODUCT(PRD_ID,PRD_DESCRIPTION) values(#id:
NUMERIC:
-ArrayArrayArrayArrayArrayArray#,#description:
VARCHAR:
NO_ENTRY#);
其中第一种方法看着就复杂,实际是为了兼容老版本留下来的,所以parameterClass是我们最常用的方法。
官方文档对parameterClass介绍很详细,因为这是核心之一,具体请自己查阅。
有3个特性说明一下:
a.parameterClass对象可以传入一个Map对象(orMap子类)。
本来如果是传入JavaBean,程序会通过get/set来分析取得参数;而Map是key-value结构的,那程序会直接通过key来分析取参数。
b.看以下语句:
insertintoPRODUCTvalues(#id#,#description#,#price#,#classify.id#)
蓝色部分#classify.id#翻译过来实际是product.getClassify().getId(),classify是Product对象的一个子对象。
c.在模板sql语句中除了“#”以外,还有“$”,它们两代表的意思当然不同了:
select*fromPRODUCTorderby$preferredOrder$
“#”在生成sql语句的过程中,会变成“?
”,同时在参数表中增加一个参数;
“$”则会直接替换成参数对象对应的值,例如上面的preferredOrder的值可能是“price”,则生成的sql语句就是:
select*fromPRODUCTorderbyprice。
*需要特别说明的是传入参数这一部分将会是后面正题“通用持久层对象”的核心,怎么个通用法,怎么设计模板sql语句,都是在这部分上。
圈圈3:
结果对象跟参数对象差不多,也有两种,resultMap和resultClass,如下:
resultMap就是配置文件中预定义了要取得的字段:
select*fromPRODUCT
resultClass则是通过分析返回的字段,来填充结果对象:
SELECTPER_IDasid,PER_FIRST_NAMEasfirstName FROMPERSONWHEREPER_ID=#value#
跟参数对象相反,结果对象一般使用resultMap形式。
引用官方的话:
使用resultClass的自动映射存在一些限制,无法指定输出字段的数据类型(如果需要的话),无法自动装入相关的数据(复杂属性),并且因为需要ResultSetMetaData的信息,会对性能有轻微的不利影响。
但使用resultMap,这些限制都可以很容易解决。
三、正题来了,怎么做“通用持久层对象”
1.表结构:
每个表都必须包含两个字段:
id和parentId,其他字段按照需求来定义,其他各种索引、约束、关系之类的也按需求定义。
2.通用的持久层对象,CustomPO:
publicclassCustomPO{ protectedStringmoduleTable;//该PO对应的表名(视图名) protectedintid;//表的id protectedintparentID;//父表的id(如果有的话) protectedMapfieldMap;//字段Map,核心,用于存储字段及其值 publicStringgetModuleTable() publicvoidsetModuleTable(StringmoduleTable) publicintgetId() publicvoidsetId(intid) publicintgetParentID() publicvoidsetParentID(intparentID) publicMapgetFieldMap() publicvoidsetFieldMap(MapfieldMap) publicvoidcopyFieldMap(MapfieldMap) //取得字段名列表 publicListgetFieldList() //设置字段名列表。
如果fieldMap没有相应的字段,则增加,字段值为null;如果有则不增加。
publicvoidsetFieldList(ListfieldList) //返回字段的“字段名-字段值”列表,使用com.fellow.pub.util.KeyValuePair对象作为存储 publicListgetFieldValueList() }
那些成员变量的get/set就没什么说的,主要说说getFieldValueList()这个方法。
该方法返回一个列表,列表元素是一个key-value结构,简单来说就是把字段map序列化。
在构造模板sql语句时会体现它的用途。
3.iBatis对象配置文件CustomPO.xml:
SELECTid,parentID$fieldList[]$FROM$moduleTable$WHEREid=#id# INSERTINTO$moduleTable$(parentID $fieldValueList[].key$ ) VALUES(#parentID# #fieldValueList[].value# ) SELECTlast_insert_id() UPDATE$moduleTable$SET $fieldValueList[].key$=#fieldValueList[].value# WHEREid=#id# DELETEFROM$moduleTable$WHEREid=#id#
要注意的地方如下:
a.跟一般的ibatis配置文件不一样,该配置中没有包含resultMap,使用的就是resultClass的方式(效率没那么高的那种)。
当然,也可以使用resultMap,这样就要为每个表写自己的配置文件了。
因此,在该设计没完成前,我暂时先使用resultClass的方式。
b.上面只列举了最简单的增删改以及按id查询,并没有更复杂的查询,为什么呢?
因为我还在研究中。
。
。
研究通用的模板sql的写法。
4.CustomPO对应的DAO:
我使用了ibaits提供的DAO框架,很好用,不单支持ibatis的框架,还支持Hibernate、JDBC等等,而且是与ibatis本身独立的,完全可以单独使用。
以后就不用自己写DAO框架了。
一下是该DAO接口:
publicinterfaceICustomDAO{ /** *通过传入moduleTable和id取得一条记录 */ CustomPOfindByID(StringmoduleTable,intid)throwsException; /** *通过传入CustomPO对象取得一条记录 *@parampoCustomPO该对象在传入前应该先设置moduleTable和id参数, *并且使用setFieldList()函数设置字段列表(该设置决定所返回的字段)。
*/ CustomPOfindByID(CustomPOpo)throwsException; /** *通过传入moduleTable和parentID取得一条记录 */ CustomPOfindByParentID(StringmoduleTable,intparentID)throwsException; /** *通过传入CustomPO对象插入一条记录 *@parampoCustomPO该对象在传入前应该先设置moduleTable和id参数, *并且使用setFieldMap()函数设置“字段-值”对。
*/ voidinsert(CustomPOpo)throwsException; /** *通过传入CustomPO对象更新一条记录 *@parampoCustomPO该对象在传入前应该先设置moduleTable和id参数, *并且使用setFieldMap()函数设置“字段-值”对。
*/ voidupdate(CustomPOpo)throwsException; /** *删除moduleTable和id所对应的记录 */ voiddelete(StringmoduleTable,intid)throwsException; }
我没有把所有的方法都列出来,反正挺简单的,跟一般的DAO没什么分别。
另外列几个实现的片断:
a.统一的数据装填函数,需要手工把id和parentID字段去掉。
protectedvoidfill(Mapresult,CustomPOpo){ LongreturnId=(Long)result.get("id"); LongreturnParentID=(Long)result.get("parentID"); result.remove("id"); result.remove("parentID"); if(returnId!
=null)po.setId(returnId.intValue()); if(returnParentID!
=null)po.setParentID(returnParentID.intValue()); po.setFieldMap(result); }
b.一般的查询,返回的是一个map,然后再用fill()函数
//查询 Mapresult=(Map)this.queryForObject("customPO_findByID",po); //处理返回结果 if(result==null) po=null; else fill(result,po);
c.增删改,没有返回值,值得一提的是增加操作完成后,po里面的id会更新,具体看前面相关的statement。
//增删改 this.insert("customPO_insert",po); this.update("customPO_update",po); this.delete("customPO_delete",po);
5.前面是通用的部分,光是通用是不够的。
因此我另外建立了一套配置文件,记录字段对应关系。
看看我所定义的一个配置文件,挺简单的:
。
。
。
其中,name是字段名,column是字段对应数据表的字段名,type是字段类型,not-null是是否不能为空,unique是是否唯一。
只有name这个属性是必填的,column如果不填默认与name相等,type默认为string,not-null和unique默认为false。
配置文件的读取类在这里就省略了。
为什么要自己定义一套这个框架?
好像挺多此一举的,但是没办法,ibatis配置文件的信息是封闭的,我无法取得。
另外我考虑的是:
a.viewer层:
在web开发中,我可以在这套配置框架的基础上,建立自己的标签,实现数据自动绑定的功能;GUI开发中也可以做相应的功能。
b.module层:
可以做通用的业务操作,不需要为每个业务都都做独立的业务逻辑。
四、有什么优点、缺陷
1.优点:
a.“通用”,一切都是为了通用,为了减少重复劳动,一个个项目面对不同的业务,其实说到底都是那些操作。
各种持久成框架已经带给我们不少的方便,但是在实际业务逻辑的处理上好像还没有一个这样的框架。
b.极大地减少代码量。
前面说了,数据库改一个字段,PO、DAO、module、JO、viewer、validator全都要改一遍。
使用了这套东西,可以把绝大部分的劳动都放在配置文件和UI上。
当然,这是完美的设想,对于很多情况,业务逻辑还是要手工写的。
c.好像没有c了。
2.缺点:
a.通常通用的东西都缺乏灵活性,在我的实际应用中也发现了这样那样的问题,解决方法都是以通用为基本原则。
但是如果针对的是某个项目,那就可以针对业务来修改了。
b.性能问题。
因为使用resultClass,按照文档所说的,性能没有resultMap好。
当然也可以使用resultMap,如前所说,就要对每个PO写配置文件了,工作量也不少。
c.也好像没有c了。