1、基于iBatis的通用持久层对象ibatis介绍使用ibatis 提供的ORM机制,对业务逻辑实现人员而言,面对的是纯粹的Java对象, 这一层与通过Hibernate 实现ORM 而言基本一致,而对于具体的数据操作,Hibernate 会自动生成SQL 语句,而ibatis 则要求开发者编写具体的SQL 语句。相对Hibernate等 “全自动”ORM机制而言,ibatis 以SQL开发的工作量和数据库移植性上的让步,为系统 设计提供了更大的自由空间。作为“全自动”ORM 实现的一种有益补充,ibatis 的出现显 得别具意义。版权声明:任何获得Matrix授权的网站,转载时请务必保留以下作
2、者信息和链接作者:fellowArrayArray原文:关键字:iBatis;ORM一、为什么要设计“通用”的东西在大多数时候,我们所需要的持久层对象(PO)大多都是一张表(or视图)对应一个类。按照Hibernate的思想,就是抛开数据库的束缚,把焦点集中到业务对象中。而很多自动化工具的确让做到了通过表结构生成对应的对象,or通过对象自动生成表。对于小项目来说,一切都是简单的;对于有规范设计的项目来说,PO的设计也不是一件困难的工作。但是对于那些业务变动频繁的项目来说,改动PO可能成了一件很繁重的工作。试想一下,假设某个表需要增加一个字段:对于Hibernate(or iBaits),首先要
3、改配置文件,然后PO,然后DAO(也许没有),然后业务逻辑,然后JO,然后界面,etc,贯通了全部层次。恩,写程序的都不喜欢这些重复劳动,但是做企业级应用的谁不是每天在这些工作中打滚。研究过iBaits以后,发现有些通用的方法可以解决,就是设计一个通用的持久层对象。二、基于什么技术iBatis可以使用Map对象作为PO,Hibernate好像也有相关的功能(我没有细看,不确定)。iBatis执行一条指令的过程大概是这样的:其中圈圈1、2、3描述了iBatis最重要的三个对象。圈圈1:statement简单来说就是存储sql语句的配置信息,一个最简单的statement:insert into
4、PRODUCT (PRD_ID, PRD_DESCRIPTION) values (1, “Shih Tzu”)其中id属性是这个statement的唯一标识,全局不能重复。以上当然是最简单的了,没有参数也不需要返回值,但实际情况下基本都需要传入参数,下面就是介绍参数。圈圈2:参数对象主要分两种类型:parameterMap、parameterClass和Inline Parameter。其中parameterMap是配置文件定义传入参数表,如下:insert into PRODUCT (PRD_ID, PRD_DESCRIPTION) values (?,?);而parameterClass
5、是传入参数对象(JavaBean),如下:insert into PRODUCT values (#id#, #description#, #price#)Inline Parameter则是强化版的parameterClass,如下: insert into PRODUCT (PRD_ID, PRD_DESCRIPTION)values (#id:NUMERIC:-ArrayArrayArrayArrayArrayArray#, #description:VARCHAR:NO_ENTRY#);其中第一种方法看着就复杂,实际是为了兼容老版本留下来的,所以parameterClass是我们最常用
6、的方法。官方文档对parameterClass介绍很详细,因为这是核心之一,具体请自己查阅。有3个特性说明一下:a. parameterClass对象可以传入一个Map对象(or Map子类)。本来如果是传入JavaBean,程序会通过get/set来分析取得参数;而Map是key-value结构的,那程序会直接通过key来分析取参数。b. 看以下语句:insert into PRODUCT values (#id#, #description#, #price#, #classify.id#)蓝色部分#classify.id#翻译过来实际是product.getClassify().getI
7、d(),classify是Product对象的一个子对象。c. 在模板sql语句中除了“#”以外,还有“$”,它们两代表的意思当然不同了:select * from PRODUCT order by $preferredOrder$“#”在生成sql语句的过程中,会变成“?”,同时在参数表中增加一个参数;“$”则会直接替换成参数对象对应的值,例如上面的preferredOrder的值可能是“price”,则生成的sql语句就是:select * from PRODUCT order by price。*需要特别说明的是传入参数这一部分将会是后面正题“通用持久层对象”的核心,怎么个通用法,怎么设
8、计模板sql语句,都是在这部分上。圈圈3:结果对象跟参数对象差不多,也有两种,resultMap和resultClass,如下:resultMap就是配置文件中预定义了要取得的字段:select * from PRODUCTresultClass则是通过分析返回的字段,来填充结果对象:SELECT PER_ID as id, PER_FIRST_NAME as firstNameFROM PERSON WHERE PER_ID = #value#跟参数对象相反,结果对象一般使用resultMap形式。引用官方的话:使用resultClass的自动映射存在一些限制,无法指定输出字段的数据类型(如
9、果需要的话),无法自动装入相关的数据(复杂属性),并且因为需要ResultSetMetaData的信息,会对性能有轻微的不利影响。但使用resultMap,这些限制都可以很容易解决。三、正题来了,怎么做“通用持久层对象”1. 表结构:每个表都必须包含两个字段:id和parentId,其他字段按照需求来定义,其他各种索引、约束、关系之类的也按需求定义。2. 通用的持久层对象,CustomPO:public class CustomPO protected String moduleTable;/该PO对应的表名(视图名)protected int id;/表的idprotected int pa
10、rentID;/父表的id(如果有的话)protected Map fieldMap;/字段Map,核心,用于存储字段及其值public String getModuleTable()public void setModuleTable(String moduleTable)public int getId()public void setId(int id)public int getParentID()public void setParentID(int parentID)public Map getFieldMap()public void setFieldMap(Map fieldM
11、ap)public void copyFieldMap(Map fieldMap)/取得字段名列表public List getFieldList()/设置字段名列表。如果fieldMap没有相应的字段,则增加,字段值为null;如果有则不增加。public void setFieldList(List fieldList)/返回字段的“字段名 - 字段值”列表,使用com.fellow.pub.util.KeyValuePair对象作为存储public List getFieldValueList()那些成员变量的get/set就没什么说的,主要说说getFieldValueList()这个
12、方法。该方法返回一个列表,列表元素是一个key-value结构,简单来说就是把字段map序列化。在构造模板sql语句时会体现它的用途。3. iBatis对象配置文件CustomPO.xml:SELECT id, parentID$fieldList$ FROM $moduleTable$ WHERE id = #id#INSERT INTO $moduleTable$ (parentID$fieldValueList.key$)VALUES (#parentID#fieldValueList.value#)SELECT last_insert_id()UPDATE $moduleTable$
13、SET$fieldValueList.key$ = #fieldValueList.value# WHERE id = #id#DELETE FROM $moduleTable$ WHERE id = #id#要注意的地方如下:a. 跟一般的ibatis配置文件不一样,该配置中没有包含resultMap,使用的就是resultClass的方式(效率没那么高的那种)。当然,也可以使用resultMap,这样就要为每个表写自己的配置文件了。因此,在该设计没完成前,我暂时先使用resultClass的方式。b. 上面只列举了最简单的增删改以及按id查询,并没有更复杂的查询,为什么呢?因为我还在研究中
14、。研究通用的模板sql的写法。4. CustomPO对应的DAO:我使用了ibaits提供的DAO框架,很好用,不单支持ibatis的框架,还支持Hibernate、JDBC等等,而且是与ibatis本身独立的,完全可以单独使用。以后就不用自己写DAO框架了。一下是该DAO接口:public interface ICustomDAO /* * 通过传入moduleTable和id取得一条记录 */CustomPO findByID(String moduleTable, int id) throws Exception;/* * 通过传入CustomPO对象取得一条记录 * param po
15、CustomPO 该对象在传入前应该先设置moduleTable和id参数, * 并且使用setFieldList()函数设置字段列表(该设置决定所返回的字段)。 */CustomPO findByID(CustomPO po) throws Exception;/* * 通过传入moduleTable和parentID取得一条记录 */CustomPO findByParentID(String moduleTable, int parentID) throws Exception;/* * 通过传入CustomPO对象插入一条记录 * param po CustomPO 该对象在传入前应该
16、先设置moduleTable和id参数, * 并且使用setFieldMap()函数设置“字段-值”对。 */void insert(CustomPO po) throws Exception;/* * 通过传入CustomPO对象更新一条记录 * param po CustomPO 该对象在传入前应该先设置moduleTable和id参数, * 并且使用setFieldMap()函数设置“字段-值”对。 */void update(CustomPO po) throws Exception;/* * 删除moduleTable和id所对应的记录 */void delete(String mo
17、duleTable, int id) throws Exception;我没有把所有的方法都列出来,反正挺简单的,跟一般的DAO没什么分别。另外列几个实现的片断:a. 统一的数据装填函数,需要手工把id和parentID字段去掉。protected void fill(Map result, CustomPO po) Long returnId = (Long) result.get(id);Long returnParentID = (Long) result.get(parentID);result.remove(id);result.remove(parentID);if (return
18、Id != null) po.setId(returnId.intValue();if (returnParentID != null) po.setParentID(returnParentID.intValue();po.setFieldMap(result);b. 一般的查询,返回的是一个map,然后再用fill()函数/查询 Map result = (Map)this.queryForObject(customPO_findByID, po);/处理返回结果if(result = null)po = null;elsefill(result, po);c. 增删改,没有返回值,值得一
19、提的是增加操作完成后,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如果
20、不填默认与name相等,type默认为string,not-null和unique默认为false。配置文件的读取类在这里就省略了。为什么要自己定义一套这个框架?好像挺多此一举的,但是没办法,ibatis配置文件的信息是封闭的,我无法取得。另外我考虑的是:a. viewer层:在web开发中,我可以在这套配置框架的基础上,建立自己的标签,实现数据自动绑定的功能;GUI开发中也可以做相应的功能。b. module层:可以做通用的业务操作,不需要为每个业务都都做独立的业务逻辑。四、有什么优点、缺陷1. 优点:a. “通用”,一切都是为了通用,为了减少重复劳动,一个个项目面对不同的业务,其实说到底都
21、是那些操作。各种持久成框架已经带给我们不少的方便,但是在实际业务逻辑的处理上好像还没有一个这样的框架。b. 极大地减少代码量。前面说了,数据库改一个字段,PO、DAO、module、JO、viewer、validator全都要改一遍。使用了这套东西,可以把绝大部分的劳动都放在配置文件和UI上。当然,这是完美的设想,对于很多情况,业务逻辑还是要手工写的。c. 好像没有c了。2. 缺点:a. 通常通用的东西都缺乏灵活性,在我的实际应用中也发现了这样那样的问题,解决方法都是以通用为基本原则。但是如果针对的是某个项目,那就可以针对业务来修改了。b. 性能问题。因为使用resultClass,按照文档所说的,性能没有resultMap好。当然也可以使用resultMap,如前所说,就要对每个PO写配置文件了,工作量也不少。c. 也好像没有c了。
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1