My FAQ,最新最全的IT技术FAQ
最新100篇 | 推荐100篇 | 专题100篇 | 排行榜 | 搜索 | 在线API文档
首 页 | 程序开发 | 操作系统 | 软件应用 | 图形图象 | 网络应用 | 精文荟萃 | 教育认证 | 未整理篇 | 技术讨论
  当前位置: > IBM专区 > DB2 > Java 技术
使用通用 SQL 实体 EJB 包装器来简化 EJB 开发
作者:佚名 时间:2005-08-31 15:17 出处:互连网 责编:小渔
              摘要:使用通用 SQL 实体 EJB 包装器来简化 EJB 开发
Tony Lau
软件工程师, IBM Toronto Lab
2003 年 6 月
Enterprise JavaBeans(EJB)的设计旨在减少企业应用程序的开发时间,但是有可能因为滥用 EJB 而导致最终开发出来的应用程序难以维护而且性能低下。本文描述一种简化自下而上 EJB 开发的编码技术,该技术允许端到端代码生成。本文提供的示例都基于 IBM DB2 通用数据库 和 WebSphere Studio。

重要:在阅读本文之前请先阅读 免责声明。

引言
Enterprise JavaBeans™(EJB)的设计旨在帮助显著地减少企业应用程序的开发时间。让我们做一个简短的回顾,有两种 EJB:提供业务逻辑的 会话 bean和封装业务数据的 实体 bean。会话 bean 类似于数据库存储过程,实体 bean 类似于数据库结果集。EJB 为远程访问、可扩展性、安全性和事务提供内置功能部件。这些内置功能部件是需要开销的。一个只执行一个 SQL 语句的简单存储过程是一个小的源文件。执行相同任务的简单会话 EJB 需要 10 个以上的类文件。很容易因为滥用 EJB 而导致最终开发出来的应用程序难以维护而且性能低下。

Java™ 2 Enterprise Edition(J2EE)使用实体 EJB 来为底层的关系数据库系统提供抽象层。然而,实体 EJB 的接口使数据库应用程序的只使用 SQL 语句的进程变得复杂。特别是在自下而上开发项目和旧的数据库开发中,设计是面向数据库的,也就是说架构师应该从 SQL 调用的角度,而不是从 EJB 工厂和对象的角度来考虑问题。

我提出了一个使实体 EJB 的使用变得通用的设计模式来简化 EJB 开发。该设计模式保留了直观的 SQL 接口,但提供了 EJB 的可重用性和内置分布式事务支持。它可以处理许多常见情况。因为比较通用,所以它可以用于自动代码生成。给定一个数据库表,自下而上方案可以生成从容器管理的持久性(Container-Managed Persistence,CMP)实体 bean、会话 bean 直到 Java Server Page(JSP)甚至 Web 服务的代码。

通用实体 EJB 包装器
通用 SQL 实体 EJB 包装器是助手类,它操作实体 EJB 工厂和接口以达到通过提供直观的 SQL 接口来执行某些 SQL 语句的目标。

图 1说明典型的 EJB 外观(facade)模式。EJB 会话外观模式提供了到服务器端组件的稳定的高级网关。模型层包括关系数据库和一系列实体 EJB。每个实体 bean 是一行数据的抽象;关系数据库负责持久存储。业务层包括会话 EJB,这个会话 EJB 是对实体 bean 的客户机。会话 bean 中定义的任何方法都是事务。会话 bean 中的方法是您实现业务逻辑的地方。

图 1. EJB 编程模型
EJB 编程模型

实体 bean 中的函数是通过 bean 的 homeremote接口被调用的,它涉及到对以下包的广泛使用:


            javax.ejb.*
            java.rmi.*
            javax.naming.*
            

