简而言之,SDO是用于数据应用程序开发的框架,其中包括体系结构和API。 SDO执行以下操作:
简化J2EE数据编程模型 在面向服务的体系结构(SOA)中抽象数据 统一数据应用程序开发 支持和集成XML 结合了J2EE模式和最佳实践在SDO框架的简介中,我们将尝试解释SDO工作背后的动机以及SDO与其他规范之间的差异。 然后,我们将描述构成SDO的组件。 最后,当我们描述一个示例SDO应用程序时,您将有机会看到SDO的实际效果。
大多数开发人员会问有关服务数据对象(SDO)的第一个问题是原因。 J2EE是否足够大和复杂(难于学习)? 另外,其他框架已经在Java环境中支持XML,不是吗? 幸运的是,答案是应该使我们大多数人感到高兴:SDO的出现是一种简化J2EE数据编程模型的方法,从而使J2EE开发人员有更多的时间专注于其应用程序的业务逻辑。
服务数据对象框架为数据应用程序开发提供了一个统一的框架。 使用SDO,您无需熟悉特定于技术的API即可访问和利用数据。 您只需要知道一个API,即SDO API,就可以使用来自多个数据源的数据,包括关系数据库,实体EJB组件,XML页面,Web服务,Java Connector Architecture,JavaServer Pages页面等等。
请注意,我们使用了框架一词。 这类似于Eclipse框架。 Eclipse的设计使其凭借坚实而可扩展的基础可以将工具集成在一起。 从某种意义上说,SDO是相似的,它提供了可以向其中添加应用程序的框架,并且这些应用程序都将与SDO模型保持一致。
与其他一些数据集成模型不同,SDO不仅停留在数据抽象上。 SDO框架还包含了大量的J2EE模式和最佳实践,从而可以轻松地将经过验证的体系结构和设计合并到您的应用程序中。 例如,当今大多数Web应用程序没有(也不能)100%地连接到后端系统。 因此SDO支持断开连接的编程模型。 同样,当今的应用程序往往非常复杂,涉及许多方面。 数据将如何存储? 已发送? 呈现给最终用户的GUI框架? SDO编程模型规定了使用模式,这些模式允许将所有这些关注点清楚地分开。
XML在分布式应用程序中变得无处不在。 例如,XML模式(XSD)用于以应用程序的数据格式定义业务规则。 同样,XML本身也用于促进交互:Web服务使用基于XML的SOAP作为消息传递技术。 XML是SDO的非常重要的驱动程序,并且受框架支持和集成。
正如我们前面提到的,SDO并不是提出解决分布式应用程序中数据集成问题的唯一技术。 在本节中,我们将了解SDO如何与类似的编程框架(如JDO,JAXB和EMF)相提并论。
Web数据对象或WDO是IBMWebSphere®Application Server 5.1和IBM WebSphere Studio Application Developer 5.1.2中附带的SDO早期版本的名称。 如果您花了很多时间在WebSphere Studio 5.1.2上,那么您应该已经对SDO有所了解,尽管您可能习惯于将它表示为WDO,例如在库名中。 忘了WDO,现在叫SDO!
JDO代表Java数据对象。 JDO已通过2003年5月的Java社区流程(JCP)1.0和维护版本1.0.1进行了标准化。正在为2.0版成立一个JCP专家组。 JDO研究Java环境中的数据编程,并提供通用API来访问存储在各种类型的数据源中的数据。 例如数据库,文件系统或事务处理系统。 JDO保留了Java对象(图形)之间的关系,同时允许并发访问数据。
JDO的目标与SDO的目标相似,因为它希望简化和统一Java数据编程,以便开发人员可以专注于业务逻辑而不是基础技术。 但是,主要区别在于,JDO仅关注持久性问题(J2EE数据层或企业信息系统(EIS)层),而SDO更通用,表示可以在任何J2EE层之间流动的数据,例如演示文稿和业务层。
有趣的是,SDO可以与JDO结合使用,其中JDO是SDO可以访问的数据源,应用了数据传输对象(DTO)设计模式。 同样,SDO可以与实体EJB组件和Java连接器体系结构(JCA)结合使用,目的是提供统一的数据访问。
EMF代表Eclipse Modeling Framework。 基于使用Java接口,XML Schema或UML类图定义的数据模型,EMF将生成一个统一的元模型(称为Ecore),该元模型与框架一起可用于创建该模型的高质量实现。 EMF提供了持久性,非常有效的反射式通用对象操作API和更改通知框架。 EMF还包括用于构建EMF模型编辑器的通用可重用类。
EMF和SDO都处理数据表示。 实际上,本文稍后将使用的IBM SDO的参考实现是SDO的EMF实现。 基于SDO本身的UML模型定义,甚至使用EMF代码生成来创建一些SDO实现。 SDO的实现本质上是EMF上的薄层(外墙),并且作为EMF项目的一部分打包和运输。 请参阅相关主题关于EMF的更多信息。
JAXB代表用于XML数据绑定的Java API。 JCP于2003年1月发布了JAXB1.0。JCP专家组已经为2.0版制定了早期草案。 JAXB是关于XML数据绑定的。 也就是说,将XML数据表示为内存中的Java对象。 作为Java语言的XML绑定框架,JAXB使您不必自己解析或创建XML文档。 (实际上,它使您完全不必处理XML。)JAXB为您执行编组/序列化(Java到XML)和解组/反序列化(XML到Java)。
SDO定义了自己的Java绑定框架,但又向前迈了一步。 尽管JAXB仅专注于Java到XML的绑定,但XML并不是唯一绑定到SDO的数据。 如前所述,SDO提供对各种类型数据的统一访问,其中只有一种是XML。 SDO还提供静态和动态API * ,而JAXB仅提供静态绑定。
* 请注意,尽管EMF代码生成器还完全支持数据对象的静态代码生成,但是本文的示例应用程序仅使用动态SDO。
ADO曾经代表ActiveX数据对象,但在.NET上下文中不再适用。 ADO .NET在.NET框架的不同层之间提供统一的数据访问。
ADO .NET和SDO具有相似的动机来支持XML和分布在多层上的应用程序。 除了技术差异之外,这两种技术之间的主要区别在于ADO .NET用于Microsoft .NET平台,并且是专有技术,而SDO用于Java(J2EE)平台,并且通过Java Community Process进行了标准化。
在本节中,我们将提供SDO的体系结构概述。 我们将描述组成框架的每个组件,并说明它们如何协同工作。 我们将讨论的前三个组件是SDO的“概念性”功能:它们在API中没有相应的接口。
SDO客户端使用SDO框架来处理数据。 他们没有使用特定于技术的API和框架,而是使用SDO编程模型和API。 SDO客户端可以处理SDO数据图(请参见图1 ),而无需知道如何使用或持久化他们正在使用的数据。
数据中介服务(DMS)负责根据数据源创建数据图,并根据对数据图所做的更改来更新数据源。 数据介体框架不在SDO 1.0规范的范围内。 换句话说,SDO 1.0并未讨论特定的DMS。 DMS的示例包括:JDBC DMS,实体EJB DMS,XML DMS等。
数据源不限于后端数据源(例如,持久性数据库)。 数据源包含其自身格式的数据。 只有DMS访问数据源,而SDO应用程序则不能。 SDO应用程序只能与数据图中的数据对象一起使用。
以下每个组件对应于SDO编程模型中的Java接口。 该SDO参考实现(请参阅相关信息 )提供了这些接口基于EMF的实现。
数据对象是SDO的基本组成部分。 实际上,它们是在规范本身名称中找到的服务数据对象 。 数据对象是结构化数据的SDO表示。 数据对象是通用的,并提供DMS构建的结构化数据的通用视图。 例如,虽然JDBC DMS需要了解持久性技术(例如关系数据库)以及如何配置和访问它,但SDO客户端不需要了解任何有关它的知识。 数据对象在属性中保留其“数据”(稍后会更多地讨论属性)。 数据对象提供了方便的创建和删除方法(带有各种签名和delete() createDataObject() )以及用于获取其类型(实例类,名称,属性和名称空间)的反射方法。 数据对象链接在一起并包含在数据图中。
数据图为数据对象树提供了一个容器。 它们由DMS制作,供SDO客户使用。 修改后,数据图将传递回DMS以更新数据源。 SDO客户端可以遍历数据图并读取和修改其数据对象。 SDO是断开连接的体系结构,因为SDO客户端与DMS和数据源断开了连接。 他们只看到数据图。 此外,数据图可以包括表示来自不同数据源的数据的对象。 数据图包含一个根数据对象,所有与根相关的数据对象,以及一个变更摘要(有关变更摘要的更多信息)。 当在应用程序组件之间(例如,在服务调用期间在Web服务请求者和提供者之间)传输到DMS或保存到磁盘时,数据图被序列化为XML。 SDO规范提供了此序列化的XML模式。 图1显示了SDO数据图。
更改摘要包含在数据图中,用于表示对DMS返回的数据图所做的更改。 它们最初是空的(当数据图返回给客户端时),并在数据图被修改时填充。 DMS在后端更新时使用更改摘要,以将更改应用回数据源。 它们允许DMS通过在数据图中提供已更改属性的列表(以及它们的旧值)以及已创建和已删除的数据对象,来高效,增量地更新数据源。 仅当更改摘要的日志记录被激活时,信息才会添加到数据图的更改摘要中。 变更摘要为DMS提供了打开和关闭日志记录的方法,我们将在示例应用程序部分中更详细地描述。
数据对象通过一系列属性保存其内容。 每个属性都有一个类型,该类型可以是诸如原始类型(例如int )之类的属性类型,也可以是常用的数据类型(例如Date )之类的属性,或者如果是引用,则是另一种数据对象的类型。 每个数据对象为其属性提供读写访问方法(getter和setter)。 提供了这些访问器的几个重载版本,允许通过传递属性名称( String ),数字( int )或属性元对象本身来访问属性。 字符串访问器还支持类似XPath的语法来访问属性。 例如,您可以在公司数据对象上调用get("department[number=123]")以获取其第一个部门,其编号为123 。 序列更高级。 它们允许在属性值对的异构列表之间保留顺序。
足够的概念和理论! 现在是一些动手练习的时候了。 好消息是您可以立即免费使用SDO! 在本节中,我们提供一个SDO示例应用程序,该应用程序在IBM的SDO参考实现上运行,该参考实现打包为Eclipse Modeling Framework(EMF)的一部分。 我们将首先描述如何安装EMF 2.0.1(包括SDO),然后向您展示如何设置本文提供的示例应用程序。
如果您已经安装了EMF 2.0.1,或者知道如何安装,请跳到下一部分。
IBM的SDO 1.0实现与EMF 2.0.1打包在一起。 您需要安装EMF 2.0.1 *才能使用SDO。 您可以使用EMF网站上介绍的Eclipse更新管理器方法,或按照以下步骤操作。
* EDO 2.0.0中还提供了SDO 1.0的实现。
在EMF主页上 ,您可以在“快速导航”部分下找到一系列下载链接。 您需要“ v2.x:EMF和SDO”下载选项。 在安装EMF之前,请确保已阅读安装要求。 基本上,在安装EMF 2.0.1之前,需要先安装Eclipse 3.0.1和Java Development Kit(JDK)1.4。 确保选择EMF 2.0.1版本。 我们建议使用“全部”作为包的类型:emf-sdo-xsd-SDK-2.0.1.zip,以便将源代码,运行时文件和文档全部集中在一个文件中。 如果愿意,可以下载SDO的最小软件包,标记为“ EMF&SDO RT”:emf-sdo-runtime-2.0.1.zip。
将zip文件提取到提取eclipse的位置(归档文件中的文件结构为eclipse / plugins / ...)。 要检查EMF安装是否成功,请启动Eclipse,然后选择帮助>关于Eclipse平台 。 单击插件详细信息按钮。 确保org.eclipse.emf。*插件处于2.0.1级别。 以下六个与SDO相关的插件:
org.eclipse.emf.commonj.sdo org.eclipse.emf.ecore.sdo org.eclipse.emf.ecore.sdo.doc org.eclipse.emf.ecore.sdo.edit org.eclipse.emf.ecore.sdo.editor org.eclipse.emf.ecore.sdo.source在运行时仅需要两个插件org.eclipse.emf.commonj.sdo和org.eclipse.emf.ecore.sdo ,如果您选择仅安装运行时插件,它们可能是您唯一看到的两个插件。 对于EMF安装就是这样。
下一步是将本文的SDO示例应用程序添加到您的工作区中。 跟着这些步骤:
启动Eclipse并创建一个新的Plug-In Project。 将项目命名为SDOSample,并使用源文件夹src和输出文件夹bin创建一个Java源项目。 单击下一步 。 取消选择“生成控制插件生命周期的Java类”选项,然后单击“ 完成” 。接下来,单击本文顶部或底部的“代码”图标(或查看“ 下载”部分)以获取j-sdoSample.zip。 将其解压缩到SDOSample目录(从Eclipse中的项目内:Import ...> Zip文件)。 确保保留文件夹结构并覆盖现有文件。 现在,使用j-sdoSample.zip中的文件填充SDOSample项目。
注意: SDOSample被打包为Eclipse插件项目,因此您不必自己设置库依赖项。 但是,该示例只是Java代码,因此,只要CLASSPATH包含EMF和SDO库(JAR文件),它也可以作为独立应用程序运行。
现在,您的环境应类似于图2所示的屏幕截图。
现在,我们准备开始使用示例SDO应用程序。
在本文的其余部分中,我们将使用的示例应用程序在功能方面受到限制,但是它将帮助您更好地理解SDO。 该应用程序分为两个部分,分为两个相应的包:dms和client。
SDO 1.0未指定标准DMS API。 因此,在此示例中,我们设计了自己的DMS接口,该接口提供了两种方法,如清单1所示。
客户端实例化DMS并为特定员工调用其get()方法:Big Boss,Wayne Blanchard和Terence Shorter。 它以用户友好的方式将有关这些员工的信息打印到控制台,然后更新Terence Shorter及其员工的部门信息。 最后,它调用DMS的update()方法,将更新的数据图传递给Terence Shorter。
请注意,出于演示目的,我们未实现数据源组件。 相反,DMS对如何基于查询构建数据图具有“硬编码”知识。 图3显示了DMS使用的员工层次结构。
如您所见,DMS背后的虚拟公司有四名员工。 公司层次结构如下:
大老板没有经理,而特伦斯·肖特(Terence Shorter)是他的直接报告。 特伦斯·肖特(Terence Shorter)由大老板(Big Boss)担任经理,约翰·达特恩(John Datrane)和迈尔斯·科维斯(Miles Colvis)作为直接下属。 约翰·达特兰(John Datrane)由特伦斯·肖特(Terence Shorter)担任经理,没有直接报告。 迈尔斯·科维斯(Miles Colvis)的特伦斯·肖特(Terence Shorter)担任经理,没有直接报告。要运行示例应用程序,请右键单击SDOClient.java,然后选择“运行”>“ Java应用程序” 。 您应该在控制台上看到类似于清单2的内容。
现在,让我们看看每个应用程序组件的工作方式。
SDO客户端实例化DMS并从中获取各个员工的数据图。 获取数据图后,它将通过根对象(使用SDO的动态API)导航和访问数据对象,如下所示:
// Get the SDO DataGraph from the DMS. DataGraph employeeGraph = mediator.get(employeeName); ... // Get the root object DataObject root = employeeGraph.getRootObject(); ... // get the employee under the manager employee = theManager.getDataObject("employees.0");然后,客户端调用动态SDO访问器API,以从数据对象中获取信息并将其打印到控制台,如下所示:
System.out.println("Name: " + employee.getString("name")); System.out.println ("Number: " + employee.getInt("number")); ... System.out.println ("Is manager?: " + (employee.getBoolean("manager") ? "yes" : "no") + "\n");我们已经看到了客户如何获取信息(阅读),但是如何写作呢? 更具体地说,客户端如何修改对象? 为了更新数据对象,SDO客户端通常使用DataObject写访问器方法。 例如,在这里我们可以看到客户端如何修改为雇员Terence Shorter获得的数据图:
employee.setString("department", newDepartmentName);请注意,客户端不会调用日志记录方法。 DMS通过在数据图的更改摘要上调用beginLogging()和endLogging()来进行日志记录。
数据图的数据格式(模型)可以视为DMS与客户端之间的合同。 这是客户对DMS的期望,也是DMS知道如何构建(以及从中读取以更新后端数据源)的知识。 如果您熟悉XML或Web服务,则可以将数据图模型视为定义数据对象的XML模式(XSD)。 数据图本身将类似于XML实例文档。 实际上,XML Schema是可以定义SDO模型的方法之一。
请注意,数据图及其模型始终可序列化为XML。 在SDOClient.java中,将debug变量设置为true ,您应该在运行时在控制台上看到结果数据图的序列化版本。 它看起来应该像清单3所示。
对于此示例,数据图由Employee数据对象(和变更摘要)组成。 Employee具有属性,例如姓名,编号,部门,职务,经理(该雇员的另一位雇员是该雇员的经理)和雇员(该雇员管理的其他雇员)。 在此示例中,当员工存在于硬编码数据源中时,DMS返回的数据图将始终采用员工经理(如果有),所请求的员工以及他/她的直接员工(如果存在)的形式任何)。
SDO 1.0未指定DMS API,该API包括数据图模型本身的设计和创建。 设计数据图可能是另一篇文章的主题,因为在建立对数据源的访问时要考虑许多场景。
对于此示例,我们将使用DMS使用动态EMF API定义的员工模型。 示例数据图没有模型文档,例如XSD。 数据对象已经动态生成的事实意味着尚未生成Employee Java类。 如果使用静态方法,则相反。
DMS使用各种数据访问API(JDBC,SQL等)从各种数据源获取信息。 但是,一旦从后端检索到信息(此示例仅具有硬编码的知识),DMS就会使用EMF API(eGet,eSet)而非SDO来构建数据对象的数据图。 这种方法可产生最佳性能,但具有无法在SDO实现中移植的缺点。
如果性能不是主要问题,则可以使用SDO API来实现相同的DMS设计。 在这种情况下,DMS类中的缓存元对象( employeeClass , employeeNameFeature等)将是commonj.sdo.Type和commonj.sdo.Property类型,而不是EMF类型EClass , EAttribute和EReference 。 此外,如果根本不考虑性能,则可以使用方便的基于String的SDO API(例如setBoolean(String path, boolean value) ),从而无需缓存元对象。 不幸的是,虽然更方便,但该解决方案的运行速度要慢得多。
下面的代码片段显示了SimpleEmployeeDataMediatorImpl.java中如何定义Employee模型。 这不是构建SDO对象的代码。 这只是SDO对象的模型:
protected EClass employeeClass; protected EAttribute employeeNameFeature; protected EReference employeeEmployeesFeature; ... employeeClass = ecoreFactory.createEClass(); employeeClass.setName("Employee"); EAttribute employeeNameFeature = ecoreFactory.createEAttribute(); ... // employees (that the employee manages) employeeEmployeesFeature = ecoreFactory.createEReference(); employeeEmployeesFeature.setContainment(true); ... EPackage employeePackage = ecoreFactory.createEPackage(); employeePackage.getEClassifiers().add(employeeClass); ...请注意,我们在employees EReference上调用值为true setContainment ,以便每个员工都将“包含”其雇员。 如果我们不这样做,则嵌套的雇员将不在数据图中(即包含在其中),并且变更摘要将不包括对雇员的修改,而不是图的根对象。
此时,您可能在想:“有趣,但这会给我EMF对象,而不是SDO数据对象。这有什么窍门?” 好吧,这很简单。 Employee EClass属于employeePackage EPackage ,它具有以下调用:
// Have the factory for this package build SDO Objects employeePackage.setEFactoryInstance( new DynamicEDataObjectImpl.FactoryImpl());在运行时,工厂将创建类型为DynamicEDataObjectImpl对象,该对象实现DataObject接口(即SDO数据对象),而不是默认的DynamicEObjectImpl ,后者仅创建普通的EMF对象。 这突出显示了SDO和EMF对象之间的关系:SDO对象只是简单的EMF对象,它们也实现了SDO DataObject接口。 实际上,这些附加方法的实现是通过委派给核心EMF方法来实现的。
现在我们有了数据对象的模型,我们可以构建Employee实例并在其上设置各种属性。 如前所述,我们将使用EMF API来最大化性能。
EObject eObject = EcoreUtil.create(employeeClass); // Note: we could cast the object to DataObject, // but chose to use EObject APIs instead. eObject.eSet(employeeNameFeature, name); eObject.eSet(employeeNumberFeature, new Integer(number)); ... ...然后,我们可以使用“员工”参考将员工“链接”在一起,例如:
((List)bigBoss.eGet(employeeEmployeesFeature)).add(terence);创建数据对象后,我们需要将它们附加到数据图。 为此,我们调用数据图的setRootObject()方法,将要在根处的数据对象传递给它,在本例中为Employee The Boss 。
EDataGraph employeeGraph = SDOFactory.eINSTANCE.createEDataGraph(); ... ... employeeGraph.setERootObject(rootObject);返回数据图之前的最后一件事是开始记录更改。 如果要使用SDO的功能, beginLogging()在对数据图进行更改之前,应在其更改摘要上调用beginLogging() 。 这样做基本上是在清除所有先前的更改后开始侦听更改。
// Call beginLogging() so that the Change Summary is // populated when changes are applied to the Data Graph. // The DMS should call beginLogging() and endLogging(), // not the client. employeeGraph.getChangeSummary().beginLogging();DMS的另一项任务(在EmployeeDataMediator接口中定义)是根据SDO客户端提供的数据图更新后端数据源。
要更新后端数据源,DMS应使用SDO的强大功能,尤其是其变更摘要。 有多种使用数据图更改摘要的方法。 在此示例中,我们查看了从变更摘要树引用的所有数据对象,并从那里获取新的数据对象。
在此示例中,没有后端更新发生。 实际上,后端更新将以这种方法进行。
当DMS从客户端取回数据图以进行后端更新时,DMS要做的第一件事是在数据图的更改摘要上调用endLogging() 。 这样做可以关闭更改记录,从而提供自beginLogging()以来(通常是从创建以来beginLogging()对数据图所做的修改的摘要。 它采用的格式允许DMS有效且增量地更新后端数据源。 变更摘要中的修改分为三种类型:
对象更改包含对数据图中数据对象的引用,这些对象的属性已被修改,以及已更改的属性和该属性的旧值。 DMS可以使用旧值来确保其间的后端数据未被其他人修改。 对象创建包含添加到数据图的数据对象。 这些对象表示需要添加到后端数据结构中的新数据。 对象删除包含从数据图中删除的数据对象。 这些对象表示需要从后端数据结构中删除的数据。注意,我们使用了标准的SDO API来检查数据图的更改。 但是,我们可以使用EMF ChangeDescription API(而不是SDO的ChangeSummary)。 在此示例中,更新简单属性的值对性能的影响不会很大。 对于其他情况,例如更改多个多重性时,使用EMF API可以大大提高性能。 例如,假设我们从几百名员工的列表中删除了一名员工。 在这种情况下,ChangeSummary仅提供对旧值(即几百名员工的旧列表)的访问。 另一方面,EMF的ChangeDescription界面还提供了更精确的信息,例如“按某个索引删除员工”,这将更加有用。
还要注意,在此示例中,更改摘要中仅对象更改,没有删除或添加。 如果您使用SDO实现并从数据图中删除对象,您会注意到objectToAttach类型的元素 。 这实际上是用于对象删除的EMF ChangeDescription的名称。 它们是已删除的数据对象,在回滚的情况下需要将其附加回图形,这是EMF所做的更改视图。 objectsToAttach == deleted objects , objectsToAttach == deleted objects 。
如果在示例应用程序中将debug变量设置为true ,它将启用以下调用,从而使您可以查看数据图的序列化版本。
((EDataGraph) dataGraph).getDataGraphResource().save(System.out, null);您还可以使用Eclipse调试环境。 例如,我们建议您在SDOClient.java的第110行中设置一个断点,并调试SDOClient (作为Java应用程序)。 然后,在debug透视图中,您可以看到内存中的数据图(在Variables下)及其数据对象(Boss,Terrence Shorter等),如图4所示。
这样,您还可以查看更改摘要,如图5所示。
上面的屏幕截图看起来很复杂,您现在可能看不到它们有用,但是在调试SDO应用程序并查找数据对象的内容并更改摘要时,您可能想回到它们。
在本文中,我们概述了SDO及其功能。 我们已经展示了使用一些SDO功能的示例应用程序。 请参阅Eclipse帮助系统下的SDO API文档以获取更多参考。 该规范仍在发展和增强。 例如,SDO 1.0专注于SDO客户端的角度,而未指定DMS API。 SDO目前正在通过JCP进行标准化,因此请注意声明。 由于SDO非常灵活,因此在设计SDO应用程序时将需要做出很多决定。 这些决定将影响可重用性和性能。 因此,在编写代码之前,您应该真正考虑应用程序数据的使用模式和特征。
翻译自: https://www.ibm.com/developerworks/java/library/j-sdo/index.html