企业级应用系统体系架构十状态管理课件.ppt

上传人:小飞机 文档编号:3682339 上传时间:2023-03-15 格式:PPT 页数:107 大小:717.50KB
返回 下载 相关 举报
企业级应用系统体系架构十状态管理课件.ppt_第1页
第1页 / 共107页
企业级应用系统体系架构十状态管理课件.ppt_第2页
第2页 / 共107页
企业级应用系统体系架构十状态管理课件.ppt_第3页
第3页 / 共107页
企业级应用系统体系架构十状态管理课件.ppt_第4页
第4页 / 共107页
企业级应用系统体系架构十状态管理课件.ppt_第5页
第5页 / 共107页
点击查看更多>>
资源描述

《企业级应用系统体系架构十状态管理课件.ppt》由会员分享,可在线阅读,更多相关《企业级应用系统体系架构十状态管理课件.ppt(107页珍藏版)》请在三一办公上搜索。

1、企业级应用系统体系架构(十)状态管理,Chen HaopengWednesday,March 15,2023,References:TedNeward:Effective Enterprise Java,1,2,状态管理,对于寻求真理的人而言,有些准则是必须遵守的,真理并非教条或无知,而是通过推理、调查、检验、与探究得来的。无论其意图有多好,信仰都必须构建在事实而非幻想之上,幻想之上的信仰是最糟糕的虚假希望。Thomas Edison,3,状态管理,在企业级系统中,大部分工作都涉及数据处理。事实上,可以论证,企业级系统只做了一件事,那就是数据处理。在两层架构的客户/服务器系统时代,这还不太明显

2、,那时企业级程序员需要关心两种状态:瞬时状态(transient state)与持久状态(durable state)。瞬时状态并不算企业级数据所覆盖的正式部分,持久状态则是无论发生什么都需要被跟踪的部分。,4,状态管理,瞬时状态是那些企业并不关心,也不会为之流泪的数据,因为在系统崩溃的时候,真正重要的东西决不会丢失。电子商务中的购物车是瞬时状态典型的例子。在厚客户端或富客户端的应用中,瞬时状态很容易处理:相当于客户端进程中存储在局部变量中的数据,它们没有被保存在持久的存储介质中。当客户端进程结束的时候,瞬时状态也随之消亡,无需为其生命周期的处理而多费心思。,5,状态管理,不过在瘦客户端中,例

3、如基于HTML浏览器的应用,瞬时状态则呈现出另一种尺度。因为HTTP本就是无状态的协议,自身并不具备保存每一个客户端状态的能力。所以要由程序员在底层协议之上自己实现瞬时状态机制。另一方面则是持久状态,在谈到它时人们自然就会想到“持久数据(persistent data)”,即需要长久保存的数据。正规的说法是,如果定义了某个持久状态,那么它就绝对会被保存下来,即使遇到JVM终止甚至崩溃的情况也是如此。,6,状态管理,持久状态通常具有隐含的法律性或经济意义。在讨论状态管理时,二者的区别至关重要。因为对瞬时状态有效的机制对持久状态不一定有效,反之亦然。,7,状态处理,第1项:节省地使用 HttpSe

4、ssion第2项:使用对象优先的持久化来保存你的领域模型第3项:使用关系优先的持久化来显示关系模型的威力第4项:使用过程优先的持久化来创建一个封装层第5项:识别对象-层次结构的阻抗失配(impedance mismatch),8,节省地使用 HttpSession,在基于HTML/HTTP的应用中,为维护代表客户端的瞬时状态,servlet容器提供了一种称为会话空间的设施,被表示为HttpSession接口。遗憾的是,这种机制并非完全免费的。首先,在服务器端为每个客户端存储数据将会减少该服务器上的可用资源,这意味着服务器的最大负载能力会成比例地下降。这个算式很简单:在会话空间中保存越多的数据,