实体 bean 的 home接口允许客户机创建新的实体 bean 实例、查找现有的实体 bean 并除去实体 bean 实例。实体 bean 的 remote接口允许客户机调用 bean 的业务方法,例如 setter 和 getter。所有这些都由容器转换为 JDBC 调用。要确保 EJB 的正确使用并确保有效生成的 SQL 语句,您可能需要参考 IBM Developer Domain 中的最佳实践文章 [ 参考资料 1]。

图 2显示了通用 SQL EJB 包装器是如何简化这些任务的。有了助手类,数据库开发变得更直观和更通用。该类主要是进行函数的相互映射。EJB 容器将 SQL JDBC 调用映射到实体 bean 中的复杂的方法矩阵,SQL EJB 助手类将方法映射回 SQL 包装器。

图 2. 使用 SQL EJB 包装器的 EJB 编程模型
使用 SQL EJB 包装器的 EJB 编程模型

在会话 bean 中的业务方法中,获取通用 SQL EJB 包装器的句柄,并调用由包装器提供的期望的 SQL 方法。SQL EJB 助手方法将操作实体 bean 接口以执行期望的 SQL 语句。而且,当外观 bean 和实体 bean 在同一台机器上时,SQL EJB 包装器还可以利用实体 bean 的本地接口。所有最佳实践都可以封装在包装器中。这些对于业务逻辑开发者都是透明的。

用法示例 1:应用程序开发
在本节中,我将把如何使用当前技术来开发一个单一事务和如何使用通用实体 bean 包装器来执行相同的任务进行比较。

当前技术
让我们看一下在 WebSphere® Studio 的 Application Developer 配置中开发一个单一事务所涉及的步骤:

  1. 使用 Enterprise Bean 向导来生成实体 bean。
  2. 使用 Enterprise Bean 向导来生成外观会话 bean。
  3. 实现 EJB 助手代码。
  4. 开发事务逻辑。

使用当前技术,Websphere Studio 可以方便地生成实体 bean 对象层和空的外观会话 bean,如上述步骤 1和 2 所示。WebSphere Studio 可以通过简单地浏览 DB2® Universal Database™ (UDB) JDBC 元数据支持来检索数据库模式。

步骤 3 是 EJB 助手代码的开发。 清单 1是从 WebSphere Performance Benchmark Sample [ 参考资料 2] 中抽取的助手代码。在这里,您作为开发者,将编写例程来处理 EJB 上下文、home 查找和高速缓存。这还将涉及复杂的异常处理和内联文档。

清单 1. EJB 助手代码

            public class TradeStaticContext
            {
            private static boolean verbose = true;
            static InitialContext _initContext = null;
            static QuoteHome _marketHome = null;
            public static QuoteHome getQuoteHome()
            {
            if (_initContext == null)
            { setContext(); }
            return _marketHome;
            }
            private static synchronized void setContext()
            {
            InitialContext localContext = null;
            //Trade 2.037 - Fixes startup race condition -
            //if _initContext has been set, another thread got here first
            //and has already setup the EJB Homes
            if (_initContext != null)
            return;
            if (verbose)
            {
            System.out.println( "TradeStaticContext.setContext: Setting context" );
            }
            try
            {
            localContext = new InitialContext();
            }
            catch (Exception me)
            {
            System.out.println(
            "TradeStaticContext.setContext: Get InitialContext failed!!");
            me.printStackTrace();
            }
            try
            {
            Object obj = null;
            //?
            obj = localContext.lookup("java:comp/env/ejb/Quote");
            _marketHome =
            (QuoteHome) javax.rmi.PortableRemoteObject.narrow(obj, QuoteHome.class);
            // Trade 2.037: Set _initContext after all EJB Homes have been setup to avoid
            race condition
            _initContext = localContext;
            }
            catch (Exception me)
            {
            System.out.println("TradeStaticContext.setContext: Lookup Homes failed!");
            me.printStackTrace();
            _initContext = null;
            }
            //?
            if (_marketHome == null)
            {
            System.out.println(
            "TradeStaticContext.setContext: Exception creating QuoteHome");
            _initContext = null;
            }
            }
            }

