原文出处:http://developer.51cto.com/art/200711/60967.htm
基于iBatis的通用持久层对象(1)
相对Hibernate等“全自动”ORM机制而言,iBatis以SQL开发的工作量和数据库移植性上的让步,为系统设计提供了更大的自由空间。研究过iBaits以后,发现有些通用的方法可以解决企业级应用的常规工作,就是设计一个通用的持久层对象。
AD:
iBatis介绍
使用iBatis提供的ORM机制,对业务逻辑实现人员而言,面对的是纯粹的Java对象,这一层与通过Hibernate实现ORM 而言基本一致,而对于具体的数据操作,Hibernate会自动生成SQL语句,而iBatis则要求开发者编写具体的SQL语句。相对Hibernate等“全自动”ORM机制而言,iBatis以SQL开发的工作量和数据库移植性上的让步,为系统设计提供了更大的自由空间。作为“全自动”ORM 实现的一种有益补充,iBatis的出现显得别具意义。
一、为什么要设计“通用”的东西
在大多数时候,我们所需要的持久层对象(PO)大多都是一张表(or视图)对应一个类。按照Hibernate的思想,就是抛开数据库的束缚,把焦点集中到业务对象中。而很多自动化工具的确做到了通过表结构生成对应的对象,or通过对象自动生成表。对于小项目来说,一切都是简单的;对于有规范设计的项目来说,PO的设计也不是一件困难的工作。但是对于那些业务变动频繁的项目来说,改动PO可能成了一件很繁重的工作。试想一下,假设某个表需要增加一个字段:对于Hibernate(or iBaits),首先要改配置文件,然后PO,然后DAO(也许没有),然后业务逻辑,然后JO,然后界面,etc,贯通了全部层次。
恩,写程序的都不喜欢这些重复劳动,但是做企业级应用的谁不是每天在这些工作中打滚。
研究过iBaits以后,发现有些通用的方法可以解决,就是设计一个通用的持久层对象。
二、基于什么技术
iBatis可以使用Map对象作为PO,Hibernate好像也有相关的功能(我没有细看,不确定)。
iBatis执行一条指令的过程大概是这样的:
图1 |
其中圈圈1、2、3描述了iBatis最重要的三个对象。
圈圈1:statement简单来说就是存储sql语句的配置信息,一个最简单的statement:
<statement id=”insertTestProduct” > |
其中id属性是这个statement的唯一标识,全局不能重复。
以上当然是最简单的了,没有参数也不需要返回值,但实际情况下基本都需要传入参数,下面就是介绍参数。
圈圈2:参数对象主要分两种类型:parameterMap、parameterClass和Inline Parameter。
其中parameterMap是配置文件定义传入参数表,如下:
<parameterMap id=”insert-product-param” class=”com.domain.Product”> |
而parameterClass是传入参数对象(JavaBean),如下:
<statement id=”statementName” parameterClass=” examples.domain.Product”> |
Inline Parameter则是强化版的parameterClass,如下:
<statement id=”insertProduct” parameterClass=”com.domain.Product”> |
其中第一种方法看着就复杂,实际是为了兼容老版本留下来的,所以parameterClass是我们最常用的方法。官方文档对parameterClass介绍很详细,因为这是核心之一,具体请自己查阅。有3个特性说明一下:
a、parameterClass对象可以传入一个Map对象(or Map子类)。本来如果是传入JavaBean,程序会通过get/set来分析取得参数;而Map是key-value结构的,那程序会直接通过key来分析取参数。
b、看以下语句:
<statement id=”statementName” parameterClass=” examples.domain.Product”> |
蓝色部分#classify.id#翻译过来实际是product.getClassify().getId(),classify是Product对象的一个子对象。
c、在模板sql语句中除了“#”以外,还有“$”,它们两代表的意思当然不同了:
<statement id=”getProduct” resultMap=”get-product-result”> |
“#”在生成sql语句的过程中,会变成“?”,同时在参数表中增加一个参数;
“$”则会直接替换成参数对象对应的值,例如上面的preferredOrder的值可能是“price”,则生成的sql语句就是:
select * from PRODUCT order by price |
*需要特别说明的是传入参数这一部分将会是后面正题“通用持久层对象”的核心,怎么个通用法,怎么设计模板sql语句,都是在这部分上。
圈圈3:结果对象跟参数对象差不多,也有两种,resultMap和resultClass,如下:
resultMap就是配置文件中预定义了要取得的字段:
<resultMap id=”get-product-result” class=”com.iBatis.example.Product”> |
resultClass则是通过分析返回的字段,来填充结果对象:
<statement id="getPerson" parameterClass=”int” resultClass="examples.domain.Person"> |
跟参数对象相反,结果对象一般使用resultMap形式。引用官方的话:使用resultClass的自动映射存在一些限制,无法指定输出字段的数据类型(如果需要的话),无法自动装入相关的数据(复杂属性),并且因为需要ResultSetMetaData的信息,会对性能有轻微的不利影响。但使用resultMap,这些限制都可以很容易解决。
三、正题来了,怎么做“通用持久层对象”
1、表结构
每个表都必须包含两个字段:id和parentId,其他字段按照需求来定义,其他各种索引、约束、关系之类的也按需求定义。
2、通用的持久层对象,CustomPO
public class CustomPO { |
那些成员变量的get/set就没什么说的,主要说说getFieldValueList()这个方法。该方法返回一个列表,列表元素是一个key-value结构,简单来说就是把字段map序列化。在构造模板sql语句时会体现它的用途。
3、iBatis对象配置文件CustomPO.xml
<sqlMap namespace="CustomPO"> |
要注意的地方如下:
a、跟一般的iBatis配置文件不一样,该配置中没有包含resultMap,使用的就是resultClass的方式(效率没那么高的那种)。当然,也可以使用resultMap,这样就要为每个表写自己的配置文件了。因此,在该设计没完成前,我暂时先使用resultClass的方式。
b、上面只列举了最简单的增删改以及按id查询,并没有更复杂的查询,为什么呢?因为我还在研究中。。。研究通用的模板sql的写法。
4、CustomPO对应的DAO
我使用了ibaits提供的DAO框架,很好用,不单支持iBatis的框架,还支持Hibernate、JDBC等等,而且是与iBatis本身独立的,完全可以单独使用。以后就不用自己写DAO框架了。该DAO接口是:
public interface ICustomDAO { |
我没有把所有的方法都列出来,反正挺简单的,跟一般的DAO没什么分别。
另外列几个实现的片断:
a、统一的数据装填函数,需要手工把id和parentID字段去掉。
protected void fill(Map result, CustomPO po) { |
b、一般的查询,返回的是一个map,然后再用fill()函数。
//查询 |
c、增删改,没有返回值,值得一提的是增加操作完成后,po里面的id会更新,具体看前面相关的statement。
//增删改 |
5、前面是通用的部分,光是通用是不够的。因此我另外建立了一套配置文件,记录字段对应关系。看看我所定义的一个配置文件,挺简单的:
<struct name="userInfo" table-name="tblUserInfo"> |
其中,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了。
五、后话
我总是喜欢写一些通用的东西,总是想把它设计成万能的。但是经过多次失败总结出来,我发现通用的东西在很多情况下都等于不能用。但我就是喜欢往通用方面想,这个毛病不知道什么时候才能改得了。其实在Delphi平台中,我早就实现了相关的东西,但是用delphi总是限制于ADO+Data Module这样掉牙的模式。现在转向java,发现有iBatis、hibernate这么多好的持久层框架,自然有移植必要了。
相关文章链接:Spring与iBATIS的集成