开发人员:Java
使用 Oracle Berkeley DB Java 版作为 Google Web 工具包的持久性管理器作者:Erick Audet 和 Gregory Burd
使用 Google Web 工具包构建 Web 应用程序时,利用 Berkeley DB Java 版作为持久性数据存储可以显著降低项目成本。2008 年 2 月发表 对于 Web 应用程序中的对象持久性,标准的 Java 平台企业版 (Java EE) 方法是使用 Enterprise JavaBeans (EJB),即一种对象关系映射 (ORM) 技术。Java 对象可以与 SQL 语句互相转换并存储在关系数据库管理系统 (RDBMS) 中,因为行可以跨多个数据库表相互关联。这是最常用的合理方法。大多数客户会发现,组织的许多方面都依赖 Oracle 的 SQL 和 RDBMS 服务器来管理业务关键信息。在这些情况下,EJB 和 Java 持久性 API (JPA) 是用于对象持久性的最佳方法。它们将 Java 应用程序和现有应用程序结合在一起,使用相同的 SQL 语言,访问相同的关系数据。 有趣的是,某些 EJB 应用程序在关系数据库中存储数据的原因只是因为其设计模式常见且熟悉,而不是因为有多个应用程序由于不同的原因要使用 SQL 访问同一数据。如果数据不在现有的关系数据库中,并且不打算通过除 ORM 层以外的其他方式访问,则 ORM 层会占用很大开销。在某些特定情况下,必须使用其他运行即席 SQL 查询的基于 SQL 的解决方案访问数据,但我们的应用程序不需要。从客观上看,如果 ORM 设计模式是系统使用 SQL 与 RDBMS 进行交互的唯一方面,那么该模式就会增加许多不必要的开销。相反,Oracle Berkeley DB Java 版的直接持久层 (DPL) 可将对象数据直接存储到 B 树数据库中,而无需将其转译为某种中间语言。 在本文中,您将探究如何在实际运用中结合使用 Berkeley DB Java 版与 Google Web 工具包,以便为对象持久性创建高效解决方案。 Oracle Berkeley DB Java 版Oracle Berkeley DB Java 版是 Oracle 在 2006 年 2 月收购 Sleepycat Software 时获得的三个产品之一。它是一个纯 Java 数据库引擎,用于将数据存储到本地文件系统中。它可以提供原子性、一致性、隔离性和可持久性 (ACID) 事务,允许高级别的并发,可以扩展到 TB 级数据,并且可以使用 Java 虚拟机内存的可预测部分作为已存储数据的缓存。Berkeley DB Java 版是获得极大成功的 Berkeley DB 产品的再实施,它以 ANSI C 语言编写,但这两个产品具有重大差异。最显著的一个差异就是 Berkeley DB Java 版提供的 DPL API。该层提供了一组类似 JPA 的 Java 批注,允许轻松存储、索引、更新和删除对象图形,而无需将对象数据与 SQL 互相转换。在使用 Berkeley DB Java 版 DPL 时,无需 ORM 组件或后端数据库,并且没有客户端/服务器连接。DPL 将以非常直观和强大的方式存储对象图形、关系和索引。DPL 批注的名称和概念与 JPA 类似。DPL 可以存储任何传统 Java 对象 (POJO),并在类本身内保留所有持久性规则和配置。与 ORM 解决方案相比,这是 Berkeley DB Java 版 DPL 的第一个关键差异和优势;不需要维护外部配置文件。这一方法大大提高了开发人员的效率,降低了项目复杂性,并消除了开销。 使用基于 EJB 的解决方案开发的应用程序通常需要一个配置文件,以便将每个类映射到物理数据库表。尽管诸如 Hibernate 之类的持久性也使用 Java 批注,但实际上,您至少需要了解并控制 Hibernate.config.xml 文件,以管理允许应用程序通过 Java DataBase Connectivity (JDBC) 连接到关系数据库服务器的连接字符串和其他设置。 尽管具有出色的集成开发环境 (IDE) 集成、配置文件管理和对 EJB 样式的解决方案的其他支持,开发人员最终仍将不可避免地终止手动编辑(一个容易出错且耗时的过程)。相反,Berkeley DB Java 版使用 Java 批注将这些设置封装到持久性 POJO 类中,这一过程十分独特、简单并且不容易出错。DPL 批注将在一个显著、直观的位置上保留存储上下文:即,源代码,开发人员预期能在其中找到该上下文。 Google Web 工具包Google Web 工具包 (GWT) 是一个模型视图控制器 (MVC) 框架,它抽象了以 Java 代码编写 HTML、JavaScript 以及相关异步 JavaScript 和 XML (Ajax) 消息的过程,然后由 GWT 处理以部署到 Java EE 应用服务器中。手动编写 HTML、JavaScript 以及将 HTML 页面中触发的 JavaScript 操作(客户端代码在用户的浏览器中运行)绑定到 Java Enterprise 应用服务器中执行的控制层(数据库所在的服务器端)的其他元素非常复杂,而它消除了这种复杂性。由于在良好的 IDE 中开发,GWT 可以提供超越其他框架的不可估量的优势,并显著降低项目的成本和复杂性。 GWT 还提供了一个调试环境,称为托管模式。这使得开发人员只需以熟悉的调试器运作方式逐行检查 Java 源代码来实时调试客户端代码(JavaScript 和 HTML)。GWT 将实时处理 JavaScript/HTML 代码与 Java 代码之间生成的映射,从而显著缩短复杂 Web 应用程序的开发时间。 应用程序的存储方面也可以在托管模式下快速调试。如果支持工具消除了配置开销和不必要的复杂性,开发人员就可以将注意力集中于应用程序。 花生酱和巧克力:GWT 和 BBD Java 版 DPL将 GWT 和 Berkeley DB Java 版 DPL 结合在一起非常简单。GWT 控制器框架很简单并且易于自定义。下面的序列图展示了 GWT 远程过程调用 (RPC) 机制与 Berkeley DB Java 版之间的典型调用流。小部件(或 com.google.gwt.user.client.ui 程序包中的任何其他 GWT 用户界面客户端类)中包含用户界面 (UI) 控件,如按钮或列表框。这些控件用于处理在 Web 浏览器中浏览页面的用户触发的事件。每个控件都有一个可以处理的屏幕事件定义。可以指示屏幕事件(如“OnClick”)使用 GWT RemoteService 接口调用服务器端类上的代码。在调用 RemoteService 之前,小部件必须通过其域创建一个数据传输对象(DTO,一个常见的 Java EE 设计模式)。然后,创建一个 AsynCallBack (Save DTO) 方法,并将其传递给 RemoteService。该服务器端类将使用 DTO 创建一个模型对象 POJO 以表示从浏览器传送到服务器的数据;随后,Berkeley DB Java 版将通过 BusinessService 对象进行调用来处理该模型对象。DataAccessor 随后由业务服务对象使用,用于管理 Oracle Berkeley DB Java 版数据库内各种概念对象的事务存储。 演示细节和代码本部分将通过展示几个关键的源代码示例,来详细描述如何实施建议的体系结构( 下载 zip)。要完全了解该结构,阅读器必须在一定程度上支持 Ajax、Java 批注和面向对象的设计。 数据模型我们将使用两个概念模型类来演示该示例:一个名为 Person 的通用类和 Person 的子类 User。这些对象及其彼此的关系可以通过 DPL 批注轻松表达。然后,Berkeley DB Java 版将使用事务的数据存储到本地数据库文件中。 package ... import com.sleepycat.persist.model.Persistent; import com.sleepycat.persist.model.PrimaryKey; @Persistent public class Person { @PrimaryKey private String userName; private String lastName; private String firstName; ... } User 类 package ... import static com.sleepycat.persist.model.Relationship.ONE_TO_MANY; import java.util.ArrayList; import com.sleepycat.persist.model.Entity; @Entity public class User extends Person { private String injury; ...} GWT UI、Callback 和 DTO我们的示例使用了一个非常简单的概念,即,针对现有用户(或病人)设置名义上的信息和伤病。它展示了如何在 Berkeley DB Java 版 DPL 数据库中使用继承概念。下图是一个简单的小部件,用于记录用户的伤病报告。 以下是该窗口的实际代码: public class Profile extends Composite { private TextBox injury; private TextBox userNameTextBox; private ListBox typesOfInjuries; public Profile() { ... final Button saveButton = new Button(); absolutePanel.add(saveButton, 13, 6); saveButton.addClickListener(new ClickListener() { public void onClick(final Widget sender) { saveProfile(); } }); saveButton.setText("Save"); ... protected void saveProfile() { AsyncCallback callback = new AsyncCallback() { public void onSuccess(Object result) { Window.confirm("Profile Saved"); } public void onFailure(Throwable ex) { Window.confirm(ex.getMessage()); } }; UserDTO wUserDTO = new UserDTO(); wUserDTO.setUserName( userNameTextBox.getText() ); wUserDTO.setInjury( injury.getText() ); ProfileService.Util.getInstance().save( wUserDTO, callback); }正如您在示例代码中看到的那样,SaveButton 事件“OnClick”将调用 SaveProfile() 方法。该方法使用简单的回调块处理从服务器端类方法 UserService.createUser() 返回的对象。UserDTO(用于将数据从服务器端运送到客户端的 POJO)仅包含基本 Java 数据类型,从而允许其成为可序列化类。 JavaScript 仅使用基本数据类型。它们必须经过序列化,才能传送到客户端 Web 浏览器和远程应用服务器;因此,该限制成为所有 GWT DTO 的要求。如果您仔细查看我们的实体类,将发现它们没有任何客户端应用程序的概念,也不必实施 GWT 接口。它们完全独立于客户端应用程序。要实现这种独立,需要使用 Java EE DTO 设计模式。这些 DTO 仅包含基本数据类型,并且由两个程序包(Services 和 GWT Remote Services)使用。DTO 必须实施 GWT IsSerializable 接口,并且还必须创建 GWT 配置 XML 文件。使用 DTO 的另一个重要原因是,必须将 GWT 客户端类转换为 JavaScript 代码。为此,GWT 使用了一个类似 Java 的汇编程序,该汇编程序不支持完整的 Java 平台标准版 API,也不支持如批注和类型化数组等最新特性。这是 GWT 的一个较小的设计限制,一般不会影响大部分编程任务。 边注:
远程服务、业务服务和访问器此时,概念实体已经创建,并使用 Berkeley DB Java 版 DPL 批注正确标注。基本服务方法将管理这些实体的事务状态。下一步是创建 GWT 远程服务。它们将调用 Berkeley DB Java 版 DPL 代码来存储数据。远程服务是客户端浏览器与 Java EE 应用服务器通讯的标准方式。这是使用标准 Ajax(实质上是 HTTP POST)完成的,调用由 GWT 生成并管理。该 Ajax 调用的服务器端代码位于 GWT 应用程序的“server”程序包中。该代码在 servlet 容器或 Java EE 应用服务器中执行(因为我们在服务器端,该代码是 Java,而浏览器中运行的代码是 JavaScript),我们就在这里将浏览器的用户操作关联到业务逻辑,然后再关联到 Berkeley DB Java 版 DPL 以实现对象持久性。 save() 方法的 RemoteService 实施如下: ... public void createUser(UserDTO pUserDTO) { //transform the presentation layer object into a conceptual object User wUser = new User(); wUser.setUserName(pUserDTO.getUserName()); wUser.setInjury( pUserDTO.getInjury()); UserService userService = new UserService(); userService.createUser(wUser); } ...模型对象是在数据库中实施应用程序数据管理方面的业务数据类。EJB 程序员可以将 UserService 大致理解为无状态会话 bean。 BaseService 类本身不处理任何逻辑。该类在数据库的一个或多个对象上处理特定任务的事务状态。要将业务流程逻辑添加到服务,应用程序需要扩展该 BaseService 类。在示例中,UserService 类将扮演该角色,并通过提供诸如 createUser()、saveUser() 和 getUser() 之类的方法实施,来处理对 User 实体的所有访问。 该实施位于项目的服务器端程序包中。它将调用名为 UserService 的业务服务。正是 UserService 为 User 的特定实例实施了业务逻辑。在我们的示例中,此时需要将 UserDTO 对象转换为 User 对象(带有 DPL 批注的模型 POJO,将存储在 Oracle Berkeley DB Java 版数据库中)。请记住,数据库只是位于服务器文件系统上的一组文件,可通过 Web 应用程序 servlet 容器访问。 经检验可靠的设计模式指出,应用程序应该通过分层和抽象使 DTO 对象与业务对象相互分离。业务服务不必了解 DTO 类,它们只需处理概念模型对象。这些对象始终位于 Berkeley DB Java 版数据库中。业务服务类负责管理数据存储中的概念模型对象。联想到 EJB,UserService 就类似于无状态会话 bean。 以下是 UserService.createUser() 方法: ... public User createUser( User pUser ) throws DatabaseException, Exception{ UserAccessor wUserAccessor = new UserAccessor(); //check mandatory fields if ( pUser.getFirstName().equals("") || pUser.getLastName().equals("") || pUser.getUserName().equals("") ){ throw new Exception("Missing mandatory fields"); } open( false, wUserAccessor); // Start a transaction. startTransaction(); // Put it in the store. Note that this causes our secondary key // to be automatically updated for us. try { wUserAccessor.userByUserName.put( pUser ); // Commit the transaction. The data is now safely written to the // store. commit(); } catch (DatabaseException dbe) { try { System.out.println("Error creating a user"); } catch (Exception willNeverOccur) { } txn.abort(); throw dbe; } catch (Exception e) { txn.abort(); e.printStackTrace(); } return pUser; } ...该方法使用继承的方法 open() 和 startTransaction() 设置 Berkeley DB Java 版并用作数据库(即,实体存储),然后再进行事务处理。这种分离有助于防止开发人员在稍后过程中设置 Berkeley DB Java 版连接和事务时犯错误。UserAccessor 类负责为数据访问设置各种 Berkeley DB Java 版 DPL 访问器(实质上是索引)。该类封装了稍后将在代码中使用的索引,用于在 Berkeley DB Java 版数据库中创建、更新和删除对象。以下是 UserAccessor 类的内容: ... public class UserAccessor extends DefaultAccessor { public PrimaryIndex<String, User> userByUserName; public UserAccessor() {} public void init() throws DatabaseException{ userByUserName = entityStore.getPrimaryIndex( String.class, User.class ); } } ...通过设置这些访问器,可简化业务服务层并使其保持一致。创建和管理索引的任务被隔离在某个地方,不会干扰业务逻辑。以下是使用索引按名称检索 User 实例的示例。 wUser = wUserAccessor.userByUserName.get( pUser.getUserName() );另外,要检索用户对象而不使用这些访问器方法,代码应如下所示: PrimaryIndex<String, User> userByUserName; userByUserName = entityStore.getPrimaryIndex( String. class, User. class ); User wUser = userByUserName.get( <user name> );访问器方法的优势很明显:在第一个示例中,代码得到大大简化。在结合使用 GWT 和 Berkeley DB Java 版时,只需考虑设计模式。 评估 GWT 和 Berkeley DB Java 版 DPL 持久性解决方案与其他对象持久性解决方案一样,需要权衡各种因素。Berkeley DB Java 版在这方面也是一样的。 优点:
结论结合 GWT 与 Berkeley DB Java 版的主要优势就是简单性。这两个框架的设计都注重易于使用以及在应用程序开发的所有阶段消除常见的、通常需要考虑的开销。 Web 应用程序的复杂性有多种原因。同时支持当今 Web 浏览体验的各种技术令人眼花缭乱。在奠定 Web 应用程序基础时,开发人员必须从数十种设计模式与框架的不同组合中进行挑选。值得庆幸的是,在 Google 和 Oracle 等公司的帮助下,开发人员可以将 Web 应用程序开发的复杂性降低到可管理级别。在许多情况下,EJB 和 ORM 在 Web 应用程序的存储(或模型)方面十分有意义,但在我们的特定情况下,不需要使用外部 SQL 访问数据。ORM 层和 RDBMS 后端是我们不需要的开销。很高兴发现了一个管理数据存储的、与 RDBMS 一样强壮和快速的新方法,而在我们的示例中,它比其他许多方式更加适合。 Berkeley DB Java 版在持久层的选择上是一场真正的革命。它消除了 ORM 障碍并解决了许多问题,如:
诸如 GWT、JavaServer Faces 和 Echo2 等框架是 Web 2.0 编程范例的新增成员。它们基本上恢复了在 Web 时代之前出现的快速开发因素。这些工具使您能够创建专业的、可伸缩的 Web 应用程序。它们可提供不同的特性,但共同点是它们都能够抽象客户端语法的复杂性。它们通过在浏览器中生成并维护客户端来实现这一点。 GWT 和 Berkeley DB Java 版在实际开发中的工作方式是什么?与其他新兴的创新想法一样,人们会感到踌躇,并等待它们在一些主要实施中证明自己。Berkeley DB Java 版对于 Web 框架持久层是新事物,但它在过去五年中逐渐成熟起来,并在几个存储 TB 级数据的项目中执行了一些重要任务,目前您可能没有意识到,但已经在不知情的情况下使用并依赖它。Web 开发人员应认真考虑替换他们的 ORM 思维方式,而采用诸如 Berkeley DB Java 版之类的存储解决方案。如果 Oracle Berkeley DB Java 版可以为我们工作,那么它也可以为您工作。 Eric Audet(M.Sc.,TechSolCom)拥有 16 年的 IT 经验,主要担任软件和数据架构师。目前,他担任软件架构师,致力于使用 WSDL、Java EE 和 Java ME 技术开发事务性 SOA、无线和 Web 应用程序。 Gregory Burd 是 Oracle 的 Berkeley DB、Berkeley DB Java 版和 Berkeley DB XML 数据库产品的产品经理。他自 2003 年以来一直担任该职位,目前仍在 Oracle 的可嵌入数据库小组中致力于 Berkeley DB 产品。Greg 拥有多方面背景,包括软件工程、产品管理、企业软件咨询、软件联盟和销售,并且经常贡献开放源代码和免费软件项目。 |