步骤 4 是您实现事务逻辑的地方。事务的主体放在会话 bean 中。某些助手函数被调用以使编码更容易,如 清单 2所示。

清单 2. 会话 bean 中的事务逻辑

            public class TradeBean implements SessionBean
            {
            public QuoteObject getQuote(String symbol)
            throws RemoteException, TradeException
            {
            //?
            Quote quote = findQuote(symbol);
            }
            private Quote findQuote(String symbol)
            throws RemoteException, TradeException
            {
            //?
            QuoteHome qhome = TradeStaticContext.getQuoteHome();
            retval = qhome.findByPrimaryKey(new QuoteKey(symbol));
            }
            }
            

很显然,一个简单的事务(对底层数据库的一个单一的 SELECT 语句)在 EJB 编程模型中也是有意义的任务。

通用 EJB 包装器技术
让我们来看一下使用通用 SQL 实体 EJB 包装器来完成相同任务的步骤:

  1. 使用 Enterprise Bean 向导来生成实体 bean。
  2. 开发(或生成)通用 SQL 实体 EJB 包装器代码。
  3. 使用 Enterprise Bean 向导来生成外观会话 bean。
  4. 填写外观会话 bean 中的业务逻辑。

类似的,Websphere Studio 可以方便地生成实体 bean 对象层和空的外观会话 bean,如上述步骤 1 和 3 所示。WebSphere Studio 可以通过简单地浏览 DB2 JDBC 元数据支持来检索数据库模式。

基于我们所学的最佳实践和课程,我们知道可以通过开发 Eclipse 插件来完成步骤 2 ,该插件生成通用 SQL 实体 EJB 包装器代码。(样本代码将在下一篇文章中提供。)这可以显著地提高生产率。通过使用通用 SQL 实体 EJB 包装器,仅需一些鼠标点击,您就可以生成为 Universal Test Client(UTC)准备的 J2EE 应用程序,这其中包括对任何表 Select、Insert、Delete 和 Update 缺省方法。开发者现在可以定制代码并定义他们自己的业务方法。

在步骤 4 中,业务事务可以放在外观会话 bean 的主体中。在 清单 3中,添加了 getQuote 事务。

清单 3. 将 getQuote 事务添加至外观会话 bean

            public class RegistryFacadeBean implements SessionBean
            {
            // Add user defined business methods here
            public QuoteData getQuote(String symbol)
            {
            QuoteWrapper wQuote = new QuoteCMPWrapper();
            Vector vQuote = wQuote.
            Select( symbol, null, null );
            if( vQuote.size() == 0 )
            return null;
            else
            return ( QuoteData ) vQuote.get( 0 );
            }
            }
            

当自下而上工作时,getQuote 更自然地作为 SELECT 实现。要这么做,只需获取生成的模型助手(即实体 bean 包装器)的句柄,并调用 Select() 方法。

在本实现中,Select() 方法不仅仅是一个包装器。它还是一个 SELECT 模板,支持各种可能的“ = ”布尔谓词组合。例如,语句: Select( symbol, null, null )

将变换为以下 EJBQL:


            Select object(q) from Quote q where q.symbol=?l
            

如果您将它更改为:


            Select( symbol, price, null )
            

那么执行以下语句:


            Select object(q) from Quote q where q.symbol=?l and q.price=?2
            

以此类推。

IBM® AccessBean 还避免了客户机程序的管理企业 bean 生命周期的复杂性。查看 AccessBeans 和 SQL 包装器接口如何一起工作以简化自下而上 EJB 开发是很有趣的事情。

用法示例 2:生成 EJB Web 页
WebSphere Studio 有很出色的 Database Web Pages 向导。给定一个 SQL 语句,该向导可以生成能够通过 JDBC 访问数据库的 HTML 和 JSP 代码。如果您想在 EJB(而不是 JDBC)中这么做,您可能需要利用通用实体 EJB 包装器。本节描述使用通用 SQL 实体 EJB 包装器如何将端到端 EJB Web 页生成为样本代码。