5、机器能够处理的会话就越少。由此推导出,为了令给定的机器能够支持尽可能多的客户端,必须将会话的存储量保持在最小。实际上,对于真正具备可扩展性的系统而言,无论何时都应该避免使用会话。如果在服务器端可以不产生任何为每个客户端进行处理的开销,那么机器的负载能力(在理论上)可以到达无限,能够支持任意多连接到它的客户端。,9,节省地使用 HttpSession,避免使用会话的建议不单单是考虑到系统的可扩展性。对于在Web集群内运行的servlet容器而言,这也是必须的。会话是驻留内存的结构。因为内存是局限于特定的机器,除非Web集群有某种机制,能够令给定客户端的每一次请求都被传送给同一个服务器,否则对应先

6、前的某个请求,其后续处理可能会找不到之前存储的会话对象。,10,节省地使用 HttpSession,有一种可能的机制可以对此提供支持:在服务器集群中,指派一个单独的节点作为会话状态服务器。对每一个请求,无论哪个节点正在处理它,该节点都向会话状态服务器查询此客户端的会话状态,然后将会话状态通过网络传给处理此请求的节点。然而,这种机制有两种副作用:(1)每个请求都增加了一次与会话状态服务器之间的往返访问,这增加了客户端请求的等待时间。但更重要的是,(2)所有的会话状态都被存储在集中的服务器上,这使得集群中产生了一个单一故障点(single point of failure)。,11,节省地使用 H

7、ttpSession,另一种可能的机制是采用P2P(peer-to-peer)方式。当一个请求进入某节点时,此节点发出一个集群广播信号,询问其它节点是否拥有此客户端最近的会话状态。拥有此客户端最近状态的节点对此做出回答,并将该会话状态传递给当前正处理请求的节点。,12,节省地使用 HttpSession,与会话相关的另一个需要注意的问题是会话的意外使用。JSP的规范清楚地陈述道,对于给定的JSP网页,“打开”会话的指示(带有session属性的page指示性标记),缺省地被设置为true,这意味着下面的JSP网页将为其建立一个会话,即使该网页从未使用会话:Hello,world,Im a st

8、ateless JSP page.It is now.,13,节省地使用 HttpSession,更糟的是,在Web应用的任何角落,只要有一个这样的JSP页面,在该客户端使用此Web应用的整个过程中,该会话(即使没有在会话中保存对象,也会带来相关的系统开销。)将一直存在。所以,除非你需要使用会话,否则你应该确保你的JSP页面都关闭会话。我希望对于给定的Web应用,有某种方法能够令session=false成为缺省设置,然而到目前为止它并非如此。希望你不要误解,我并不是鼓吹完全不使用会话,实际上,如果建议HTTP不提供任何合理的机制以提供每个用户的状态的话,将是很滑稽的。如果小心地使用,那么Ht

9、tpSession就可以提供必要而强大的机制去提供在Web应用中的每个用户的状态,而这通常是关键而且必需的东西。(否则怎么才能确定用户是否成功地通过了身份认证,或者在应用中跟踪用户的进度呢?)危险在于,不是必需使用会话的时候,过度地使用或滥用此机制,那将给servlet容器带来额外的开销。所以,如非必要,尽量不要使用会话,如果非用不可,为了尽可能少地消耗运行servlet容器的机器上的资源,请保持会话精简而有意义。,14,状态处理,第1项:节省地使用 HttpSession第2项:使用对象优先的持久化来保存你的领域模型第3项:使用关系优先的持久化来显示关系模型的威力第4项:使用过程优先的持久化

10、来创建一个封装层第5项:识别对象-层次结构的阻抗失配(impedance mismatch),15,使用对象优先的持久化来保存你的领域模型,使用对象优先的持久化方式时,我们力求在持久化的过程中保持对象的视角。这意味着无需我们的任何提示,对象就知道如何默默地持久化自己,或者它们能够提供某种以对象为中心的(object-centric)API进行持久化和读取操作。,16,使用对象优先的持久化来保存你的领域模型,那么在理想世界中,编写像下面这样的代码将自动在数据库中创建一个包含了代表25岁的Stu Halloway的项:Person p=new Person(“Stu”,“Halloway”,25)

11、;System.out.println(p);/Prints“Stu Halloway,age 25”而下面的代码将自动更新在第一段代码中所创建的行,将Stu的年龄从25改成30:Person p=Person.find(“Stu”,“Halloway”);System.out.println(p);/Prints“Stu Halloway,age 25”p.setAge(30);System.out.println(p);/Prints“Stu Halloway,age 30”注意到了吗,对象优先持久化方法的一个重要的优点:没有丑陋的SQL语句,不用为是该INSERT还是该UPDATE这样的

