#
在20世纪60年代后期引入的面向对象技术引起了一场革命。到20世纪80年代后,面向对象的技术已经成为了行业的主流,其原因多种多样:面向对象不仅简化了界面的开发,而且也提供了一种更加灵活、简单数据处理方法,这种方法从根本上改变了应用程序的构建方法。不再像关系型数据库一样用死板的二维表格来表示数据,对象技术使用类对数据进行描述。一个对象是一个类的实例,就像一颗特定的橡树是橡树类的实例一样。
对象技术使用继承方案,使得类是按等级设计的。“橡树”类能够从更加普遍的类“树”继承数据结构和数据行为。
对象技术能够更好地描述我们所见的世界,面向对象的语言已经被证实在大多数编程领域更加通用。他们使得编程语言更加接近自然语言和多数软件开发领域的主流思想。面向对象是一个新的典范,它的影响将持久而深远。
面向对象的特性很快被添加到各种成熟的语言中,并因此成就了一些语言,如C++。新的面向对象的开发环境出现了,包括Visual Basic,Visual C++,PowerBuilder,Delphi,以及Caché。尽管面向对象的技术在高级开发环境下受到了广泛支持,它还是需要花一定的时间形成正规的课程。而且还需要花更长的时间来构建一个真正的基于对象的世界——我们目前还没有到达这样一个阶段。
万维网上对象技术的发展
随着万维网(World Wide Web)转变为交换各种信息的手段,面向对象的编程语言Java成为Web开发者的最爱。基于C++,Java能够用来创建可以在浏览器执行的小程序(Java applets)。
Sun为了促进Java的发展免费提供Java环境。在短短几年内,成百上万的Java环境被复制下载,Java渗透到世界的每一个角落。同时Java引发了更多的面向对象语言,如JavaScript,C#以及Jscript。Internet的发展也培育了一些新的面向对象语言像Perl和PHP。现在的开发者使用面向对象的技术已经是理所当然的了。
对象的崛起
对象技术影响了软件开发的各个方面。对象建模已经占领了应用建模的市场,标准UML建模方法独占鳌头。
20世纪90年代,面向对象中间件产品的出现为面向对象的应用提供了安全交流服务。当1998年JMS(Java Messaging Serivce)的出现,使得中间件市场向前跨越了一大步。JMS定义了一整套消息传递的应用编程接口(APIs),使得经认证的J2EE应用必须引入JMS服务器。这进一步强化了标准化进程,大大降低了中间件的费用,提供了编写企业范围基于对象的应用程序平台。
XML和Web服务
1998年,HTML,专门用于网页设计的标识语言,经过进一步发展并标准化,创造出了XML(扩展的标识语言)。XML提供了一整套语法,能够用于创建与存储在数据库中定义相似的自定义数据格式,可以。有了XML,程序能够把定义附加在数据上,能够交换数据和数据含义。XML能够使得有特定标准的数据模型(如发票或者购买订单)的定义能够在公司内部或者公司之间进行数据交换。XML引发了Web服务的兴起——在不需要客户定制的情况下,程序能够与其他程序立即交互。现在出现了两种Web服务环境——J2EE和.NET。像SQL一样,XML为程序员提供了获取数据的标准,但XML同时还提供了一种在对象层定义数据的标准语言。XML和对象技术一样迅速成长。结果,数据对象的新标准和基于XML的新的开发产品出现了。
对象数据库——缺失的一环
与软件开发各个环节中对象技术的快速应用形成鲜明对比,对象数据库直到现在才开始逐渐被人们所接受。对象数据的迟缓行动原因有很多。
早期的对象语言没有考虑数据存储。程序在内存数据上工作,数据作为文件存储,当程序下次运行时数据也作为文件被读取。这种方法使得应用程序之间不可能共享数据,数据的恢复、管理、扩展几乎不可能。
目前在市场上已经有大量的面向对象数据库产品:Versant,Objectivity,ObjectStore,GemStone等等。他们为面向对象的开发环境提供了相应的数据存储。这些产品满足了最初的热情,甚至这些产品被期望能够打造一个新的数据库市场——甚至可能成为市场的领袖。
但不幸的是,这些对象数据库出现时,关系型数据库供应商已经积聚了巨大的动力,并占领了大量市场份额。在标准的SQL接口下,访问关系型数据库的面向对象程序很容易写。相反,多数早期的对象数据完全不提供SQL接口,不适合任何查询应用程序。结果,对象数据库在商业上没有建立坚实的基础。他们在应用领域只创建了一个小市场来管理和存储复杂对象如CAD/CAM,电信业、多媒体、人工智能,模拟金融设备、病人诊治跟踪系统以及科学应用。
数据库市场从未特别关注过对象数据库,直到对象定义语言XML出现,这种情况才有所改变,促进了对象数据库的再次呈现,因为他们管理XML定义的数据是最合适的。使用XML,必然会提高存储复杂数据的需求,将进一步引发对象数据库的复苏。
03年9月份InfoWorld公布了一项开发员调查,其中有一个惊奇的结果,89.2%的被调查者说他们使用关系型数据库,52%的被调查者说他们使用面向对象或者XML数据库。当问及有关存储数据的类型时,40.2%的人说他们存储持久的对象,58.9%的人说他们存储XML数据,89%的人说他们存储关系型数据。Baroudi Bloor相信对象数据库比我们想象的用的更加广泛,随着需求的激增,将进一步扩大市场份额。
InfoWorld的调查还显示了面向对象的语言是新应用开发的主流选择。我们相信这些统计数字反映了当今开发员面临的困境。他们需要与他们一直使用的面向对象语言有更好协调性的数据库,但他们有需要关系型数据库所提供的查询能力。
关系型数据库——另一半是如何存在的
只要有程序,就会有数据。IT行业最早具有商业价值之一的就是数据管理。自动的数据管理意味着业务能够扩展、具有竞争力,没有它就不可能。所以毫无疑问机智的商业技术员很早把目光聚集在数据管理市场。在对象数据库产生之前的20年,E.F Codd博士提出的关系型理论找到了出路,开发出商业的关系型数据库产品。在80年中期,在IT领域有一个宗教式的信仰,认为数据的所有理论问题都已经解决,实践的问题也会随之解决。然而,很明显,事实并不是这样。
关系型数据库把数据存储在简单的两维表中,这是一种表达大量数据的有效方法,而且程序员也易于理解。关系型数据库使用SQL建立了一种标准的数据访问语言。关系型数据库有一个逻辑和物理形式清楚的结构,这种结构使得应用程序对数据结构是透明的,而且在很多商业应用程序中工作的很好。
然而,关系理论的基础之一是数据和使用数据的程序能够而且应该是相互独立的。这与对象技术的整个理念是不一致的。对象技术鼓励设计者使用对象而不是表来思考数据。对象和使用对象的方法是不可能彼此分开的。
如果把汽车作为一个复杂的对象来考虑。当你使用汽车时,你使用一辆完整的汽车,作为一个东西——一个对象来使用。与汽车相联系的有很多动作(也就是面向对象术语中的方法)。你驾驶汽车,进行换档,发信号,开车灯,等等。如果汽车是一个对象,这些动作就是对象的方法,他们对汽车而言是基础性的。这些动作独立于汽车的想法是荒唐的。当你把你的车停在车库,你把它作为一个东西来存储。而不是分别在车库中的某些地方存放方向盘,转换器,信号器,车灯。数据和它相对应的处理过程也不能而且也不应该被隔离开来。在对象数据库中他们是不分开的。
事实上,这两种观点各有优缺点。有些处理过程确实是独立于数据的。尤其是访问大量数据的查询操作。简单的查询就是根据一些标准来选取数据,而不关心数据是什么,也不用关心数据是如何被组织的,只要它能快速的被取出就可以了。查询是独立于数据的,但对象方法则不是。
关系型数据库的局限性
关系型数据库有比我们想的更多的局限性。存储和表示一些相当普通的数据结构也是非常困难的。试想一条公交线路——简单,有序的一组站点。关系型数据库以无序的方式存放表,只有创建一个特殊的索引,才能提取有序的数据。对象数据库就没有这个问题,它有有序的数组,不需要索引——这种索引是因为关系数据结构的局限性而要求创建的人工索引。
另一个简单的例子是产品用料单——在制造系统中记录一个产品和它的组件。组件自身也许还有组件,组件的组件还有组件,以此类推。一个关系型数据表不能表达这种部件与部件的部件之间的关系。而这些关系却是重要的数据。查询一个产品数据库,它的所有组件应该是一目了然的。关系型数据库结构使得开发员花费很多的工作来回答这种简单的查询,非常的复杂、困难。与这个例子类似的例子:地图和它的路、河、路标;网站和它的页面、链接以及图像。实际上,搜集的信息越复杂,等级层次和交叉层次就越多,在简单二维表的关系型数据库就越不可能表达清楚。对象数据库没有这样的限制,事实上,他们就是为了解决这个问题而设计的。
虽然关系型数据库发展成熟,在这十年中发展也非常迅猛,但我们还听到一些项目因为所使用的关系数据的性能不是很好而导致失败。通常,是因为关系型数据库物理上存储数据的方法导致的。对开发员而言,为了集合他们所需的数据,他们常常不得不进行这个表与另一个表联接,再与另外的表联接,然后再与另一个表联接。为了提取数据,数据库运行优化程序来判断提取数据的最好方法,然后再提取数据。这样的处理常常要花费很长的时间,结果就大大影响了性能。尽管关系型数据库优化器已经改善了运行时间,但他们还需要比对象数据库更多的处理时间。
关系型数据库和“阻抗不匹配”障碍
关系型数据的一个问题是他们所使用的基本数据结构是一种二维形式的表。在关系理论中,数据应该被组织成规范的表——也就是数据应该按唯一的方式组织,使得程序员能够消除冗余,确保数据变化的一致性。这种设计技术的引入确保了关系表中的数据是一组独立的、通过键相关的数据。这种技术来自集合论的数学理论,但问题是集合论不能表达数据之间所有的关系和结构。
以规范的方式存储数据常常要求程序员在存入数据库之前分解对象,并且重新组织数据,但要使用它是,在使用SQL查询(多重连接)。就像在车库中存储车时,你把它的门、椅子、轮子等等分别卸下来存放。这是非常耗时的,而是也是没有任何意义的。
但面向对象的语言占主导地位时,问题就越发明显了。这个问题通常被称为对象-关系不匹配障碍。这个问题是由于面向对象语言和关系型数据库使用语言的方法不同导致的,结果这个问题只能有程序员自己来解决。事实上,大多数关系型数据库在使用的时候并不是完全规范的,但即使是这样,不匹配问题还是发生,对编程人员的工作造成了很大的困难。我们可以估计使用关系型数据库的面向对象开发员25%到40%的时间用于编写代码来解决对象与关系表的匹配问题。
也许这个根本性困难产生了对对象数据的强烈需求,但多数对象数据库也有一个很大的问题:他们对SQL的支持很少。而许多软件工具需要SQL接口,尤其是商业智能应用。甚至有SQL接口的对象数据库也不能创建用于管理商业智能应用所产生的这类查询机制。
对象-关系数据库
关系型数据库的供应商并没有忽视对象的出现。显然,规范复杂数据是没有意义的。举个极端的例子,如果你要规范一个位图形式的图像——是一系列的象素表示的——你最终要得出一个表,这个表的行是象素,并且主键的属性反映他们的顺序。很明显最好是把这个数据作为一个对象来存储。
他们提出了“对象-关系”数据库的创意,这个创意中保留了关系型数据库的结构,但允许关系表中的列含有一个复杂的对象。这些对象能够捆绑处理复杂数据的处理过程(一种存储过程)。并且SQL能够允许调用与关系型等同的“对象方法”。
这种方法是对数据关系理论的一种嘲弄,事实上,它完全忽略了这个理论,但又允许复杂数据(地图,矢量图,图表,甚至整个表格)被定义为一个项目存放在关系结构中。因此,这些功能被实现并商品化。Informix称它的嵌入过程为DateBlades,Oracle称之为Cartridges。
对象-关系数据库成为存储数据时对象数据库的一种替代方案,但根本的问题它并没有解决。对象-关系数据库还是受不匹配障碍的困扰。
对象数据库与关系型数据库
实践中,对象数据库相对于关系数据库有显著的优势。
他们能更快的运行事务处理程序
他们能够更有效的处理对象
他们能够提供更好的开发效率
他们能够管理更容易
在一些例子中,因为是性能方面的原因,用对象数据库能够替代关系型数据库。在不能存储复杂对象的大规模的业务处理程序中确实是这样的——也许有些人会认为这个必然是关系型数据库的领地。
对象数据库最大的性能优势是他们不必像关系型数据库一样在数据使用之前先连接数据。他们就以使用数据的方式存储数据,这就大大提高了性能。对象数据库能够使用缓存技术,这样就使得在请求数据时数据就已经存放在内存中了。对象数据库在抽取数据时几乎不需要进行优化。
但开发一个新的系统,处理复杂数据如文档、复杂图表、网页、多媒体等的需求不断增长时,这些需求对象数据库可以很好的满足。
当今面向对象的前景
在软件开发的各个方面使用对象技术的人群都在不停地增长。甚至在最后一个领域——数据库——尽管对象数据库还没有取代关系型数据库,这种增长也是十分显著的。InfoWorld报告说52%的开发员在使用对象数据库或者XML数据库(通常也是一种对象数据库)。还有一些选择混合形式的数据库,这种数据库能比较容易地使用对象结构。随着新应用程序开发过程中Web接口成为一个必不可少地部分,Web服务成为应用系统交互地一种可行的机制,构建一个面向对象的世界似乎是当今的现实。
03年9月的InfoWorld调查也显示了使用面向对象语言的程序员几乎无处不在。事实上,尽管有些人宣称使用C语言,但是面向对象的语言还是成为当今90%的程序员的选择。调查也显示了程序员比较喜欢基于Web的应用,易用的对象编程和脚本语言。随着越来越多的有着正规培训的软件工程师进入市场,面向对象技术将成为新应用开发的唯一选择。
结论
也许关系型数据库将继续领导数据库市场,而对象数据库在市场上只占有一席之地。也许对象数据库将进一步提升市场份额,因为他们能够处理当今使用的复杂的数据。然而,我们认为还有其他的可能:数据库技术可能发展出一种真正的混合型产品,这种产品能提供关系接口和对象接口双重优势。我们知道这是有可能的。事实上,至少有一种产品,来自InterSystems的Caché,就是这样一个产品。(Caché数据库,描述他自己时,既不是说是关系型的,也不是说是对象的,而是后关系型数据库)。数据库供应商——不管他们的产品是属于关系型还是对象型——都会朝着这个方向前进的。
这种混合产品的方法包括给数据库提供一个映射层,程序员通过映射层访问数据库。映射层应该基于开发的标准以解决不匹配障碍问题。数据库的调用能够用SQL完成,也可以直接请求对象类或者类的集合。映射层能够把这些调用转换为对数据库的物理数据请求以抽取数据。这种方法将消除不匹配的障碍。
改变任何一种数据类型都是非常大的挑战。对象数据库需要快速索引能力,以从庞大的数据集中抽取数据。在这方面做得比较好的关系型数据库使用位图索引技术,但数据一旦更新,这些索引就需要重新建立。因为这个原因,很少有对象数据有这个功能。对关系数据库而言,他们需要提供更加灵活的物理数据结构。在发展过程中,关系型数据库倾向于在物理层使用表。他们需要放弃这种不灵活的限制,允许存储多种数据结构。数据库使用者将获得最大的收益。试想把对象数据库的优势和关系型数据库的优势整合在一起:
好的处理性能
复杂数据管理
管理简便
快速开发
灵活的查询功能
标准的数据访问接口
更好地适用于商业智能应用
这种混合产品使使用一个数据库引擎成为可能,并且所有应用只有一个数据定义集。Baroudi Bloor相信企业界需要混合式的数据库产品。供应商们必须放弃他们对关系数据库宗教式的倾向,转向更具优势的混合式的数据库,否则的话他们将陷于COBOL以及打孔卡片的深渊而不能自拨。
做数据仓库系统,ETL是关键的一环。说大了,ETL是数据整合解决方案,说小了,就是倒数据的工具。回忆一下工作这么些年来,处理数据迁移、转换的工作倒还真的不少。但是那些工作基本上是一次性工作或者很小数据量,使用access、DTS或是自己编个小程序搞定。可是在数据仓库系统中,ETL上升到了一定的理论高度,和原来小打小闹的工具使用不同了。究竟什么不同,从名字上就可以看到,人家已经将倒数据的过程分成3个步骤,E、T、L分别代表抽取、转换和装载。
其实ETL过程就是数据流动的过程,从不同的数据源流向不同的目标数据。但在数据仓库中,ETL有几个特点,一是数据同步,它不是一次性倒完数据就拉到,它是经常性的活动,按照固定周期运行的,甚至现在还有人提出了实时ETL的概念。二是数据量,一般都是巨大的,值得你将数据流动的过程拆分成E、T和L。
现在有很多成熟的工具提供ETL功能,例如datastage、powermart等,且不说他们的好坏。从应用角度来说,ETL的过程其实不是非常复杂,这些工具给数据仓库工程带来和很大的便利性,特别是开发的便利和维护的便利。但另一方面,开发人员容易迷失在这些工具中。举个例子,VB是一种非常简单的语言并且也是非常易用的编程工具,上手特别快,但是真正VB的高手有多少?微软设计的产品通常有个原则是“将使用者当作傻瓜”,在这个原则下,微软的东西确实非常好用,但是对于开发者,如果你自己也将自己当作傻瓜,那就真的傻了。ETL工具也是一样,这些工具为我们提供图形化界面,让我们将主要的精力放在规则上,以期提高开发效率。从使用效果来说,确实使用这些工具能够非常快速地构建一个job来处理某个数据,不过从整体来看,并不见得他的整体效率会高多少。问题主要不是出在工具上,而是在设计、开发人员上。他们迷失在工具中,没有去探求ETL的本质。
可以说这些工具应用了这么长时间,在这么多项目、环境中应用,它必然有它成功之处,它必定体现了ETL的本质。如果我们不透过表面这些工具的简单使用去看它背后蕴涵的思想,最终我们作出来的东西也就是一个个独立的job,将他们整合起来仍然有巨大的工作量。大家都知道“理论与实践相结合”,如果在一个领域有所超越,必须要在理论水平上达到一定的高度
探求ETL本质之一
ETL的过程就是数据流动的过程,从不同异构数据源流向统一的目标数据。其间,数据的抽取、清洗、转换和装载形成串行或并行的过程。ETL的核心还是在于T这个过程,也就是转换,而抽取和装载一般可以作为转换的输入和输出,或者,它们作为一个单独的部件,其复杂度没有转换部件高。和OLTP系统中不同,那里充满这单条记录的insert、update和select等操作,ETL过程一般都是批量操作,例如它的装载多采用批量装载工具,一般都是DBMS系统自身附带的工具,例如Oracle SQLLoader和DB2的autoloader等。
ETL本身有一些特点,在一些工具中都有体现,下面以datastage和powermart举例来说。
1、静态的ETL单元和动态的ETL单元实例;一次转换指明了某种格式的数据如何格式化成另一种格式的数据,对于数据源的物理形式在设计时可以不用指定,它可以在运行时,当这个ETL单元创建一个实例时才指定。对于静态和动态的ETL单元,Datastage没有严格区分,它的一个Job就是实现这个功能,在早期版本,一个Job同时不能运行两次,所以一个Job相当于一个实例,在后期版本,它支持multiple instances,而且还不是默认选项。Powermart中将这两个概念加以区分,静态的叫做Mapping,动态运行时叫做Session。
2、ETL元数据;元数据是描述数据的数据,他的含义非常广泛,这里仅指ETL的元数据。主要包括每次转换前后的数据结构和转换的规则。ETL元数据还包括形式参数的管理,形式参数的ETL单元定义的参数,相对还有实参,它是运行时指定的参数,实参不在元数据管理范围之内。
3、数据流程的控制;要有可视化的流程编辑工具,提供流程定义和流程监控功能。流程调度的最小单位是ETL单元实例,ETL单元是不能在细分的ETL过程,当然这由开发者来控制,例如可以将抽取、转换放在一个ETL单元中,那样这个抽取和转换只能同时运行,而如果将他们分作两个单元,可以分别运行,这有利于错误恢复操作。当然,ETL单元究竟应该细分到什么程度应该依据具体应用来看,目前还没有找到很好的细分策略。比如,我们可以规定将装载一个表的功能作为一个ETL单元,但是不可否认,这样的ETL单元之间会有很多共同的操作,例如两个单元共用一个Hash表,要将这个Hash表装入内存两次。
4、转换规则的定义方法;提供函数集提供常用规则方法,提供规则定义语言描述规则。
5、对数据的快速索引;一般都是利用Hash技术,将参照关系表提前装入内存,在转换时查找这个hash表。Datastage中有Hash文件技术,Powermart也有类似的Lookup功能。
探求ETL本质之二(分类)
昨在IT-Director上阅读一篇报告,关于ETL产品分类的。一般来说,我们眼中的ETL工具都是价格昂贵,能够处理海量数据的家伙,但是这是其中的一种。它可以分成4种,针对不同的需求,主要是从转换规则的复杂度和数据量大小来看。它们包括
1、交互式运行环境,你可以指定数据源、目标数据,指定规则,立马ETL。这种交互式的操作无疑非常方便,但是只能适合小数据量和复杂度不高的ETL过程,因为一旦规则复杂了,可能需要语言级的描述,不能简简单单拖拖拽拽就可以的。还有数据量的问题,这种交互式必然建立在解释型语言基础上,另外他的灵活性必然要牺牲一定的性能为代价。所以如果要处理海量数据的话,每次读取一条记录,每次对规则进行解释执行,每次在写入一条记录,这对性能影响是非常大的。
2、专门编码型的,它提供了一个基于某种语言的程序框架,你可以不必将编程精力放在一些周边的功能上,例如读文件功能、写数据库的功能,而将精力主要放在规则的实现上面。这种近似手工代码的性能肯定是没话说,除非你的编程技巧不过关(这也是不可忽视的因素之一)。对于处理大数据量,处理复杂转换逻辑,这种方式的ETL实现是非常直观的。
3、代码生成器型的,它就像是一个ETL代码生成器,提供简单的图形化界面操作,让你拖拖拽拽将转换规则都设定好,其实他的后台都是生成基于某种语言的程序,要运行这个ETL过程,必须要编译才行。Datastage就是类似这样的产品,设计好的job必须要编译,这避免了每次转换的解释执行,但是不知道它生成的中间语言是什么。以前我设计的ETL工具大挪移其实也是归属于这一类,它提供了界面让用户编写规则,最后生成C++语言,编译后即可运行。这类工具的特点就是要在界面上下狠功夫,必须让用户轻松定义一个ETL过程,提供丰富的插件来完成读、写和转换函数。大挪移在这方面就太弱了,规则必须手写,而且要写成标准c++语法,这未免还是有点难为最终用户了,还不如做成一个专业编码型的产品呢。另外一点,这类工具必须提供面向专家应用的功能,因为它不可能考虑到所有的转换规则和所有的读写,一方面提供插件接口来让第三方编写特定的插件,另一方面还有提供特定语言来实现高级功能。例如Datastage提供一种类Basic的语言,不过他的Job的脚本化实现好像就做的不太好,只能手工绘制job,而不能编程实现Job。
4、最后还有一种类型叫做数据集线器,顾名思义,他就是像Hub一样地工作。将这种类型分出来和上面几种分类在标准上有所差异,上面三种更多指ETL实现的方法,此类主要从数据处理角度。目前有一些产品属于EAI(Enterprise Application Integration),它的数据集成主要是一种准实时性。所以这类产品就像Hub一样,不断接收各种异构数据源来的数据,经过处理,在实施发送到不同的目标数据中去。
虽然,这些类看似各又千秋,特别在BI项目中,面对海量数据的ETL时,中间两种的选择就开始了,在选择过程中,必须要考虑到开发效率、维护方面、性能、学习曲线、人员技能等各方面因素,当然还有最重要也是最现实的因素就是客户的意象。
探求ETL本质之三(转换)
ETL探求之一中提到,ETL过程最复杂的部分就是T,这个转换过程,T过程究竟有哪些类型呢?
一、宏观输入输出
从对数据源的整个宏观处理分,看看一个ETL过程的输入输出,可以分成下面几类:
1、大小交,这种处理在数据清洗过程是常见了,例如从数据源到ODS阶段,如果数据仓库采用维度建模,而且维度基本采用代理键的话,必然存在代码到此键值的转换。如果用SQL实现,必然需要将一个大表和一堆小表都Join起来,当然如果使用ETL工具的话,一般都是先将小表读入内存中再处理。这种情况,输出数据的粒度和大表一样。
2、大大交,大表和大表之间关联也是一个重要的课题,当然其中要有一个主表,在逻辑上,应当是主表Left Join辅表。大表之间的关联存在最大的问题就是性能和稳定性,对于海量数据来说,必须有优化的方法来处理他们的关联,另外,对于大数据的处理无疑会占用太多的系统资源,出错的几率非常大,如何做到有效错误恢复也是个问题。对于这种情况,我们建议还是尽量将大表拆分成适度的稍小一点的表,形成大小交的类型。这类情况的输出数据粒度和主表一样。
3、站着进来,躺着出去。事务系统中为了提高系统灵活性和扩展性,很多信息放在代码表中维护,所以它的“事实表”就是一种窄表,而在数据仓库中,通常要进行宽化,从行变成列,所以称这种处理情况叫做“站着进来,躺着出去”。大家对Decode肯定不陌生,这是进行宽表化常见的手段之一。窄表变宽表的过程主要体现在对窄表中那个代码字段的操作。这种情况,窄表是输入,宽表是输出,宽表的粒度必定要比窄表粗一些,就粗在那个代码字段上。
4、聚集。数据仓库中重要的任务就是沉淀数据,聚集是必不可少的操作,它是粗化数据粒度的过程。聚集本身其实很简单,就是类似SQL中Group by的操作,选取特定字段(维度),对度量字段再使用某种聚集函数。但是对于大数据量情况下,聚集算法的优化仍是探究的一个课题。例如是直接使用SQL的Group by,还是先排序,在处理。
二、微观规则
从数据的转换的微观细节分,可以分成下面的几个基本类型,当然还有一些复杂的组合情况,例如先运算,在参照转换的规则,这种基于基本类型组合的情况就不在此列了。ETL的规则是依赖目标数据的,目标数据有多少字段,就有多少条规则。
1、直接映射,原来是什么就是什么,原封不动照搬过来,对这样的规则,如果数据源字段和目标字段长度或精度不符,需要特别注意看是否真的可以直接映射还是需要做一些简单运算。
2、字段运算,数据源的一个或多个字段进行数学运算得到的目标字段,这种规则一般对数值型字段而言。
3、参照转换,在转换中通常要用数据源的一个或多个字段作为Key,去一个关联数组中去搜索特定值,而且应该只能得到唯一值。这个关联数组使用Hash算法实现是比较合适也是最常见的,在整个ETL开始之前,它就装入内存,对性能提高的帮助非常大。
4、字符串处理,从数据源某个字符串字段中经常可以获取特定信息,例如身份证号。而且,经常会有数值型值以字符串形式体现。对字符串的操作通常有类型转换、字符串截取等。但是由于字符类型字段的随意性也造成了脏数据的隐患,所以在处理这种规则的时候,一定要加上异常处理。
5、空值判断,对于空值的处理是数据仓库中一个常见问题,是将它作为脏数据还是作为特定一种维成员?这恐怕还要看应用的情况,也是需要进一步探求的。但是无论怎样,对于可能有NULL值的字段,不要采用“直接映射”的规则类型,必须对空值进行判断,目前我们的建议是将它转换成特定的值。
6、日期转换,在数据仓库中日期值一般都会有特定的,不同于日期类型值的表示方法,例如使用8位整型20040801表示日期。而在数据源中,这种字段基本都是日期类型的,所以对于这样的规则,需要一些共通函数来处理将日期转换为8位日期值、6位月份值等。
7、日期运算,基于日期,我们通常会计算日差、月差、时长等。一般数据库提供的日期运算函数都是基于日期型的,而在数据仓库中采用特定类型来表示日期的话,必须有一套自己的日期运算函数集。
8、聚集运算,对于事实表中的度量字段,他们通常是通过数据源一个或多个字段运用聚集函数得来的,这些聚集函数为SQL标准中,包括sum,count,avg,min,max。
9、既定取值,这种规则和以上各种类型规则的差别就在于它不依赖于数据源字段,对目标字段取一个固定的或是依赖系统的值。
探求ETL本质之四(数据质量)
“不要绝对的数据准确,但要知道为什么不准确。”
这是我们在构建BI系统是对数据准确性的要求。确实,对绝对的数据准确谁也没有把握,不仅是系统集成商,包括客户也是无法确定。准确的东西需要一个标准,但首先要保证这个标准是准确的,至少现在还没有这样一个标准。客户会提出一个相对标准,例如将你的OLAP数据结果和报表结果对比。虽然这是一种不太公平的比较,你也只好认了吧。
首先在数据源那里,已经很难保证数据质量了,这一点也是事实。在这一层有哪些可能原因导致数据质量问题?可以分为下面几类:
1、数据格式错误,例如缺失数据、数据值超出范围或是数据格式非法等。要知道对于同样处理大数据量的数据源系统,他们通常会舍弃一些数据库自身的检查机制,例如字段约束等。他们尽可能将数据检查在入库前保证,但是这一点是很难确保的。这类情况诸如身份证号码、手机号、非日期类型的日期字段等。
2、数据一致性,同样,数据源系统为了性能的考虑,会在一定程度上舍弃外键约束,这通常会导致数据不一致。例如在帐务表中会出现一个用户表中没有的用户ID,在例如有些代码在代码表中找不到等。
3、业务逻辑的合理性,这一点很难说对与错。通常,数据源系统的设计并不是非常严谨,例如让用户开户日期晚于用户销户日期都是有可能发生的,一个用户表中存在多个用户ID也是有可能发生的。对这种情况,有什么办法吗?
构建一个BI系统,要做到完全理解数据源系统根本就是不可能的。特别是数据源系统在交付后,有更多维护人员的即兴发挥,那更是要花大量的时间去寻找原因。以前曾经争辩过设计人员对规则描述的问题,有人提出要在ETL开始之前务必将所有的规则弄得一清二楚。我并不同意这样的意见,倒是认为在ETL过程要有处理这些质量有问题数据的保证。一定要正面这些脏数据,是丢弃还是处理,无法逃避。如果没有质量保证,那么在这个过程中,错误会逐渐放大,抛开数据源质量问题,我们再来看看ETL过程中哪些因素对数据准确性产生重大影响。
1、规则描述错误。上面提到对设计人员对数据源系统理解的不充分,导致规则理解错误,这是一方面。另一方面,是规则的描述,如果无二义性地描述规则也是要探求的一个课题。规则是依附于目标字段的,在探求之三中,提到规则的分类。但是规则总不能总是用文字描述,必须有严格的数学表达方式。我甚至想过,如果设计人员能够使用某种规则语言来描述,那么我们的ETL单元就可以自动生成、同步,省去很多手工操作了。
2、ETL开发错误。即时规则很明确,ETL开发的过程中也会发生一些错误,例如逻辑错误、书写错误等。例如对于一个分段值,开区间闭区间是需要指定的,但是常常开发人员没注意,一个大于等于号写成大于号就导致数据错误。
3、人为处理错误。在整体ETL流程没有完成之前,为了图省事,通常会手工运行ETL过程,这其中一个重大的问题就是你不会按照正常流程去运行了,而是按照自己的理解去运行,发生的错误可能是误删了数据、重复装载数据等。
探求ETL本质之五(质量保证)
上回提到ETL数据质量问题,这是无法根治的,只能采取特定的手段去尽量避免,而且必须要定义出度量方法来衡量数据的质量是好还是坏。对于数据源的质量,客户对此应该更加关心,如果在这个源头不能保证比较干净的数据,那么后面的分析功能的可信度也都成问题。数据源系统也在不断进化过程中,客户的操作也在逐渐规范中,BI系统也同样如此。本文探讨一下对数据源质量和ETL处理质量的应对方法。
如何应对数据源的质量问题?记得在onteldatastage列表中也讨论过一个话题-"-1的处理",在数据仓库模型维表中,通常有一条-1记录,表示“未知”,这个未知含义可广了,任何可能出错的数据,NULL数据甚至是规则没有涵盖到的数据,都转成-1。这是一种处理脏数据的方法,但这也是一种掩盖事实的方法。就好像写一个函数FileOpen(filename),返回一个错误码,当然,你可以只返回一种错误码,如-1,但这是一种不好的设计,对于调用者来说,他需要依据这个错误码进行某些判断,例如是文件不存在,还是读取权限不够,都有相应的处理逻辑。数据仓库中也是一样,所以,建议将不同的数据质量类型处理结果分别转换成不同的值,譬如,在转换后,-1表示参照不上,-2表示NULL数据等。不过这仅仅对付了上回提到的第一类错误,数据格式错误。对于数据一致性和业务逻辑合理性问题,这仍有待探求。但这里有一个原则就是“必须在数据仓库中反应数据源的质量”。
对于ETL过程中产生的质量问题,必须有保障手段。从以往的经验看,没有保障手段给实施人员带来麻烦重重。实施人员对于反复装载数据一定不会陌生,甚至是最后数据留到最后的Cube,才发现了第一步ETL其实已经错了。这个保障手段就是数据验证机制,当然,它的目的是能够在ETL过程中监控数据质量,产生报警。这个模块要将实施人员当作是最终用户,可以说他们是数据验证机制的直接收益者。
首先,必须有一个对质量的度量方法,什么是高质什么是低质,不能靠感官感觉,但这却是在没有度量方法条件下通常的做法。那经营分析系统来说,联通总部曾提出测试规范,这其实就是一种度量方法,例如指标的误差范围不能高于5%等,对系统本身来说其实必须要有这样的度量方法,先不要说这个度量方法是否科学。对于ETL数据处理质量,他的度量方法应该比联通总部测试规范定义的方法更要严格,因为他更多将BI系统看作一个黑盒子,从数据源到展现的数据误差允许一定的误差。而ETL数据处理质量度量是一种白盒的度量,要注重每一步过程。因此理论上,要求输入输出的指标应该完全一致。但是我们必须正面完全一致只是理想,对于有误差的数据,必须找到原因。
在质量度量方法的前提下,就可以建立一个数据验证框架。此框架依据总量、分量数据稽核方法,该方法在高的《数据仓库中的数据稽核技术》一文中已经指出。作为补充,下面提出几点功能上的建议:
1、提供前端。将开发实施人员当作用户,同样也要为之提供友好的用户界面。《稽核技术》一文中指出测试报告的形式,这种形式还是要依赖人为判断,在一堆数据中去找规律。到不如用OLAP的方式提供界面,不光是加上测试统计出来的指标结果,并且配合度量方法的计算。例如误差率,对于误差率为大于0的指标,就要好好查一下原因了。
2、提供框架。数据验证不是一次性工作,而是每次ETL过程中都必须做的。因此,必须有一个框架,自动化验证过程,并提供扩展手段,让实施人员能够增加验证范围。有了这样一个框架,其实它起到规范化操作的作用,开发实施人员可以将主要精力放在验证脚本的编写上,而不必过多关注验证如何融合到流程中,如何展现等工作。为此,要设计一套表,类似于DM表,每次验证结果数据都记录其中,并且自动触发多维分析的数据装载、发布等。这样,实施人员可以在每次装载,甚至在流程过程中就可以观察数据的误差率。特别是,如果数据仓库的模型能够统一起来,甚至数据验证脚本都可以确定下来,剩下的就是规范流程了。
3、规范流程。上回提到有一种ETL数据质量问题是由于人工处理导致的,其中最主要原因还是流程不规范。开发实施人员运行单独一个ETL单元是很方便的,虽然以前曾建议一个ETL单元必须是“可重入”的,这能够解决误删数据,重复装载数据问题。但要记住数据验证也是在流程当中,要让数据验证能够日常运作,就不要让实施者感觉到他的存在。总的来说,规范流程是提高实施效率的关键工作,这也是以后要继续探求的。
探求ETL本质之六(元数据漫谈)
对于元数据(Metadata)的定义到目前为止没有什么特别精彩的,这个概念非常广,一般都是这样定义,“元数据是描述数据的数据(Data about Data)”,这造成一种递归定义,就像问小强住在哪里,答,在旺财隔壁。按照这样的定义,元数据所描述的数据是什么呢?还是元数据。这样就可能有元元元...元数据。我还听说过一种对元数据,如果说数据是一抽屉档案,那么元数据就是分类标签。那它和索引有什么区别?
元数据体现是一种抽象,哲学家从古至今都在抽象这个世界,力图找到世界的本质。抽象不是一层关系,它是一种逐步由具体到一般的过程。例如我->男人->人->哺乳动物->生物这就是一个抽象过程,你要是在软件业混会发现这个例子很常见,面向对象方法就是这样一种抽象过程。它对世界中的事物、过程进行抽象,使用面向对象方法,构建一套对象模型。同样在面向对象方法中,类是对象的抽象,接口又是对类的抽象。因此,我认为可以将“元”和“抽象”换一下,叫抽象数据是不是好理解一些。
常听到这样的话,“xx领导的讲话高屋建瓴,给我们后面的工作指引的清晰的方向”,这个成语“高屋建瓴”,站在10楼往下到水,居高临下,能砸死人,这是指站在一定的高度看待事物,这个一定的高度就是指他有够“元”。在设计模式中,强调要对接口编程,就是说你不要处理这类对象和那类对象的交互,而要处理这个接口和那个接口的交互,先别管他们内部是怎么干的。
元数据存在的意义也在于此,虽然上面说了一通都撤到哲学上去,但这个词必须还是要结合软件设计中看,我不知道在别的领域是不是存在Metadata这样的叫法,虽然我相信别的领域必然有类似的东东。元数据的存在就是要做到在更高抽象一层设计软件。这肯定有好处,什么灵活性啊,扩展性啊,可维护性啊,都能得到提高,而且架构清晰,只是弯弯太多,要是从下往上看,太复杂了。很早以前,我曾看过backorifice的代码,我靠,一个简单的功能,从这个类转到父类,又转到父类,很不理解,为什么一个简单的功能不在一个类的方法中实现就拉到了呢?现在想想,还真不能这样,这虽然使代码容易看懂了,但是结构确实混乱的,那他只能干现在的事,如果有什么功能扩展,这些代码就废了。
我从98年刚工作时就开始接触元数据的概念,当时叫做元数据驱动的系统架构,后来在QiDSS中也用到这个概念构建QiNavigator,但是现在觉得元数据也没啥,不就是建一堆表描述界面的元素,再利用这些数据自动生成界面吗。到了数据仓库系统中,这个概念更强了,是数据仓库中一个重要的部分。但是至今,我还是认为这个概念过于玄乎,看不到实际的东西,市面上有一些元数据管理的东西,但是从应用情况就得知,用的不多。之所以玄乎,就是因为抽象层次没有分清楚,关键就是对于元数据的分类(这种分类就是一种抽象过程)和元数据的使用。你可以将元数据抽象成0和1,但是那样对你的业务有用吗?必须还得抽象到适合的程度,最后问题还是“度”。
数据仓库系统的元数据作用如何?还不就是使系统自动运转,易于管理吗?要做到这一步,可没必要将系统抽象到太极、两仪、八卦之类的,业界也曾定义过一些元数据规范,向CWM、XMI等等,可以借鉴,不过俺对此也是不精通的说,以后再说
程序员,是最聪明的一个群体。以前是这样,现在我也同样这么认为。可是,作为最聪明的我们,你又对自己能把握多少呢?
程序员是最不善于言语的。任何辛苦,程序员都藏在了心里。市场人员的辛苦,天天在讲。就连你都,天天听得都感觉他们很辛苦。可是却没有人来说你。你可能会在位置上发发牢骚,可能会暗地里伤神流泪,也可能在自己的博客上和网友分享。可唯独不会向领导反映。可惜,领导向来是谁辛苦给谁加薪水。所以,你最好运气好,遇到一位会体察下情的领导。否则,我只能问你,你能掌握你自己的命运吗?
程序员又是最揉不得沙子的。对于任何开发方面的问题,他们都有自己的独到意见。任何不同的意见,都会让他竭尽全力地去找出其中的漏洞。他们的前辈都是从这条路上,踏过无数人的口水,成长起来的。当你看到的沙子在你的领导身上的时候,你会怎么办呢?是放弃程序员的尊严去漠不关心,还是放下一切,去奋力争取?所以,你最好运气好,遇到会告诉你如何沟通的领导。否则,我只能问你,你能掌握你自己的命运吗?
程序员也是最不知道计划的。经常的加班,并不能说明你对计划掌握很好。反而是说明你被公司剥削地更厉害。你最喜欢的是铺开做所有事,可往往所有事都不能按时提交。刚开始的时候,任务少还好说。当能力提升,任务繁多的时候,这种情况越来越明显。于是,你继续着程序员最经常的行为:加班。加班让你不需要关心晚餐的来源,加班让你不需要关心回家的路费,加班让你不需要寻找男女朋友。当你被加班困的焦头烂额的时候,你会怎么办呢?是继续顶下去,还是放弃跳槽?所以,你最好运气好,遇到一个能帮你统筹时间的领导。否则,我只能问你,你能掌握你自己的命运吗?
程序员是最最乐观的。你要让他估计时间。他往往都是告诉你比最好情况都要好的结果。问他这个系统有多难,哈哈,每一点都很简单。是的,也许每一点确实很简单,可是程序员却总是忘记那些繁杂的关系。你要是领导,会一次一次地失望,一次一次地给他压力。可是,你认为他这样的后果会是什么呢?所以,你只能为他祈祷,能遇到一位会帮助学会如何估算的领导。否则,你只能问他,能把握自己的命运吗?
不管如何,程序员总还是乐观地、不自觉地看着别人身上的沙子。可是除了等待遇到一位好领导,我们还能做什么呢?与其让别人教我们,还不如我们自己去学习。程序员啊,什么时候你能够掌握你自己?
很多人都在叹息自己队伍中高手很少。但是,如果队中真的有一高手,你以为会一帆风顺吗?
凡高手者,往往必然是强势者。他必有其不容质疑的能力体现。一旦这个能力遭到怀疑时,你可能会看到意想不到的破坏力。
凡高手者,往往也是好为人师者。他必然要体现他的影响力,不管有意无意。一旦有不如他意的地方,他可以完全推倒。然后告诉你他会怎么做。当然了。他的做法必然又很多非常正确的地方。这点也无庸置疑。
凡高手者,往往是好争论者。你的意见到他那里,往往会遇到来自各个方面的攻击。他会告诉你什么地方可能有什么问题。也会告诉你他有更好的方法。总之,争论不可避免。
凡高手者,往往又是执着者。也许有时候你会认为你已经完全占有上风。但是他可能会告诉你,“你不管怎么说,我也坚持我的。”不是靠讲道理,就能让他就范的。
凡高手者,往往是孤独者。你往往只看到他风光的一面,却难以体会他的无奈。自己的很多想法,无法与人切磋。说高处不胜寒一点不为过。
凡高手者,往往是最忙的人。除了本组的事务,很可能牵涉到其他各部门的事情。有时候还有很多求教。真正静下心来做该做的,嘿嘿,难啊!
所以,不管幸运还是不幸运,你和一位高手在一起,你可以有十二分的准备。
假如你是一位新手,那你要随时准备接受高手的提问、质疑。他随时可能抓你去,告诉你的问题所在。所以到那个时候,你只要记住一句话:嗯,我就该!
当然了,也可能问你问题,千万不要紧张。高手给你的往往不是压力,更多的是你对你的勉励。因为关注才会指导你。所以,就算错了,也不要紧。
关键在于,错了之后,要将错误做一个非常完整的分析,再对比一下正确的方案。然后统一汇报给高手。不为弥补你原来的失误,而是为了赢取他对你的继续关注。
假如你自己感觉状态很好,嘿嘿,那你要准备好随时进行战斗。因为你的每一次发言都是对高手的挑衅。他会非常容易地进入战斗状态。这个时候,你只要记住一点,不管争论结果如何,你都是赢家。
最最忌讳的就是,将证明自己的观点作为你战斗的目标。因为,在高手看来,他往往能清楚的认识到,自己有可能存在的错误。而他追求的正是追求真理的快感,一旦你的立场错了,你会发现你越是面红耳赤,他却越是兴高采烈。
假如你也自认为高手。别的什么也不说,送你一句话:英雄惺惺相惜!
假如非常不幸,你不是一位高手,就像我。又有一位高手的手下。嘿嘿。我只能告诉你,你很走运。至少,你不是一个人在面对这个问题。
首先要设法满足高手的种种欲望。高手的求知欲,高手的表现欲,高手的自尊欲。尊敬你的高手,是你必须做的第一步。
帮助你的高手,安排他的时间。避免不必要的干扰。
不要随意将你的压力反反复复地传达给你的高手。高手很容易理解压力。不过他很可能会遇到很多自己无法处理的问题。这个时候,你要做的不是谈压力,而是给他解除干扰。
你很可能在某方面做的不好,在你意识到的第一时间和你的高手坦白。他会坚决支持你的。
让高手成为高手。一定要完全体现高手的地位和尊严。否则,干脆就不要高手。这位标杆一定要树好。他好了,你的一帮子人都会好起来。
愿你能像我一样,有一位高手在身旁!那是一件很幸福的事情。
五、 数据接口的实现(见Org.jpg文件)
Org.JSP文件用来在
服务器上运行
Java的类与前台web页之间架起一座桥。取到
中间件的接口作用。
这里分析部分代码:
<%@ page contentType="text/html; charset=GBK" %> <%@ page import="java.sql.*" %> <%@ page import="javax.naming.*" %> <%@ page import="javax.sql.*" %> <%@ page import="tool.*" %> <%@ page import="orgNew.*" %> <%@ page import="org.w3c.dom.*" %> //上面主要是引用一些java类 <% try{ //request.setCharacterEncoding("GBK"); Document doc = XmlTool.createDocumentFromRequest(request); //建立web面文档请求的文档对象 Connection conn = ConnTool.getConnectionFromPool(); //获取请求的方法名 String mode=request.getParameter("mode"); //out.println("ccc"); //如果方法中没有其它参数则读取组织树数据 if(mode == null){ /* int OrgId = Integer.parseInt(request.getParameter("id")); String str = orgManager.getChildOrg(OrgId, conn); out.println(str); */ String str = orgManager.getTree(conn); //out.println(str); out.println(str); }else if(mode.equals("createOrg")){ //如果是createOrg方法则建立一个组织 int parentOrgId = Integer.parseInt(request.getParameter("parentOrgId")); //取出传递来的第一个参数parentOrgId int OrgId = orgManager.createOrg(parentOrgId, conn); //调用orgManager 类的createOrg方法来建立一个组织 out.println(OrgId); //返回结果 } conn.close(); } catch(Exception e){ e.printStackTrace(); } %> |
六、 后台数据的实现 1.
数据结构的定义
这里,我们主要有三个表。一个是组织结构表,一个是人员表person,一个组织人员关联表orgPerson。组织结构表有OrgCode(组织代码)、OrgName(组织名称)、orgId(组织Id), parentOrgId(父Id)。人员表有personCode(人员代码)、personName(人员名称), sex(性别)、personId(人员Id)。orgPerson表有orgId, personId。
2.
数据库的连接
WEB应用程序常用
MySQL作后台数据库,这是因为MySQL简单、高效。这里我们也用MySQL作为数据库。Java中用jdbc连接数据库。下面是连接数据库的CODE:
public static Connection getConnectionFromPool() throws Exception { Context ctx = new InitialContext(); DataSource ds = (DataSource) ctx.lookup("java:/erpds"); return ds.getConnection(); }
/** * 取数据库链接对象 * @return Connection 数据库链接对象 * @throws Exception */ /* public static Connection getDirectConnection() throws Exception { Class.forName("com.sybase.jdbc2.jdbc.SybDriver"); String url = "jdbc:sybase:Tds:19.64.13.16:4100/wydb?charset=iso_1"; String user = "sa"; String password = "2860008"; Connection conn = DriverManager.getConnection(url, user, password); return conn; } */ |
3. 业务逻辑层的实现
后台开发我们用Java类来实现。这里我们开发了一个orgNew包,类名为orgManager。此类封装了与数据库操作有关的方法。通过main可调试程序的正确性。
这里给出了新增加一个组织的全部代码和通过XML取得树结构信息的代码,树结构通过递归实现。
package orgNew;// Java类所打的包 import tool.*; import java.sql.*; import java.util.*; // 引用Java类的 public class orgManager { public orgManager() { } public static void main(String[] args) throws Exception { Connection conn = tool.ConnTool.getDirectConnection();// 引用数据访问类 conn.setAutoCommit(false);
orgManager orgManager1 = new orgManager(); orgManager1.createOrg(0, conn); //测试建立组织是否正确 } //建立一个组织 public static int createOrg(int parentOrgId, Connection conn) throws Exception { String sql = "insert into Org (OrgName, parentOrgId) values('新组织', ?)"; PreparedStatement pstat = conn.prepareStatement(sql); pstat.setInt(1, parentOrgId); pstat.executeUpdate(); pstat.close();
Statement stat = conn.createStatement(); String sql2 = "select max(OrgId) from Org"; ResultSet rs = stat.executeQuery(sql2); rs.next(); int OrgId = rs.getInt(1); rs.close(); stat.close(); System.out.println(OrgId); return OrgId; } } //通过递归得到组织信息的XML格式的数据 public static String getTree(Connection conn) throws Exception { StringBuffer ret = new StringBuffer();//定义可缓冲的字符流 ret.append("<?xml version='1.0' encoding='gb2312'?><tree id='0'>");//定义XML格式的头信息 ret.append(" <item child='1' text='组织' id='1' >");//插入结点体。注树结点以item为标记 ret.append(getChildTree(1, conn)); ret.append("</item>");//结点体结束标记 ret.append("</tree>");//树结束标记 return ret.toString();//返回字符流 }
public static String getChildTree(int OrgId, Connection conn) throws Exception { StringBuffer ret = new StringBuffer(); String sql = "select a.OrgId, a.OrgName, a.OrgCode,count(b.parentOrgId) from Org a " + "left join Org b on a.OrgId = b.parentOrgId " + "where a.parentOrgId = ? " + "group by a.OrgId, a.OrgName"; PreparedStatement pstat = conn.prepareStatement(sql); pstat.setInt(1, OrgId); ResultSet rs = pstat.executeQuery(); while (rs.next()) { int childOrgId = rs.getInt(1); String childOrgName = rs.getString(2); String childOrgCode = rs.getString(3); if (childOrgCode == null) { childOrgCode = " "; } if (childOrgName == null) { childOrgName = "新组织"; } int childCount = rs.getInt(3); if (childCount > 0) { childCount = 1; } ret.append("<item child='" + childCount + "' text='" + childOrgName + "' id='" +childOrgId + "' code='"+childOrgCode+"'>"); ret.append(getChildTree(childOrgId, conn)); ret.append("</item>"); } rs.close(); pstat.close(); return ret.toString(); } |
其它代码见orgManager.java文件。
七、 总结 本文件通过一个实例全面介绍了
Ajax开发的各个细节。通过与
J2ee的结合来实现三层分布式开发的层次划分,后台与前端的调用。数据的读取、访问及展现。
通过这个实例,我们可见,Ajax使WEB中的界面与应用分离。数据与呈现分离的分离,有利于分工合作、减少非技术人员对页面的修改造成的WEB应用程序错误、提高效率、也更加适用于现在的发布系统。也可以把以前的一些服务器负担的工作转嫁到客户端,利于客户端闲置的处理能力来处理。
Ajax是传统WEB应用程序的一个转变。以前是服务器每次生成HTML页面并返回给客户端(浏览器)。Ajax理念的出现,揭开了无刷新更新页面时代的序幕,并有代替传统web开发中采用form(表单)递交方式更新web页面的趋势,可以算是一个里程碑。
四、
XML
与XSL文件设计
XML是种可扩展的标记语言,它具有开放的、可扩展的、可自描述的语言结构,它已经成为网上数据和文档传输的标准。XSLT的目的是将信息内容与 Web 显示分离,HTML 通过按抽象概念(如段落、重点和编号列表)定义显示来实现设备独立性。XSLT用来具体显示控件,设置控件风格。
Ajax主要使用XML和XSLT进行数据交换与处理。
1. 树信息的XML文件(见root.xml文件)
XML是标记语言,元素必须成对出现。树结构中以tree为根结点,以item为结点体,属性text指出结点所显示的文本,id指出唯一的所标识号。
<?xml version='1.0' encoding='gb2312'?> <tree id="0"> <item child="1" text="组织" id="1" > </item> </tree> |
这文件并不是必要的,只是为了系统能独立运行才加的。事实如果连接了后台数据是不需要的。只要吧OrgTree.loadXML("root.xml?0")改为OrgTree.loadXML("Org.jsp")就可以了。
2. 人员信息XML文件(见peorson.xml文件)
说明![CDATA[]]可在任何显示任何格式的文本,文本中可插入其它任何字符。这文件也不是必要的。
3. 人员信息展现的xsl文件(见addOrgPerson.xsl文件)
xsl文件同样是XML格式文件。所以一律遵守XML标准。下面对主要的行讲解:
<?xml version="1.0" encoding="gb2312"?> //这是定义xml文件的首行。用来指明版本及字符集 <xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl" language="JavaScript"> //这里定义了stylesheet 元素。并指出其国际命名的组织及语言。 <xsl:template match="/"> <xsl:apply-templates select="peorsones"/> </xsl:template> //上面是匹配的规则。"/"表示从根结开始去匹配。匹配到下面的peorsones标记。这是正则表达式有关的学问。我们只要理解就可以。 <xsl:template match="peorsones"> //当匹配上peorsones时所要做的事情。 <table id="tbList" border="1" width="100%"> //定义一个id为"tbList的表格。此表格是显示在WEB上的 <xsl:for-each select="peorsone"> //循环匹配peorsone <tr> //定义tbList表格的一行,并在行上增加一个叫seqNo的属性名,值为匹配到的seqNo(序号) <xsl:attribute name="seqNo"><xsl:value-of select="@seqNo"/></xsl:attribute> <td> //定义行上的一列,列又去匹配 <xsl:apply-templates select="."/> </td> </tr> </xsl:for-each> </table> </xsl:template>
<xsl:template match="peorsone"> <table border="1" width="100%"> <tr> //定义宽为5%的一列,在该列上插入一个checkbox控件 <td width="5%"> <input type="checkbox" value="on" size="10"></input> </td> //定义一个不显示的列,在该列上插入一个text控件,text的值为匹配到的personId(人员Id) <td style="display:none"> <input type="text" size="25"> <xsl:attribute name="value"><xsl:value-of select="personId"/></xsl:attribute> </input> </td> <td width="30%"> <input type="text" size="20"> <xsl:attribute name="value"><xsl:value-of select="personCode"/></xsl:attribute> </input> </td> <td width="40%"> <input type="text" size="40"> <xsl:attributename="value"><xsl:value-of select="personName"/></xsl:attribute> </input> </td> //定义一个width为28%的列,在该列上插入一个下拉列表select 控件,select的值如果匹配到为0时则为"男",1时则为"女" <td width="28%"> <select size="1"> <option value="0"> <xsl:if test=".[sex=0]"> <xsl:attribute name="selected">true</xsl:attribute> </xsl:if> 男 </option> <option value="1"> <xsl:if test=".[sex=1]"> <xsl:attribute name="selected">true</xsl:attribute> </xsl:if> 女</option> </select> </td> //定义一列,在该列上插入一个button控件,onclick 事件为自定义的方法,该方法传递当前单击的按纽 <td width="*"> <button onclick="openPersonRolePage(this)" style="width: 36; height: 21">角色</button> </td> </tr> </table> </xsl:template> </xsl:stylesheet> |
3. 人员管理的实现
人员可以增加、删除、编辑。同时当选择树结点时应该把人员显示出来供编辑、查看......
1) 增加人员
人员增加实现的原理是在personDom中加入结点peorsone,该结点相当于表的一行,设置属性。同时在peorsone中不继地加入其它结点,代表
数据库的字段,且必须与XLT文件的标号同名。这些结点相当该行的列。最后在表中插入一行,行上插入一列,并显示之。
function addPerson(){ var seqNo = nextSeq; nextSeq++; var peorsonNode = personDom.createNode("1", "peorsone",""); peorsonNode.setAttribute("isNew", "Y"); peorsonNode.setAttribute("isDelete", "N"); peorsonNode.setAttribute("seqNo", seqNo); personDom.documentElement.appendChild(peorsonNode); var PersonId= personDom.createNode("1", "personId", ""); peorsonNode.appendChild(PersonId); var personCode= personDom.createNode("1", "personCode", ""); peorsonNode.appendChild(personCode); var PersonName= personDom.createNode("1", "personName", ""); peorsonNode.appendChild(PersonName); var Sex= personDom.createNode("1", "sex", ""); peorsonNode.appendChild(Sex); var tr = tbList.insertRow(tbList.rows.length); tr.setAttribute("seqNo", seqNo); var td = tr.insertCell(0); td.innerHTML = peorsonNode.transformNode(stylesheet); } |
2) 删除人员
人员删除同样是调用Org.jsp 文件中的deletePerson方法来实现,该方法传递所删除的人员ID。如何确定人员ID是通过读取隐藏的ID,并扫描整个表,看那些被选中。这里我们要注意是提供多项选择的。
function deletePerson(){ for(var i=0; i<tbList.rows.length; i++){ var row=tbList.rows[i].cells[0].children[0].rows[0]; if(row.cells[0].children[0].checked) { var personId=row.cells[1].children[0].value; if(personId>0) { var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); xmlhttp.open("POST","Org.jsp?mode=deletePerson&personId=" + personId, false); xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xmlhttp.send(); } tbList.deleteRow(i); i--; } } } |
3) 编辑人员
人员修改我们要判定哪些行被修改了。刚增加但没保存的行应该是新增而不是修改的。
function save(){ if( modifyOrg()=="N") { return; } for(var i=0; i<tbList.rows.length; i++) { var row=tbList.rows[i].cells[0].children[0].rows[0]; var personId=row.cells[1].children[0].value; var seqNo = tbList.rows[i].getAttribute("seqNo"); var staffNode = personDom.selectSingleNode("//peorsone[@seqNo='" + seqNo + "']"); var personCode=row.cells[2].children[0].value; var personName=row.cells[3].children[0].value; var sex=row.cells[4].children[0].value; //alert(staffN;ode ); if(staffNode.getAttribute("isNew") == "Y") { createPerson(CurrNodeId,personCode,personName,sex); } else { var strXML = "<?xml version='1.0' encoding='gb2312'?>" + "<data>" + "<personCode><![CDATA[" + personCode+ "]]></personCode>" + "<personName><![CDATA[" + personName + "]]></personName>" + "<sex><![CDATA[" + sex+ "]]></sex>" + "<personId><![CDATA[" + personId+ "]]></personId>" + "</data>"; //alert(strXML ); var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); xmlhttp.open("POST","Org.jsp?mode=modifyPerson", false); xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xmlhttp.send(strXML ); } } } |
三、 前端页面的主要编码
1. 树的实现
在WEB上实现树结构,同样我们是通过
Ajax来实现的。树上可以显示自定义的图标,可以插入、删除、结点。并且结点可任意移动。这里我们不重点讲树的实现技术,我们已经封装好了,你只要按要求去改动就是了。
1) 键接树型文件
在<head>与</head>之间键接我们的与树有关的文件, 代码如下:
<link rel="STYLESHEET" type="text/css" href="css/dhtmlXTree.css"> <script src="js/dhtmlXCommon.js"></script> <script src="js/dhtmlXTree.js"></script> |
2) 装载方法
在页面的文档打开时装载自定义方法, preLoadImages方法实现树控件的图标定义,doOnLoad实现树控件的图标定义代码如下:
<body onload="preLoadImages();doOnLoad();"> |
3) 编写方法
//doOnLoad实现装载并显示树。设置树属性等。 function doOnLoad(){ OrgTree=new dhtmlXTreeObject(document.getElementById('divTree'),"100%","100%",0); //dhtmlXTreeObject是树对象,通过新建对象,指定树显示的DIV可定义树。 OrgTree.setImagePath("imgs/");//设置树的图片所在位置 OrgTree.setDragHandler();//设置树结点拖动 OrgTree.enableDragAndDrop(true) //设置树结点是否可拖动 OrgTree.setDragHandler(myDragHandler); //设置树结点拖动时所执行的方法 OrgTree.setOnClickHandler(mySelectHandler); //设置树单击时所执行的方法 //OrgTree.setXMLAutoLoading("Org.jsp");//装载树结点数据。数据来源如Org.jsp所返回的XML格式的字符串,数据是动态装载,且当展开时才装载。 OrgTree.loadXML("root.xml?0");//装载树结点数据。数据来源root.xml文件,并且从xml文件的ID号为0处读取数据。 //OrgTree.loadXML("Org.jsp");//装载树结点数据。数据来源如Org.jsp所返回的XML格式的字符串,并且是一次性全部装载数据。 } //preLoadImages方法实现树控件的图标定义 function preLoadImages(){ var imSrcAr = new Array("line1.gif","line2.gif","line3.gif","line4.gif","minus2.gif","minus3.gif", "minus4.gif","plus2.gif","plus3.gif","plus4.gif","book.gif","books_open.gif","books_close.gif", "magazine_open.gif","magazine_close.gif","tombs.gif","tombs_mag.gif","book_titel.gif") var imAr = new Array(0); for(var i=0;i<imSrcAr.length;i++){ imAr[imAr.length] = new Image(); imAr[imAr.length-1].src = "imgs/"+imSrcAr[i] } } |
2. 组织管理的实现
组织可以增加、删除、编辑。同时当选择树结点时应该把组织显示出来供编辑,查看。为了实现这些功能,你只要按要求去改动就是了。
1) 全局变量的定义
许多地方我们要用到一些公共变量,我们在<script>与</script>之间定义全局变量, 代码如下:
var OrgTree = null; //组织树Dom var nextSeq = 0;//人员管理的顺序号(流水号) var personDom;//人员Dom var CurrNodeId;//当前结点Id |
2) 初始化
当页面打开时我们要控件好那部分该显示,那部分要隐藏。且对全局变量的赋值等,组织类型装载。在页面的文档打开时装载自定义方法init(), init方法实现初始化。
init方法实现如下:
function init(){ //定义personDom为一个XMLDOM'对象 personDom= new ActiveXObject('Microsoft.XMLDOM'); personDom.async = false; //定义stylesheet为一个XMLDOM'对象,且stylesheet为personDom确定显示风格 stylesheet = new ActiveXObject('Microsoft.XMLDOM'); stylesheet.async = false; stylesheet.load("addOrgPerson.xsl"); //装载stylesheet的风格定义文件
//装载组织类型数据 var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); xmlhttp.open("POST","Org.jsp?mode=GetOrgType", false); xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xmlhttp.send(); retXml=xmlhttp.responseText; // alert(retXml); //把组织类型插入下拉列表控件中 var OrgDoc = new ActiveXObject('Microsoft.XMLDOM'); OrgDoc.async = false; OrgDoc.loadXML(retXml); var root = OrgDoc.documentElement; oNodeList = root.childNodes; txtType.options.length =oNodeList.length; for (var i=0; i<oNodeList.length; i++) { Item = oNodeList.item(i); var OrgTypeId=Item.childNodes(0).text; var OrgTypeName=Item.childNodes(1).text; txtType.options[i].value=OrgTypeId; txtType.options[i].text=OrgTypeName; // txtType.options[0]. } } |
3) 编写树拖动及选择结点的方法
// myDragHandler实现树结点拖动时重新指定父子关系。 function myDragHandler(idFrom,idTo){ var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); xmlhttp.open("POST","Org.jsp?mode=moveOrg&orgId=" + idFrom + "&newparentOrgId=" + idTo, false); xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xmlhttp.send(); retXml=xmlhttp.OrgponseText;
return true; } // mySelectHandler实现选择树结点对系统的控制,同时显示组织信息及该组织下的人员。 function mySelectHandler(id){ tbOrg.style.display="block"; divOrgMemo.style.display="none"; divOrgInfo.style.display="none";
if(id==1) { divOrgMemo.style.display="block"; div1.style.display="none"; div2.style.display="none"; div3.style.display="none"; divContent.style.display="none"; div5.style.display="none"; } else { divOrgInfo.style.display="block"; div1.style.display="block"; div2.style.display="block"; div3.style.display="block"; divContent.style.display="block"; div5.style.display="block"; } CurrNodeId=id; //装载组织信息并显示在编码和名称的文本控件上。 loadOrg(id); //装载某组织下人员信息 var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); xmlhttp.open("POST","Org.jsp?mode=GetPerson&orgId=" + id, false); xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xmlhttp.send();
retXml=xmlhttp.responseText; personDom.loadXML (retXml); //给人员信息的每行加上序号 for(var i=0; i<personDom.documentElement.childNodes.length; i++){ personDom.documentElement.childNodes[i].setAttribute("seqNo", nextSeq); nextSeq++; } //人员信息显示在divContent上面 divContent.innerHTML = personDom.transformNode(stylesheet); }; //装载组织信息并显示在编码和名称的文本控件上。 function loadOrg(OrgId){ if(OrgId == null){ OrgId = OrgTree.getSelectedItemId(); } if(OrgId == ""){ tbOrg.style.display = "none"; return; } var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); xmlhttp.open("POST","Org.jsp?mode=loadOrg&OrgId=" + OrgId, false); xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xmlhttp.send(); retXml=xmlhttp.responseText; var OrgDoc = new ActiveXObject('Microsoft.XMLDOM'); OrgDoc.async = false; OrgDoc.loadXML(retXml); if(OrgId != 1){ txtCode.value = OrgDoc.selectSingleNode("//OrgCode").text; txtName.value = OrgDoc.selectSingleNode("//OrgName").text; } tbOrg.style.display = "block"; } |
4) 建立组织
组织建立主要是通过调用XMLHTTP对象来实现。我们主要学会如何调用XMLHTTP。组织建立应该在后台实现,把组织信息插入
数据库中。这里我们通过JSP来实现。我们的Org.jsp 文件中有个createOrg方法,该方法传递一个父ID。
function createOrg(parentOrgId){ var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); xmlhttp.open("POST","Org.jsp?mode=createOrg&parentOrgId=" + parentOrgId, false); xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xmlhttp.send(); retXml=xmlhttp.responseText; var orgId = (new Number(retXml)).toString(); return orgId; } |
5) 删除组织
组织删除同样是调用Org.jsp 文件中的deleteOrg方法来实现,该方法传递所删除的结点ID。
function deleteOrg(){ var OrgId = OrgTree.getSelectedItemId(); var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); xmlhttp.open("POST","Org.jsp?mode=deleteOrg&OrgId=" + OrgId, false); xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xmlhttp.send(); } |
6) 编辑组织
组织修改是调用Org.jsp 文件中的modifyOrg方法来实现,该方法传递所修改的结点ID。同时修改的数据通过自定义的XML格式的字符串传送,这时通过send字符串来实现。修改前数据一律要验证其合法性,并提示错误信息。
function modifyOrg(){ if(OrgTree.getSelectedItemId() == ""){ return "N"; } if(txtCode.value == ""){ alert("请输入编码!"); return "N"; } if(txtName.value == ""){ alert("请输入名称!"); return"N"; } var OrgId = OrgTree.getSelectedItemId(); var OrgKind; //alert(txtType.options[txtType.selectedIndex].value) var strModify = "<?xml version='1.0' encoding='gb2312'?>" + "<data>" + "<OrgCode><![CDATA[" + txtCode.value + "]]></OrgCode>" + "<OrgName><![CDATA[" + txtName.value + "]]></OrgName>" + "<OrgKind><![CDATA[" + txtType.options[txtType.selectedIndex].value+ "]]></OrgKind>" + "</data>"; var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); xmlhttp.open("POST","Org.jsp?mode=modifyOrg&OrgId=" + OrgId, false); xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xmlhttp.send(strModify); OrgTree.setItemText(OrgTree.getSelectedItemId(),txtName.value); } |
一、 概述
AJAX是今年初才问世的新技术,是Asynchronous JavaScript and XML的缩写。它是一组开发Web应用程序的技术,它使浏览器可以为用户提供更为自然的浏览体验。每当需要更新时,客户端Web页面的修改是异步的和逐步增加的。
这样,AJAX在提交Web页面内容时大大提高了用户界面的速度。在基于AJAX的应用程序中没有必要长时间等待整个页面的刷新。页面中需要更新的那部分才进行更改,如果可能的话,更新是在本地完成的,并且是异步的。
J2ee是一种用来开发分布式系统的体系结构。它主要是用Java类开发业务实体。通过JSP来连接应用服务器。
本文开发一个组织机构管理小系统,通过这个实例来介绍如何用Ajax开发WEB应用程序。本系统具有增加、修改、删除组织机构的功能。同时给机构分配人员,能增加、修改、删除人员。
二、 界面设计
树结构是大多软件系统中常采用的结构形式。由于树型结构层次分明、上下级关系清楚、且展开收缩表达信息方便、界面也较美观,所以是大家热衷于用此结构。组织机构管理是一般软件基本具有的。组织机构是指公司的组织结构。集团公司可包括分公子公司,公司下面又有科室。员工归属于所在的公司。系统运行后的界面如下:
orgManager.htm是组织机构管理的主页面。WEB应用程序界面设计是非常重要的。如何布局、么样组织可直接体现一个人的设计水平。
组织机构主要包括树结构、组织机构编辑、人员编辑等三大块,如何分成三块呢,然而一般树型结构的窗体常先二块,树型结构独占一块,另一块又分成上下二部分,上面是机构编码,下面是人员编码。固可以把页面划分成如下图形式:
显然我们是通过表来实现。这是一个二行二列的表,且第一、二行的左边列合并单元格。代码如下:
< TABLE border="1" width="100%" height="100%"> <TR> <TD rowspan="2"></TD> <TD></TD> </TR> <TR> <TD></TD> </TR> </TABLE> |
我们在1区(单元格)上加上一个DIV,因为DIV可以动态地滚动,并且可以插入其它控件。DIV的id为"divTree",且风格设置为溢出时自动滚动,宽与高都为100%,及满区域。代码如下:
<div id="divTree" style="width:100%; height:100%;background-color:#f5f5f5;border :1px solid Silver;overflow:auto;"> </div> |
我们在2区(单元格)上也加上一个DIV,在DIV里再插入一个表格。表格上放下控件,这很简单,就不详细说了。
我们在3区(单元格)上加上一个DIV。此DIV的id为" divContent ",且风格设置为竖直溢出时自动滚动,宽与高都为100%,及满区域,此DIV用来装载人员信息;在DIV里再插入一个表格, 此table的id为" tbList ",是用来输入、显示人员作息,同时在此表中插入一些如checkbox 、text、select等控件。说明,表的第二列是用来放人员唯一编号的,不显示。代码如下:
<div id="divContent" style="height:100%; overflow-y:auto;" width="100%"> <table id="tbList" border="1" width="100%"> <tr seqNo="1"><td> <table border="1" width="100%"> <tr> <td width="5%"><input type="checkbox" value="on"></input> </td> <td width="0%" style="display:none"> <input type="text" size="20"></input></td> <td width="40%"><input type="text" size="20"></input></td> <td width="25%"> <select size="1" name="D1"> <option value="0">男</option> <option selected="true" value="1">女</option> </select> </td> </tr> </table> </td></tr> </table> </div> |
摘要: 一个
Jbpm
员工请假流程的实例
作者:吴大愚
...
阅读全文