样本数据库
这是自下而上代码生成,数据库是起点。下面的示例说明如何使用通用 SQL 实体 EJB 包装器从 DB2 数据库表开始生成端到端自下而上代码。让我们将以下表定义作为代码生成示例的起点。


            Column                         Type      Type
            name                           schema    name
            ------------------------------ --------- ------------
            USERID                         SYSIBM    VARCHAR
            PASSWORD                       SYSIBM    VARCHAR
            STATUS                         SYSIBM    INTEGER
            

实体 bean
使用 Enterprise Bean 向导来生成上面显示的表的实体 bean。实体 bean 是一行数据的抽象。表列被映射到容器管理的字段。

清单 4. 生成实体 bean

            public class RegistryBean implements EntityBean
            {
            // Container managed fields
            public java.lang.String userid;
            public java.lang.String password;
            public java.lang.Integer status;
            //...
            }
            

通用 SQL 实体 EJB 包装器
SQL 实体 EJB 包装器由我们在 用法示例 1中提到的 Eclipse 插件为实体 bean 生成。包装器实现 Select、Insert、Delete 和 Update 方法接口。

清单 5. SQL 实体 EJB

            public interface RegistryWrapper
            {
            public Vector Select( RegistryData dRegistry );
            public Vector SelectForUpdate( RegistryData dRegistry );
            public void Insert( RegistryData dRegistry );
            public void Delete();
            public void Update( RegistryData dRegistry );
            }
            

RegistryData 类是数据类。在我们的例子中,它用作 SQL 谓词的占位符以及结果集的占位符,该结果集从会话 bean 和实体 bean 传入/传出。

外观会话 bean
Enterprise Bean 向导可以为实体 bean 生成外观会话 bean。会话 bean 是实现业务方法的地方。在缺省情况下,生成 Select、Insert、Delete 和 Update 业务方法。实体 bean 包装器在关系数据库业务方法中被调用。

清单 6. 实体 bean 的外观 bean

            public class RegistryFacadeBean implements SessionBean
            {
            public Vector Select( RegistryData dRegistry ) {
            RegistryWrapper wRegistry
            = new RegistryCMPWrapper();
            return wRegistry.Select( dRegistry );
            }
            public Vector SelectForUpdate( RegistryData dRegistry )
            { /*...*/ }
            public void Insert( RegistryData dRegistry ) { /*...*/ }
            public void Delete() { /*...*/ }
            public void Update( RegistryData dRegistry ) { /*...*/ }
            // Add user defined business methods here
            }
            

在 清单 6的示例中,生成的 Select() 业务方法是通过简单地调用 Registry 实体 bean 包装器中的 Select() 方法来完成的。Insert、Delete 和 Update 业务方法的实现是类似的。

客户机
然后,外观会话 bean 中的 SQL 方法可以提交到控制器层和/或表示层以用于测试目的。这并不局限于 servlet 和 JSP。没有任何限制阻止方法提交到诸如 J2EE 应用程序、Java Applet 和 Web 服务之类的客户机。

结束语
通用 SQL 实体 EJB 包装器不仅简化自下而上 EJB 数据库开发,而且在许多情况下允许端到端代码生成。如示例中所示,通用提供了使用生成代码的机会,而且封装显著地提高了自下而上 EJB 编程和旧的数据库开发的生产率。该实现可以由 WebSphere 和 DB2 UDB 使用。下一篇文章将详细讨论设计模式的实际使用。