12、问题而烦心。我们所能看到的只是对象,这正是我们喜欢的方式。,17,使用对象优先的持久化来保存你的领域模型,然而,在读取对象时,对象优先的方法往往很快就不起作用了。一般说来,对象优先的方法可能会采取以下两种方式:要么以纯面向对象的形式,通过创建包含查询规则的对象,进行对象查询;要么使用某种特定的“查询语言”进行对象查询。在纯粹的对象优先的环境中,除了对象,我们不希望看到任何东西,所以我们创建了查询对象(Query Object),它包含了我们所关心的约束查询的规则。遗憾的是,如果要创建一个复杂的查询,若它的查询规则不是对象的主键(有时称为对象标识符object identifier,简称OID)

13、,从OODBMS的角度来看,这样做通常是复杂而且笨拙的:QueryObject q=new QueryObject(Person.class);q.add(Criteria.and(Criteria.greaterThan(“dependents”,2),Criteria.lessThan(“income”,80000);q.add(Criteria.and(Criteria.greaterThan(“dependents”,0),Criteria.lessThan(“income”,60000);,18,使用对象优先的持久化来保存你的领域模型,我们此处所做的等价于下面几行语句:SELECT*

14、FROM person pWHERE(p.dependents 2 AND p.income 0 AND p.income 60000)哪一种更易于阅读?如果我们开始在查询中执行深层嵌套的布尔逻辑,例如查询“收入少于$80,000而且有超过2个子女的人,或者收入少于$60,000而且无子女的人”,此时事情将以指数级地恶化。事实上并不难发现,比起通用目的的查询语言,例如SQL,纯对象优先的查询方法对于能查询什么,有着过于严格的限制。,19,使用对象优先的持久化来保存你的领域模型,这启发我们找寻第二条路,即创建某种“查询语言”,使它能够更简明地表达查询,而无需使用过于复杂的代码。Java中所有的对

15、象优先技术最终都回到了这一点:EJB 引入了EJBQL,一种为实体bean编写查询方法(finder)的查询语言;JDO引入了JDOQL,它为JDO增强的持久类做相同的工作;而OODBMS回过头来采用OQL(Object Query Language)对象查询语言。这些语言相互之间有着微妙的区别,但有着一个明确的共同点:它们都很像SQL,而SQL正是我们起初试图摆脱的。,20,使用对象优先的持久化来保存你的领域模型,使用对象优先的方法还有另一个副作用,即不可视的往返访问。例如,当像下面这样使用实体bean时,引发了多少次数据库访问呢?PersonHome ph=(PersonHome)ctx.

16、lookup(java:comp/env/PersonHome);Collection personCollection=ph.findByLastName(Halloway);for(Iterator i=personCollection.iterator();i.hasNext();)Person p=(Person)i.hasNext();System.out.println(Found+p.getFirstName()+p.getLastName();虽然看起来似乎只访问了一次数据库(读取每个姓Halloway的Person对象,并将其组装到PersonBean池中的实体bean上),

17、但实际上,这正是EJB中的N+1次查询问题,查找方法调用只查找符合查询条件的行的主键,然后用只知道主键的实体bean的存根组装Collection中,并在必要的时候才将数据惰性加载到(lazy-load)实体bean中。,21,使用对象优先的持久化来保存你的领域模型,开发一个实体bean的实现,对于查询的结果,它不是简单地取回实体的OID/主键,而是取得保存在实体中的整个数据集,这也是可行的。实质上这就是采用积极加载(eager-load),而不是更为常用的惰性加载。遗憾的是,这引发了一个相反的问题,现在我们抱怨的是取回的数据太多,而不是太少。这里问题的关键是,在对象优先的持久化场景中,数据读

18、取的原子单位是对象本身,从面向对象的观点来看,返回那些比对象小的东西根本没有意义,就像在SQL查询中,如果返回的是比行还小的单位,结果同样没有意义。,22,使用对象优先的持久化来保存你的领域模型,我可以写出某些类似于下面的语句:SELECT p.FirstName,p.LastNameFROM Person pWHERE p.LastName=Halloway;可是,这到底返回了什么?通常而言,一次对象查询的返回值是一个已定义类型的对象(如上例中的Person实例)。可此处我们得到的是什么?对于只返回“部分”对象,并没有普遍都可接受的方式,所以典型的结果是采用像ResultSet或者Java

19、Map一类的东西(或者是Map实例的一个List)。即使我们理清了这些问题,对象优先的查询还是有其它的问题:对象到对象的引用。在此种情况下,困难并不经常发生,因为我们还没有很好的建模技巧去管理在关系型数据库中的一对多、多对多、或多对一的关系(顺带一提,这并非微不足道)。但是问题仍然存在,当一个对象被读取的时候,问题就来了,是否应该将所有与它相关联的对象都读取出来呢。还有,我们应该如何解决这个问题:两次独立的查询通过该间接引用取回了两个相同的对象?,23,使用对象优先的持久化来保存你的领域模型,例如,考虑此场景,我们的系统中有四个Person对象:Stu Halloway 娶了Joanna Ha

20、lloway,他们有两个孩子Hattie Halloway和Harper Halloway。从任何良好的对象观点来看,这意味着好的Person模型应该有一个配偶属性spouse,它是Person类型的(或者更确切一点,是指向Person的引用),同样还有一个孩子属性children,它是某种集合类型,包含指向Person的引用。现在,如果我们执行前面的查询,以取得第一个对象(让我们假设是Stu),那么通过网络取回Stu对象时是否也应该通过网络去取回Joanna、Hattie和Harper呢?问题又来了,我们此处是采用积极加载数据,还是惰性加载呢,记住,这些对象是被Stu对象实例的域所引用的。当

21、我们从查询结果中取得下一个对象Joanna时,她也引用着Stu,此时在客户端的进程空间中,我们是有一个Stu对象还是两个?如果我们做两次独立的查询,第一次只读取Stu对象,第二次读取Joanna,会发生什么状况呢?对象同一性(identity)的概念很重要,因为在Java中,对象的同一性是通过this指针(对象的位置)来确立的,而在数据库中它是通过主键来表现的,令二者相匹配困难重重,特别是当我们将事务处理置于二者之间时。不过这也并非是不可解决的问题,同一性映射(Identity Map)就是一个典型的解决方案,但是作为一个对象程序员,你对此必须警惕,以防你采用的对象优先持久化机制没有考虑到此问

22、题。,24,使用对象优先的持久化来保存你的领域模型,此处最终的结论是,如果你想采用某种对象优先的持久化方法,可不能仅仅因为“更容易使用”就选择它。在很多情况下,只使用对象,其性能与吸引力就已经足够弥补你别处的损失了,并且还具备很多优点。,25,状态处理,第1项:节省地使用 HttpSession第2项:使用对象优先的持久化来保存你的领域模型第3项:使用关系优先的持久化来显示关系模型的威力第4项:使用过程优先的持久化来创建一个封装层第5项:识别对象-层次结构的阻抗失配(impedance mismatch),26,使用关系优先的持久化来显示关系模型的威力,对象和关系相处得并不好。在这两种技术之间

23、取得良好的映射很困难,这种困难甚至有自己的名字:对象与关系的阻抗失配(impedance mismatch)。面向对象语言与关系型数据访问技术(如JDBC)协同工作时的问题,很大一部分只是因为这两种技术的基础在看待世界时所采用的方式非常不同。面向对象语言希望使用对象,它具有属性(域)和行为(方法)。而关系技术将世界看成元组,即被群组为某种逻辑“事物”的数据项集合。,27,使用关系优先的持久化来显示关系模型的威力,尽管针对对象-关系映射层所固有的问题有大量的论文,且此问题也超出了本书的讨论范围,但是简要地看看其中的一个问题,将有助于我们理解为什么对象-关系映射问题在J2EE系统中如此普遍。请思考

24、下面这个简单的领域对象模型:public class Person private String firstName;private String lastName;private int age;/.public class Employee extends Person private long employeeID;private float monthlySalary;这可能是世界上最简单的领域模型了,但我们应该如何将它持久化到一个关系型数据库中呢?,28,使用关系优先的持久化来显示关系模型的威力,一种方法是创建两个表:PERSON和EMPLOYEE。使用外键(foreign-key)

25、关系将二者的行彼此关联起来。每次我们想得到一个Employee时就需要对两个表做一次连接(join)操作,而每次查询和修改数据时,数据库还需做更多的工作。我们也可以将Person和Employee数据存入单一的EMPLOYEE表中,但如果我们又创建了Student(继承自Person),并想找到所有姓Smith的Person对象时,我们不得不搜索STUDENT和EMPLOYEE两个表,而二者在关系层面上并不相关。如果这种继承层次继续变得更深,则问题几乎呈指数级地混杂起来。更何况,企业应用的开发人员通常没有对数据库模式(schema)的控制权,因为遗留系统或其它J2EE系统已经在使用它了,或者是

26、由其他开发团队负责数据库模式。所以,即使我们想建立一个表的结构,使它优雅地映射到我们的对象模型,我们也不能随心所欲地改变数据库模式的定义。,29,使用关系优先的持久化来显示关系模型的威力,从另一个完全不同的角度来看,也许有其它更为实际的原因致使我们放弃对象优先的方式。由于这些(以及更多的)原因,我们通常更易于采用关系的观点来看待并操纵数据,而不是将访问关系的操作隐藏在其它某种封装技术之后,例如面向对象、面向过程、或面向层次结构。如果要理解我对于采用关系优先方式的看法,我们需要后退一步,重新看看关系型方式到底是什么。Chris Date与E.F.Codd一同被看作是关系模型之父,按Chris D

27、ate的说法,“关系系统建立在形式化的基础或理论之上,被称为数据的关系模型”。对数学家而言,关系模型是建立在集合论和谓词逻辑的基础上。然而对我们而言,用最简单的话来说,数据的关系模型就是表而已。访问数据得到的只是表,而操作那些数据的运算符(SQL)也是由表再产生表。,30,使用关系优先的持久化来显示关系模型的威力,关系型数据库的核心就是表,表就是关系模型中“关系”,就是它使得关系模型如此强大。由此关系数据访问做到了闭包性(closure):一次访问的结果可以作为另一次访问的输入。这使得我们能够写出嵌套的表达式:表达式中的操作数由一般的表达式来代表,而不是直接使用表名。SQL之所以那么强大,很大

28、部分原因就是因为支持嵌套,虽然我们并不想过多地使用嵌套为什么闭包性很重要?对于关系型数据库而言,SQL是一种强大的数据访问语言,而SQL查询得到的结果就是表,这真是值得庆幸,因为这样我们只需要一个API,就能取得任何一次查询的返回结果,无论结果数据是很多,还是很少。我们也没有“比对象小”的问题,因为取得的结果总是表,即使是只有一列的表。我们必须面对的问题是,关系模型经常不能与程序员使用的对象模型相匹配,不过关于这一点我们可说的有很多。还是让我们先来看看怎样令关系访问本身更容易吧。,31,使用关系优先的持久化来显示关系模型的威力,在你一想到你余下的职业生涯都要跟讨厌的底层的JDBC访问打交道,因

29、而在恐惧中萎缩之前,请先做个深呼吸,采用关系优先的方法并不意味着放弃任何比JDBC层次高的方法。实际上,远远不是这样。Java允许我们可以采用多种更为简单的机制进行关系的存取访问,而不仅仅是原始的JDBC(在许多情况下JDBC仍然是一种可选的方案,尽管它具有相对比较低层的特性)。首先,JDBC可不仅仅只是Connection、Statement、和ResultSet对象。RowSet和Sun公司独特的CachedRowSet,通过将查询行为与获得的结果进行封装,令JDBC更便于使用。,32,使用关系优先的持久化来显示关系模型的威力,因此,假设你没有JDBC DataSource,也可以很容易地

30、像下面这样进行查询:RowSet rs=new WebRowSet();/Or use another RowSet implementation/Provide RowSet with enough information to obtain a/Connectionrs.setUrl(jdbc:dburl:/dbserver/PEOPLE);rs.setUsername(user);rs.setPassword(password);rs.setCommand(SELECT first_name,last_name FROM person+WHERE last_name=?);rs.setS

31、tring(1,Halloway);rs.execute();/rs now holds the results of the query,33,使用关系优先的持久化来显示关系模型的威力,大多数对RowSet的调用都可以被隐藏在对象工厂接口之后,所以客户端代码还可以再少一些,如下所示:RowSet rs=MyRowSetFactory.getRowSet();rs.setCommand(.);rs.setString(1,“Halloway”);rs.execute();这与直接使用SQL进行访问几乎一样简单。此处使用的工厂也并不难以想象:public class MyRowSetFactor

32、y public static getRowSet()RowSet rs=new WebRowSet();rs.setDataSourceName(java:comp/env/jdbc/PEOPLE_DS);return rs;,34,使用关系优先的持久化来显示关系模型的威力,一种方法是保持数据库面向表的观点,然后通过表数据网关(Table Data Gateway)在你与数据访问技术之间构架更多的渠道。其实质上就是将每个表变成一个类,然后用这些类依次作为该表中任意行的访问点。public class PersonGateway private PersonGateway()/*Singlet

33、oncant create*/public static Person findAll()ArrayList al=new ArrayList();RowSet rs=MyRowSetFactory.getRowSet();rs.setCommand(SELECT first_name,last_name,age+FROM person);rs.execute();while(rs.next()al.add(new Person(rs.getString(1),rs.getString(2),rs.getInt(3);return(Person)al.toArray(new Person0);

34、public static void update(Person p)/And so on,and so on,and so on,35,使用关系优先的持久化来显示关系模型的威力,表数据网关可以扩展出新的变体,叫做查询数据网关(Query Data Gateway),它不是对单个表进行包装,而是对一个查询进行包装。public class ChildrenGateway private ChildrenGateway()public static Person findKidsForPerson(Person p)ArrayList al=new ArrayList();RowSet rs=M

35、yRowSetFactory.getRowSet();rs.setCommand(SELECT first_name,last_name,age+FROM person p,parent_link pp+WHERE p.id=pp.child_id+AND p.last_name=?);rs.setInt(1,p.getPersonID();rs.execute();while(rs.next()al.add(new Person(rs.getString(1),rs.getString(2),rs.getInt(3);return(Person)al.toArray(new Person0)

36、;,36,状态处理,第1项:节省地使用 HttpSession第2项:使用对象优先的持久化来保存你的领域模型第3项:使用关系优先的持久化来显示关系模型的威力第4项:使用过程优先的持久化来创建一个封装层第5项:识别对象-层次结构的阻抗失配(impedance mismatch),37,使用过程优先的持久化来创建一个封装层,使用存储过程作为持久化模型最大的优点是,我们能够将存储过程的实现交给负责数据库的同事,将整个数据模型都交给他们设计、实现,以及在必要时进行调整。只要存储过程的定义不变(否则我们就会得到运行期的SQLExecption异常),以及它的实现确实如我们所希望的(存储或取回数据),我们

37、就可以完全忽略底层的数据模型。如果你在关系的设计与执行方面与我一样“优秀”的话(即根本不擅长),这确实是件好事。,38,状态处理,第1项:节省地使用 HttpSession第2项:使用对象优先的持久化来保存你的领域模型第3项:使用关系优先的持久化来显示关系模型的威力第4项:使用过程优先的持久化来创建一个封装层第5项:识别对象-层次结构的阻抗失配(impedance mismatch),39,识别对象-层次结构的阻抗失配,XML无所不在,包括在你的持久化方案中。问题在于XML在表示数据时使用的是固有的层次结构的方式,看看XML Infoset 规范,它要求数据是格式良好的(well-formed

38、),即XML文档中的元素必须形成一棵良好的元素树(每个元素都可以有子元素嵌套其中,每个元素都有一个唯一的父结点,唯一的例外是“根”结点,它囊括了整个文档)。这意味着XML善于表现层级结构的数据(所以作为这一项的题目),假设你的对象形成一个整洁的层次结构,则XML是表达该对象最自然的方式(因此自然地假设了XML和对象是手牵着手)。,40,识别对象-层次结构的阻抗失配,但是当对象没办法形成整洁、自然的树时会发生什么呢?层次模型带来的问题是,在其中查找数据很困难。用户必须亲自在树的元素中搜寻,这迫使用户必须知道“如何查询”以获取数据,而不是仅仅关注“查询什么”感兴趣的数据即可。随着XML的出现(以及

39、不断增长的对“XML数据库”的兴趣,尽管这个词具有二义性),似乎层次数据模型也再次流行起来。虽然对层次数据模型的全面讨论超出了本书的范围,但在此仍有必要弄清两件事:我们何时会在J2EE中使用层次数据模型,而它对Java程序员又有什么影响。,41,识别对象-层次结构的阻抗失配,将对象映射到XML(今天最常见的层次结构存储模型)并不是件简单的事情,这使得我们想知道是否存在对象-层次结构的阻抗失配(impedance mismatch),换言之,形式自由的对象模型与XML Infoset严格的层次结构模型是否难以匹配。将对象映射到层次模型的问题,很大程度上与发生在将对象映射到关系模型时发生的问题一样

40、:保证对象的同一性。为了说清楚我的意思,让我们重新看看前面曾经用过的Person对象:public class Person public String firstName;public String lastName;public int age;public Person(String fn,String ln,int a)firstName=fn;lastName=ln;age=a;,42,识别对象-层次结构的阻抗失配,同样地简单而直接,也不难想象该对象的XML表示会是什么样子:Ron Reynolds 30 到目前为止一切都好。但是,现在让我们来添加一些东西,它们对面向对象模型而言绝对

41、合理,却将层次结构完全打破了,这就是循环引用:public class Person public String firstName;public String lastName;public int age;public Person spouse;public Person(String fn,String ln,int a)firstName=fn;lastName=ln;age=a;,43,识别对象-层次结构的阻抗失配,你应该如何表现下面的对象集?Person ron=new Person(Ron,Reynolds,31);Person lisa=new Person(Lisa,Rey

42、nolds,25);ron.spouse=lisa;lisa.spouse=ron;下面的将ron序列化输出到XML的方法并非不合理,它简单地遍历属性成员,递归处理每个的对象并依次遍历其属性成员,以此类推。然而这样做很快就会出问题,如下所示:Ron Reynolds 31 Lisa Reynolds 25 Ron Reynolds 31,44,识别对象-层次结构的阻抗失配,正如你看到的,因为两个对象循环引用对方,此处产生了无限递归。我们可以使用与Java 对象序列化所采用的相同的方法来处理这个问题,即对哪些项被序列化了而哪些项没有被序列化都保持跟踪,但是这样的话我们就陷入了更大的问题:即使在给

43、定的XML层次结构中我们保持对对象同一性的跟踪,那么我们怎么在多个层次结构之间作到这一点呢?String param1=ron.toXML();/Serialize to XMLString param2=lisa.toXML();/Serialize to XMLsendXMLMessage(+param1+param2+);/*Produces:param1=,45,识别对象-层次结构的阻抗失配,Ron Reynolds 31 Lisa Reynolds 25,46,识别对象-层次结构的阻抗失配,param2=Lisa Reynolds 25 Ron Reynolds 25,47,状态管理

44、,对于寻求真理的人而言,有些准则是必须遵守的,真理并非教条或无知,而是通过推理、调查、检验、与探究得来的。无论其意图有多好,信仰都必须构建在事实而非幻想之上,幻想之上的信仰是最糟糕的虚假希望。Thomas Edison,48,状态处理,第1项:使用进程内(in-process)或本地存储以避开网络第2项:不要假设拥有数据或数据库第3项:惰性加载不频繁使用的数据第4项:积极载入频繁使用的数据第5项:批处理SQL的工作以避免往返访问第6项:了解你的JDBC供应商第7项:调整你的SQL语句,49,使用进程内或本地存储以避开网络,大多数时候,当J2EE开发人员开始设计或划分项目的架构层次时,数据存储问

45、题已经形成结论了:将采用关系型数据库,运行在数据中心或操作中心某处的机器上。为什么?并没有什么神秘或神奇的答案,仅仅只是为了可访问性。我们希望该系统的任何潜在客户都能够访问到数据。过去,在n层架构流行之前,客户端直接连接到服务器上,为了让所有客户端都能够访问到全部数据,数据需要直接存储在服务器端。那时网络还不普及,使网络更简单的无线访问技术还没出现。将机器连接到网络上是日常主要的杂务,结果是,P2P通讯的基础概念总是在讨论最底层的网络堆栈问题(例如IP协议)。,50,使用进程内或本地存储以避开网络,我们渐渐认识到,将所有数据存放在一台中心服务器上有许多优点,即主要的负荷都置于该服务器端,而不是

46、客户端。因为服务器是单独的机器,(所以我们相信)升级或替换它变得很划算,特别是比起替换所有连接到它的客户端来,更是如此。所以不久之后我们就建立起了数据库中心化的思想,我们开始将数据库放在能找到的最重的铁盒子中,用容量最大的RAM和巨大的千兆字节(后来是万亿字节)的驱动器装满其中。这些关于历史的题外话的重点是,中心化的远程数据库服务器的存在只是为了提供单一的数据聚合点,而不是因为数据库“必须”运行在服务器上,那可是花了5、6、或7位数的开销的。我们将数据存放到服务器上,因为(a)它是个很方便的地方;(b)易于将所有为客户端进行的处理放在一个集中的地方,且无需向客户端推出更新(零部署);(c)这是

47、一种令数据与处理靠近的方法。,51,使用进程内或本地存储以避开网络,通过线路传送所有数据并不便宜,而且也有其内在固有的问题。在可扩展性与执行性能两方面都有所牺牲(因为每有一个byte的带宽用于在网络上传送数据时,就有一个byte的带宽不能用于其它目的),而重组来回传递的数据所消耗的时间也并非微不足道。考虑到我们将数据放在中心的数据库中是为了使其它客户也可以使用它们,而这样的数据传送开销又不低,所以除非必须,否则不要将数据放在远程数据库中。也就是说,不要将任何数据放到远程数据库中,除非确实需要与其它客户端共享它们。,52,使用进程内或本地存储以避开网络,在这种情况下,在与客户端应用相同的进程中运

48、行关系型数据库(或其它数据存储技术,这里我们可以考虑使用对象数据库,甚至选择XML数据库),不仅可以令网络往返访问回合最少,而且可以将数据完全保存在本地机器上。虽然在我们的servlet容器内运行Oracle恐怕一段时间内还不可行,但是运行一个全Java的RDBMS实现却并非遥不可及。例如,Cloudscape就提供了这项功能,以及PointBase和HSQLDB(在Sourceforge上的一个开放源代码的实现),实质上是通过伪装成JDBC驱动程序而成为数据库的。或者,如果你喜欢基于对象的方法,另一个开源项目Prevayler,它以传统的对象优先持久化方式存储任意的Java对象。如果你希望看

49、到层次结构形式的数据,Xindice是Apache Group下面的一个开源的XML数据库。,53,使用进程内或本地存储以避开网络,RowSet本身就是一个简单的进程内数据存储技术。因为RowSet是完全与数据库断开连接的,我们可以用查询的结果创建一个RowSet,并在客户进程的整个生命期内都保持着此RowSet,而无需担心对数据库可扩展性的影响。因为RowSet是可序列化的,所以我们可以将它存进任何OutputStream而不会对其造成改动,例如一个文件或一个Preferences节点。事实上,如果RowSet是围绕着配置数据而被包装的,那么与以某些方式将其存储在本地文件中相比,将它保存在P

50、references节点中更有意义。它不会扩展到数千行,也不强制要求关系的完整性,但是如果你需要存储很多数据,或是将关系约束施加于本地数据,那么你就应该使用“真正的”数据库,例如HSQLDB或支持“嵌入”到Java进程中的商业产品。,54,使用进程内或本地存储以避开网络,然而这个故事还有另一面,一个显而易见的事实,远程数据库需要能够触及它的网络。尽管这似乎没有必要说出来,但是还是先思考一下这个问题,然后我们来看看大型制造业公司中普遍存在的连接到清单数据库的销售-订单应用。我们将此应用安装在销售员的便携式电脑上,然后此销售员去拜访一个客户公司的副总裁(VP)。在饮过红酒,吃过午餐,以及打完漂亮的

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 生活休闲 > 在线阅读


备案号:宁ICP备20000045号-2

经营许可证:宁B2-20210002

宁公网安备 64010402000987号