免责声明
本文包含样本代码。IBM 授予您(“被许可方”)使用这个样本代码的非专有的、版权免费的许可证。然而,样本代码是以“按现状”的基础提供的,不附有任何形式的(不论是明示的,还是默示的)保证,包括对适销性、适用于某特定用途或非侵权性的默示保证。IBM 及其许可方不对被许可方使用该软件所导致的任何损失负责。任何情况下,无论损失是如何发生的,也不管责任条款怎样,IBM 或其许可方都不对由使用该软件或不能使用该软件所引起的收入的减少、利润的损失或数据的丢失,或者直接的、间接的、特殊的、由此产生的、附带的损失或惩罚性的损失赔偿负责,即使 IBM 已经被明确告知此类损害的可能性,也是如此。

IBM 公司可能已拥有或正在申请与本文档内容有关的各项专利。提供本文档并未授予用户使用这些专利的任何许可证。

致谢
作者对 Peter Shum、Matthew Huras 和 Grant Hutchison 的参与及所给予的宝贵的建议表示感谢。

参考资料

  • [1] Harvey W. Gunther 著 WebSphere Application Server: Best Practices for Performance and Scalability( http://www-3.ibm.com/software/webservers/appserv/ws_bestpractices.pdf)
  • [2] Tony Lau 和 Yongli An 著 Getting Started with WebSphere Performance Benchmark Sample( http://www7b.software.ibm.com/dmdd/library/techarticle/0205an/0205an.html)

 

附录 A

附录 A — 带有内联文档的 Registry bean 的生成 SQL EJB 包装器接口


            /**
            * The Select() method maps to the Registry EJB finder method. Initial
            * context is obtained and Home interface is looked up if necessary. The
            * parameters are used as the predicates in the where clause. Any reference to
            * null will be taken away from the clause. For example, a call to
            *
            * 	Select( null, null, 1 )
            *
            * translates to the SQL statement
            *
            * 	select * from Registry where status = 1
            */
            public Vector Select(java.lang.String userid, java.lang.String password, java.lang.Integer status);
            /**
            * Same as above except parameters are passed in form of RegistryData
            * wrapper.
            */
            public Vector Select(RegistryData data);
            /**
            * The Insert() method maps to the Registry EJB create method. Initial
            * context is obtained and Home interface is looked up if necessary. The
            * parameters are the data to be inserted to the underlying table.
            */
            public void Insert(java.lang.String userid, java.lang.String password, java.lang.Integer status)
            throws javax.ejb.CreateException, java.rmi.RemoteException;
            /**
            * Same as above except parameters are passed in form of RegistryData
            * wrapper.
            */
            public void Insert(RegistryData data)
            throws javax.ejb.CreateException, java.rmi.RemoteException;
            /**
            * The Delete() method maps to the Registry EJB remove method. Initial
            * context is obtained and Home interface is looked up if necessary. The
            * Select() method must be called beforehand with resultset not null
            */
            public void Delete();
            /**
            * The Update() method maps to the Registry EJB setter methods. Initial
            * context is obtained and Home/Remote interface is looked up if necessary. The
            * parameters contain the updated data for the current row(s). The
            * Select() method must be called beforehand with resultset not null
            */
            public void Update(java.lang.String userid, java.lang.String password, java.lang.Integer status);
            /**
            * Same as above except parameters are passed in form of RegistryData
            * wrapper.
            */
            public void Update(RegistryData data);
            
关于作者
Tony Lau是 IBM 认证的系统专家。他有滑铁卢大学(University of Waterloo)计算机工程(computer engineering)专业的应用科学(Applied Science)学士学位。他是滑铁卢大学大学校友会中的一个活跃的成员。他当前的研究重点是 WebSphere 和电子商务应用程序的 DB2 性能。您可以通过 tktlau@ca.ibm.com 和他联系。
 
首页 | 投资与合作 | 服务条款 | 隐私政策 | 收藏本站 | 设为首页 | 新用户注册 | 免责声明 | 使用帮助
Copyright ©2005-2008 myfaq.com.cn All rights reserved. www.myfaq.com.cn 版权所有