第一章 前言........................................................................................................................................................................5 1.1 本教程适合人群........................................................................................................................................................5 1.2 联系作者....................................................................................................................................................................5 1.3 《EJB3.0实例教程》官方MSN群..........................................................................................................................5 第二章 运行环境配置........................................................................................................................................................5 2.1 下载与安装................................................................................................................................................................5 2.2 运行一个EJB3例子...............................................................................................................................................10 2.3 在独立的TOMCAT 中调用EJB..............................................................................................................................10 2.4 发布在JBOSS中的WEB应用调用EJB............................................................................................................... 11 第三章 基础知识学习...................................................................................................................................................... 11 3.1 熟悉JBOSS的目录结构.......................................................................................................................................... 11 3.2 JBOSS中的部署.......................................................................................................................................................12 3.3 如何进行EJB打包.................................................................................................................................................12 3.4 如何进行WEB应用打包.......................................................................................................................................13 3.5 使用了第三方类库的EJB如何打包......................................................................................................................14 3.6 共用了第三方类库的J2EE 项目如何打包.............................................................................................................15 3.7 如何恢复本书配套例子的开发环境.......................................................................................................................16 3.8 如何对EJB3进行调试...........................................................................................................................................21 第四章 会话BEAN(SESSION BEAN)...........................................................................................................................30 4.1 STATELESS SESSION BEANS(无状态BEAN)开发..................................................................................................30 4.1.1 开发只存在Remote 接口的无状态Session Bean ..........................................................................................30 4.1.2 开发只存在Local 接口的无状态Session Bean.............................................................................................33 4.1.3 开发存在Remote 与Local 接口的无状态Session Bean ...............................................................................34 4.2 STATEFUL SESSION BEANS(有状态BEAN)开发....................................................................................................37 4.3 STATELESS SESSION BEAN与STATEFUL SESSION BEAN的区别...............................................................................38 4.4 如何改变SESSION BEAN的JNDI 名称..................................................................................................................39 4.5 SESSION BEAN的生命周期......................................................................................................................................40 4.6 拦截器(INTERCEPTOR) .............................................................................................................................................43 4.7 依赖注入(DEPENDENCY INJECTION).........................................................................................................................46 4.8 定时服务(TIMER SERVICE) ......................................................................................................................................49 4.9 安全服务(SECURITY SERVICE) .................................................................................................................................51 4.9.1 自定义安全域.................................................................................................................................................56 第五章 消息驱动BEAN (MESSAGE DRIVEN BEAN) ...............................................................................................58 第六章 实体BEAN(ENTITY BEAN) .............................................................................................................................61 6.1 实体BEAN的组成文件PERSISTENCE.XML配置.....................................................................................................62 6.2 JBOSS数据源的配置................................................................................................................................................62 6.2.1 MySql 数据源的配置.......................................................................................................................................63 6.2.2 Ms Sql Server2000 数据源的配置....................................................................................................................63 6.3 实体BEAN发布前的准备工作................................................................................................................................64 6.4 单表映射的实体BEAN............................................................................................................................................64 Jboss EJB3.0实例教程 版权所有:黎活明 6.5 持久化实体管理器ENTITYMANAGER....................................................................................................................69 6.5.1 Entity 获取find()..............................................................................................................................................69 6.5.2 添加persist()...................................................................................................................................................69 6.5.3 更新Merge() ...................................................................................................................................................70 6.5.4 删除Remove() .................................................................................................................................................70 6.5.5 执行EJB3 QL 操作createQuery() ..................................................................................................................70 6.5.6 执行SQL 操作createNativeQuery() ...............................................................................................................71 6.6 关系/对象映射.........................................................................................................................................................71 6.6.1 映射的表名或列名与数据库保留字同名时的处理.......................................................................................71 6.6.2 一对多及多对一映射.....................................................................................................................................72 6.6.3 一对一映射.....................................................................................................................................................79 6.6.4 多对多映射.....................................................................................................................................................86 6.7 使用参数查询..........................................................................................................................................................92 6.7.1 命名参数查询.................................................................................................................................................92 6.7.2 位置参数查询.................................................................................................................................................92 6.8 EJB3 QL语言..........................................................................................................................................................93 6.8.1 大小写敏感性(Case Sensitivity) ....................................................................................................................104 6.8.2 排序(order by) ...............................................................................................................................................104 6.8.3 查询部分属性...............................................................................................................................................105 6.8.4 查询中使用构造器(Constructor)...................................................................................................................105 6.8.5 聚合查询(Aggregation).................................................................................................................................106 6.8.6 关联(join) ......................................................................................................................................................109 6.8.7 比较Entity..................................................................................................................................................... 112 6.8.8 批量更新(Batch Update)............................................................................................................................... 113 6.8.9 批量删除(Batch Remove).............................................................................................................................. 113 6.8.10 使用操作符NOT........................................................................................................................................ 114 6.8.11 使用操作符BETWEEN.............................................................................................................................. 114 6.8.12 使用操作符IN............................................................................................................................................ 115 6.8.13 使用操作符LIKE ....................................................................................................................................... 115 6.8.14 使用操作符IS NULL.................................................................................................................................. 116 6.8.15 使用操作符IS EMPTY............................................................................................................................... 117 6.8.16 使用操作符EXISTS.................................................................................................................................... 118 6.8.17 字符串函数................................................................................................................................................. 119 6.8.18 计算函数.....................................................................................................................................................120 6.8.19 子查询.........................................................................................................................................................121 6.9 调用存储过程........................................................................................................................................................122 6.9.1 调用无返回值的存储过程............................................................................................................................122 6.9.2 调用返回单值的存储过程............................................................................................................................122 6.9.3 调用返回表全部列的存储过程....................................................................................................................123 6.9.4 调用返回部分列的存储过程........................................................................................................................124 6.10 事务管理服务......................................................................................................................................................125 6.11 ENTITY的生命周期和状态..................................................................................................................................129 6.12 复合主键(COMPOSITE PRIMARY KEY)..................................................................................................................130 第七章 WEB服务(WEB SERVICE)............................................................................................................................137 Jboss EJB3.0实例教程 版权所有:黎活明 7.1 WEB SERVICE的创建.............................................................................................................................................137 7.2 WEB SERVICE的客户端调用.................................................................................................................................142 7.2.1 用java 语言调用Web Service.......................................................................................................................142 7.2.2 用asp 调用Web Service ...............................................................................................................................147 第八章 使用EJB3.0构建轻量级应用框架...................................................................................................................148 8.1 在WEB中使用EJB3.0 框架................................................................................................................................149 8.1.1 如何使用Session Bean.................................................................................................................................150 8.1.2 如何使用Message Driven Bean....................................................................................................................151 8.1.3 如何使用依赖注入(dependency injection) ....................................................................................................153 8.1.4 如何使用Entity Bean....................................................................................................................................153 Jboss EJB3.0实例教程 版权所有:黎活明 第一章 前言 期待已久的EJB3.0最终规范已经发布了。虽然EJB3.0最终规范出来了一段时间,但对EJB3.0的应用还停留在介 绍之中,应用实例更是少之又少,所以作者拟写本书,以简单的实例展现EJB3.0 的开发过程,希望对大家有所帮 助。 EJB3 最激动人心的是POJO 编程模型,我想对开发人员的影响将是非常大的,因为他降低了开发人员编写EJB 的 要求。EJB3 的bean类将更像常规的Java bean。不要求像过去那样实现特殊的回调界面或者扩展EJB类。所以它 将使EJB的开发更像常规的Java 开发。 作者对EJB3.0 接触的时间很短,所以一些新的概念和知识理解也难免有误,有些概念和语义把握的不是很准, 希望在这方面有经验和了解的朋友批评指正,欢迎多提意见。 因为JBOSS EJB3.0产品常未成熟,本教程随着新产品的推出将有所改动,请密切关注! 1.1 本教程适合人群 本教程适合具有Java 语言基础的EJB初学者。有读者来邮件问需不需要先学EJB2.x,作者明确地告诉你不用 学了。随着EJB3的发展,EJB2.x将会逐步成为历史。 1.2 联系作者 黎活明,广东佛山人,毕业于中国农业大学,一直从事于B/S系统架构工作,现任游易航空旅行网运营主管。 电子邮件:[email protected] 1.3 《EJB3.0实例教程》官方MSN群 MSN 群账号:[email protected] ,添加该账号到你的MSN 中即可加入群,在群中你可以和大家一起交流学 习的经验,技术的最新发展等。 第二章 运行环境配置 2.1 下载与安装 1>下载安装JDK5.0 http://java.sun.com/j2se/1.5.0/download.jsp 2>下载安装开发工具JBossIDE(内含Eclipse 3.2),直接解压缩即可完成安装。 http://prdownloads.sourceforge.net/jboss/JBossIDE-2.0.0.Beta1-Bundle-win32.zip?download 想使用中文的朋友可以下载中文语言包NLpack1-eclipse-SDK-3.2-win32.zip Jboss EJB3.0实例教程 版权所有:黎活明 下载路径: http://www.eclipse.org/downloads/download.php?file=/eclipse/downloads/drops/L-3.2_Language_Packs- 200607121700/NLpack1-eclipse-SDK-3.2-win32.zip 解压语言包,把features 及plugins 文件夹拷贝复盖JBossIDE 安装目录下的features 及plugins 文件夹。如 果汉化失败,可能是你安装语言包之前运行过eclipse,解决办法是:把eclipse 安装目录下的configuration 文件夹删除,从JBossIDE 安装包中解压出configuration文件夹,把configuration 文件夹拷贝到JBossIDE 安 装目录下。 3>下载和安装jboss-4.0.4.GA服务器 http://sourceforge.net/project/showfiles.php?group_id=22866&package_id=16942&release_id=416591 选择 jboss-4.0.4.GA-Patch1-installer.jar 文件下载。 安装方法: 双击jboss-4.0.4.GA-Patch1-installer.jar 文件,或者在DOS命令下执行: java -jar G:/soft/jboss-4.0.4.GA-Patch1-installer.jar,G:/soft为Jboss安装文件所在目录 安装界面如下: 这一步是让你选择安装语言,由于中文会出现乱码,所以请选择英文(eng)。以后点“Next”并同意许可协议,直 到选择安装目录(如下图)。 Jboss EJB3.0实例教程 版权所有:黎活明 在国外的技术论坛上,开发者建议不要安装在Program Files目录,否则一些应用会导致莫名的错误。这里大家就 安装在别的目录吧。如C:/JavaServer/jboss。跟着选择一个安装类型,本文选择带集群功能的安装选项 “ejb3-clustered”,如下图 Jboss EJB3.0实例教程 版权所有:黎活明 点下一步,直到出现下图.在Name 输入栏中输入 all 点下一步,直到出现下图: Jboss EJB3.0实例教程 版权所有:黎活明 四个选框都选上。点下一步就开始安装了。 安装完后请在“系统变量”里添加JBOSS_HOME 变量,值为Jboss的安装路径。如下图 Jboss EJB3.0实例教程 版权所有:黎活明 现在验证安装是否成功。在Dos 命令窗口下执行: C:/JavaServer/jboss/bin/run –c all 这个命令用作启动jboss 观察控制台有没有Java 的例外抛出。如果没有例外并看到下图,恭喜你,安装成功了。 你可以输入http://localhost:8080来到Jboss的欢迎主页。在JBoss Management 栏中点击”JMX Console”进入Jboss 的管理界面,这里需要你输入用户名及密码,如果你在安装的时候没有修改过默认值,那么用户名及密码都是 admin。 如果启动jboss出现例外,先看看jboss所用端口有没有被占(如1099,1098 ,8080, 8083 等端口)。可以下载端口 查看器(Active Ports)进行检查,如果端口被占用就关闭此进程。确定不是端口被占用,那很大可能是你的JDK 安装不正确。怎么排错,你自己看着办吧。 2.2 运行一个EJB3例子 服务安装成功了,得来一个真家伙试试。在源代码的HelloWorld 文件夹下(源代码下 载:http://www.foshanshop.net/),把HelloWorld.jar拷贝到“jboss安装目录/server/all/deploy/”目录下, jboss 支持热布署,HelloWorld 会被发现,并自动完成布署。接下来继续把EJBTest文件夹下的EJBTest.war拷 贝到“jboss 安装目录/server/all/deploy/”目录下。 在浏览器上输入:http://localhost:8080/EJBTest/Test.jsp。怎样?看见什么了。 2.3 在独立的Tomcat 中调用EJB 在正式的生产环境下,大部分调用EJB的客户端所在的服务器都为独立的Tomcat或Resin 。下面介绍如何在 独立的Tomcat服务器中调用Jboss中的EJB。在独立的Tomcat服务器中调用EJB需要有以下步骤: 1> 根据应用的需要,把调用EJB所依赖的Jar 包拷贝到tomcat下的/shared/lib目录或WEB应用的 WEB-INF/lib下,所依赖的Jar 一般在jboss安装目录的client,/server/all/deploy/jboss-aop-jdk50.deployer, Jboss EJB3.0实例教程 版权所有:黎活明 /server/all/deploy/ejb3.deployer,/lib/endorsed等文件夹下。 下面的jar 文件是必需的: [jboss安装目录]/client/jbossall-client.jar [jboss安装目录]/server/all/deploy/jboss-aop-jdk50.deployer/jboss-aop-jdk50.jar [jboss安装目录]/server/all/deploy/jboss-aop-jdk50.deployer/jboss-aspect-library-jdk50.jar [jboss安装目录]/server/all/deploy/ejb3.deployer/jboss-ejb3.jar [jboss安装目录]/client/jboss-remoting.jar 2> 把调用的EJB接口拷贝到应用的/WEB-INF/classes/目录下 注意:在此环境下不能调用EJB的Local 接口,因为他与JBOSS不在同一个VM中。 2.4 发布在JBOSS 中的WEB应用调用EJB 有些调用EJB 的WEB 应用是直接发布在Jboss 集成环境下,本教程的客户端调用例子就是发布在Jboss 中。在 Jboss下发布WEB应用,需要把WEB应用打包成war 文件。另外在此环境下调用EJB不需要把EJB的接口类放 入/WEB-INF/classes/目录中,否则调用Stateful Bean 就会发生类型冲突,引发下面的例外。 java.lang.ClassCastException: $Proxy84 org.apache.jsp.StatefulBeanTest_jsp._jspService(org.apache.jsp.StatefulBeanTest_jsp:55) 注意:在此环境下,EJB的Local或Remote接口都可以被调用。 第三章 基础知识学习 3.1 熟悉JBoss的目录结构 安装JBoss会创建下列目录结构: 目录 描述 bin 启动和关闭JBoss的脚本 client 客户端与JBoss通信所需的Java 库(JARs) docs 配置的样本文件(数据库配置等) docs/dtd 在JBoss中使用的各种XML文件的DTD。 lib 一些JAR,JBoss启动时加载,且被所有JBoss配置共享。(不要把你的库放在这里) server 各种JBoss配置。每个配置必须放在不同的子目录。子目录的名字表示配置的名字。JBoss 包含3 个默认的配置:minimial,default和all,在你安装时可以进行选择。 server/all JBoss的完全配置,启动所有服务,包括集群和IIOP。(本教程就采用此配置) server/default JBoss 的默认配置。在没有在JBoss 命令航中指定配置名称时使用。(本教程没有安装此 配置,如果不指定配置名称,启动将会出错) server/all/conf JBoss的配置文件。 server/all/data JBoss的数据库文件。比如,嵌入的数据库,或者JBossMQ。 server/all/deploy JBoss的热部署目录。放到这里的任何文件或目录会被JBoss自动部署。EJB、WAR、EAR, 甚至服务。 Jboss EJB3.0实例教程 版权所有:黎活明 server/all/lib 一些JAR,JBoss在启动特定配置时加载他们。(default和minimial配置也包含这个和下 面两个目录。) server/all/log JBoss的日志文件 server/all/tmp JBoss的临时文件。 3.2 JBoss中的部署 JBoss中的部署过程非常的简单、直接。在每一个配置中,JBoss不断的扫描一个特殊目录的变化: [jboss安装目录]/server/config-name/deploy 此目录一般被称为“部署目录”。 你可以把下列文件拷贝到此目录下: * 任何jar 库(其中的类将被自动添加到JBoss的classpath中) * EJB JAR * WAR (Web Appliction aRrchive) * EAR (Enterprise Application aRchive) * 包含JBoss MBean定义的XML文件 * 一个包含EJB JAR、WAR或者EAR的解压缩内容,并以.jar、.war 或者.ear 结尾的目录。 要重新部署任何上述文件(JAR、WAR、EAR、XML 等),用新版本的文件覆盖以前的就可以了。JBoss 会根 据比较文件的时间发现改变,然后部署新的文件。要重新部署一个目录,更新他的修改时间即可。 3.3 如何进行EJB打包 要发布EJB时必须把她打成jar 或ear 包,打包的方式有很多,如:jar 命令行、集成开发环境的打包向导和Ant 任务。下面介绍Elispse打包向导和Ant打包任务。 1. Elispse打包向导 在Elispse开发环境下,可以通过向导进行打包。右击项目名称,在跳出的菜单中选择“导出”,在“导出”对话 框选择“Jar 文件”,在“选择要导出的资源”时,选择源目录和用到的资源。然后选择一个存放目录及文件名。 点“完成”就结束了打包。 2. Ant打包任务 采用Ant进行打包是比较方便的,也是作者推荐的打包方式。下面我们看一个简单的打包任务。 Jboss EJB3.0实例教程 版权所有:黎活明 上面建立了一个名为jartest的Ant项目,默认的任务为default="jar",项目的路径为build.xml文件所在目录 basedir="." 。应用编绎过后的class文件已经存在于应用的/build/classes/目录下。Ant 定义了一个属性 “build.classes.dir”,他指向应用的/build/classes/目录。 定义了一个名叫jar 的任务,description是描述信息。任务中使用jar 命令把/build/classes/目录下的所有class文件打包进jar 文件,同时也把应用下的META-INF目录下的所有文件打 包进jar 文件的META-INF目录。打包后的jar 文件存放在应用目录下。文件名为:ejbfile.jar 3.4 如何进行WEB应用打包 一个Web 应用发布到Jboss 服务器时需要打成war 包。本节将介绍jar 命令行及Ant 任务两种war 文件的打包方 式。 1.在命令行下用jar 命令进行war 文件打包 在打包前把文件存成以下结构: WEB应用根目录 | -- **/*. jsp | -- WEB-INF | -- web.xml | -- lib | -- *.* | -- classes | -- **/*.class 在Dos窗口中进入到WEB应用根目录下,执行如下命令 jar cvf EJBTest.war * 此命令把WEB应用根目录下的所有文件及文件夹打包成EJBTest.war 文件 例如WEB应用根目录在: D:/java/webapp/ ,命令输入如下: D:/java/webapp> jar cvf EJBTest.war * 2.在Ant 任务中进行war 文件打包 如果文件存放的结构如下面所示: WEB应用根目录 |-- build.xml |--**/*.jsp |-- WEB-INF | -- web.xml | -- lib | -- *.* | -- classes | -- **/*.class 那么Ant 的war 文件打包任务如下: Jboss EJB3.0实例教程 版权所有:黎活明 定义一个名叫war 的任务。任务中执行war 打包操作,把应 用下除了build.xml, /WEB-INF/web.xml 之外的所有文件夹及文件打进war 包,同时把 webxml="${basedir}/WEB-INF/web.xml"作为web应用的web.xml文件。 3.5 使用了第三方类库的EJB如何打包 在实际项目中,我们经常需要使用第三方的类库。这些类库应该放在哪里?EJB应用一般都有被“卸出”(这里 指装入的反向过程)的能力,这种能力由部署时装入它们的类装载器支持。如果我们把第三方类库放入应用服务 器的标准类路径([jboss安装目录]/server/all/lib),这些类很可能完全失去被卸出的能力。这样,如果EJB应用要 更新某个第三方类库的版本,重新部署EJB应用时,第三方类库也要重新部署。在这种情形下,把第三方类库放 入应用服务器标准类路径很不方便,因为每次部署EJB应用时,都要重新启动整个应用服务器,这显然不是理想 的选择。适合放入应用服务器类路径的第三方类库通常是一些通用类库,如JDBC驱动。 对于针对特定应用的第三方类库,最理想的选择是把他们放入EJB Jar 文件中。每一个JAR文件里都有一个 manifest文件,这个文件由jar工具自动创建,默认名字是MANIFEST.MF。我们可以在manifest文件中加入一个 Class-Path属性,引用它所依赖的JAR文件。我们可以手工编辑manifest.mf文件,在原有内容的基础上,添加 Class-Path属性。Class-Path属性的值是用来搜索第三方类库的相对URL。这个URL总是相对于包含Class-Path 属性的组件。单个Class-Path属性内可以指定多个URL,一个manifest文件可以包含多个Class-Path属性。 假设本例EJB使用了两个第三方类库,名为:Upload.jar,Socket.jar,修改后的manifest.mf文件内容如下: Manifest-Version: 1.0 Ant-Version: Apache Ant 1.6.5 Created-By: 1.5.0_01-b08 (Sun Microsystems Inc.) Class-Path: Upload.jar Socket.jar 注意:Class-Path: 与Upload.jar 之间有一空格分隔(缺少了空格就会发生找不到jar 文件),多个jar 文件之间需 要用空格分隔。Class-Path所在行还需要进行回车换行。 下面是打完包后的目录结构: EJB应用根目录 | -- com (注:ejb 类包) Jboss EJB3.0实例教程 版权所有:黎活明 | -- Upload.jar (注:第三方类库) | -- Socket.jar (注:第三方类库) | -- META-INF | -- MANIFEST.MF (注:加入了Class-Path属性) 3.6 共用了第三方类库的J2EE 项目如何打包 一个J2EE 项目通常由多个EJB 和Web 应用构成,如果多个EJB 及Web 应用共用了一个第三方类库,我们又如 何打包呢?按照上节介绍的内容,我们会把第三方类库打进每个EJB Jar 文件及放在Web应用的/WEB-INF/lib 目 录下。虽然这种方案也能解决问题,但它明显地不够完善。封装JAR文件的目的是为了提高应用的模块化程度, 把同一个类库放入多个JAR文件正好是背其道而行之。此外,多次放置同一个类库无谓地加大了应用的体积。最 后,即使只改变一个类库的版权,每一个EJB JAR文件也都要重新构造,从而使构造过程复杂化。 下面的方案很好地解决了上面的问题 假设一个J2EE 项目含有两个EJB 及一个Web 应用,他们的文件名分别为:HelloWorld.jar,HelloChina.jar, MyEJBTest.war。这三个模块都使用了一个第三方类库,名为:Tools.jar 现在我们要做的是编辑这三个模块的manifest.mf文件,在原有内容的基础上,添加Class-Path属性。 三个模块的jar 文件修改后的manifest.mf文件内容如下: Manifest-Version: 1.0 Ant-Version: Apache Ant 1.6.5 Created-By: 1.5.0_01-b08 (Sun Microsystems Inc.) Class-Path: Tools.jar 注意:Class-Path: 与Tools.jar 之间有一空格分隔(缺少了空格就会发生找不到jar 文件),Class-Path所在行还需 要进行回车换行。 各个模块通过manifest.mf文件都能找到其所依赖的Tools.jar 文件。 下面是打完包后的目录结构: J2EE 应用根目录 | -- HelloWorld.jar | -- META-INF | -- MANIFEST.MF (注:加入了Class-Path属性, 引用它所依赖的Tools.jar) | -- HelloChina.jar | -- META-INF | -- MANIFEST.MF (注:加入了Class-Path属性, 引用它所依赖的Tools.jar) | -- MyEJBTest.war | -- META-INF | -- MANIFEST.MF (注:加入了Class-Path属性, 引用它所依赖的Tools.jar) | -- Tools.jar (注:第三方类库) | -- META-INF | -- application.xml | -- MANIFEST.MF (注:由工具自动生成,没有加入Class-Path属性) 把第三方类库和EJB模块并排打进jar 包,如果第三方类库很多的情况下,显的有些零乱而不雅观。在此作者建 Jboss EJB3.0实例教程 版权所有:黎活明 议大家建个文件夹专门用来存放第三方类库。如建个lib 文件夹,把第三方类库放在此文件夹下。然后修改J2EE 各模块的manifest.mf文件内容,修改后的内容如下: Manifest-Version: 1.0 Ant-Version: Apache Ant 1.6.5 Created-By: 1.5.0_01-b08 (Sun Microsystems Inc.) Class-Path: lib/Tools.jar J2EE应用的文件后缀为ear, 应用使用到的各模块在application.xml文件中定义,本例的application.xml内容如下: EJB3Trail J2EE Made Easy Trail Map HelloWorld.jar HelloChina.jar MyEJBTest.war MyEJBTest 因为EJB打进了EAR文件,在访问EJB时,JNDI名应为以下格式: 访问本地接口:EAR-FILE-BASE-NAME/EJB-CLASS-NAME/local 访问远程接口:EAR-FILE-BASE-NAME/EJB-CLASS-NAME/remote 例:如果上面J2EE 应用打成MyJ2EE.ear 文件,访问HelloWorld EJB远程接口的JNDI 名是: MyJ2EE/HelloWorldBean/remote 3.7 如何恢复本书配套例子的开发环境 登陆http://www.foshanshop.net,下载本书配套例子,解压缩后可以看到以下文件夹(本例解压缩到:E:/book): Jboss EJB3.0实例教程 版权所有:黎活明 每个文件夹都是一个项目,lib 文件夹存放所有项目使用到的jar 文件。下面我们以恢复HelloWorld项目为例,介绍 开发环境的恢复。 Eclipse必须是eclipse3.1.x以上版本,使用JDK5.0以上版本。 在Eclipse开发工具上点击“文件”->“新建”->“项目”,选择“Java 项目”,点击“下一步”,出现如下界面: Jboss EJB3.0实例教程 版权所有:黎活明 在上图中选择“从现有资源创建项目(X)”,点击“浏览(R)”,在出现的文件窗口中选择E:/book/HelloWorld 文件夹,如下图: 用文件夹名HelloWorld作为项目的名称,如下图: Jboss EJB3.0实例教程 版权所有:黎活明 在上图中点击“下一步”,Eclipse将会检测已存在项目的配置信息,检测完后出现下图: Jboss EJB3.0实例教程 版权所有:黎活明 上图中“java 设置” 出现感叹号,并提示缺少31 个构建路径条目,实际上是31 个Jar 文件路径不正确。现在 我们需要把Jar 文件的路径调整正确。在上图中点击“库(L)”,在出现的界面中把全部jar 文件删除(不要把“JRE 系统库”也删除哦),然后点击“添加外部JAR(X)”,在出现的文件窗口中把E:/book/lib文件夹下所有的Jar 文件选上(Ctrl+A),回到了如下界面: Jboss EJB3.0实例教程 版权所有:黎活明 点击“完成”就结束了开发环境的建立。 本书其他项目的开发环境恢复步骤如上。 3.8 如何对EJB3进行调试 要对EJB3进行调试,我们需要用到一个工具:JBossIDE,这个工具就是第二章要你下载安装的开发工具,如果 你没有安装,请参照第二章的内容进行安装。 在对EJB3进行调试前我们首先需要在Eclipse中配置JBoss server 的调试环境。调试环境建立后,所有的项 目都可以共用,配置步骤如下: 1> 首先需要在Eclipse开发界面中显示出”Jboss Server View”视图,你可以在Eclipse菜单栏点击 “窗口 (W)”-“显示视图(V)”-“其他(O)…”,在出现的“显示视图”窗口中选择Server 文件夹下的”Jboss Server View”,如下图: Jboss EJB3.0实例教程 版权所有:黎活明 点击“确定”后,“Jboss Server View”视图将出现在Eclipse开发界面的控制区(如果你的开发界面已经很 零乱,可以通过“窗口(W)”-“复位透视图”复位Eclipse原始界面),如下图: 2> 接着我们需要建立一个Jboss Server。在“Jboss Server View”视图上边白色区域点击鼠标右键,在出 现的属性菜单中点击“New”-“Server”。如下图: 点击“Server”后跳出如下窗口,在这个窗口中我们选择“Jboss AS 4.0”,其他参数采用系统默认值。 Jboss EJB3.0实例教程 版权所有:黎活明 点击“下一步”后跳出如下窗口,在这个窗口中我们一定要先选择Jboss服务器所在目录然后再输入Server 名 称(作者的Jboss 安装在C:/JavaServer/jboss,Server 名称采用foshanshop,为什么要先选择Jboss服务器 所在目录?因为在这个界面中存在BUG,有时先输入Name再选择Home Directory,“完成”按钮不可用,如 果先选择Home Directory 再输入Name,“完成”按钮才可用)。当选择完Jboss 的安装目录后,Configuration 栏中将会自动出现Jboss 配置项如all,default或minimial,因为本教程安装Jboss软件时采用all配置项,所以 在Configuration栏中只出现了all,如果你同样使用了all配置,那么请选择all选项(注意:如果你在 Configuration栏中不选择all选项,系统将会默认采用default配置,届时启动Jboss 时将会出错) Jboss EJB3.0实例教程 版权所有:黎活明 点击“完成”,“Jboss Server View”视图如下图所示: 此时的服务器处于Stopped状态,你需要调试EJB3 时可以以Debug方式启动Jboss,方法是:在服务器 foshanshop上右击鼠标,在跳出的属性菜单中点击“Debug”,或者点击“Jboss Server View”视图右边的 小昆虫图标。控制台会输出Jboss 的启动信息,如果启动出错,先看看在DOS窗口下以命令行方式(run –c all) 能否启动Jboss,如果DOS窗口下能正常启动Jboss,那估计是你在建立Jboss Server 的配置中出错,除了 检查Jboss 安装目录的选择是否正确之外,还要重点检查Jboss 配置项选择是否正确,可以双击服务器 foshanshop,在打开的界面中点击“Open launch configuration”。在“编缉启动配置属性”窗口中点击“自 变量”,查看程序自变量的值。其中-configuration=all即为当前采用的Jboss配置项,如果你的Jboss配置项 是all,而configuration=default,那么就会出现启动出错,解决方法是把新建的Jboss Server 删除再重建。 经过上面的步骤,JBoss server的调试环境已经搭建好,下面我们就以HelloWorld项目为例介绍EJB3的调试。 我们按照上面章节的步骤恢复HelloWorld项目的开发环境。因为SayHello(String name)方法内部代码不多, 为了有代码可调试,我们在方法内部随便添加几行代码,并把断点设在代码的第一行,如下图: Jboss EJB3.0实例教程 版权所有:黎活明 接着我们点击“Jboss Server View”视图右边的小昆虫图标,以Debug方式启动名为foshanshop的Jboss Server。在“控制台”观察启动是否完成,如果启动完成,我们就打开HelloWorld项目下的build.xml文件, 在“大纲”视图中以调试方式执行Ant的deploy任务,如下图: Jboss EJB3.0实例教程 版权所有:黎活明 执行”Ant构建”后,在控制台将会输出Ant的任务信息及HelloWorld在Jboss 中的发布信息,当HelloWorld 发布完成后,我们在浏览器上输入http://localhost:8080/EJBTest/Test.jsp(注意:确保已经发布了Web应用 EJBTest.war),Eclipse 将会提示是否切换到调试透视图中,如下图, 这里点“是”.如果你不想老出现这个对话框,可以勾选上”记住我的决定”.接着将出现如下窗口,提示找不class 对应的java 源文件。 Jboss EJB3.0实例教程 版权所有:黎活明 此时你可以在上图点击“编辑源查找路径(E)”,出现“编辑源查找路径”窗口,如下图: 在上图中点击“添加(A)”,出现“添加源代码”窗口,如下图: Jboss EJB3.0实例教程 版权所有:黎活明 在上图我们选择从”java项目”寻找对应的源代码,如果你的源代码不在eclipse 的项目中,你可以通过“文件系 统目录”进行寻找,把路径指向源代码所在的src 目录。选择”java项目”,点击“确定”后出现下面对话框: 在上图中我们勾选HelloWorld项目,点击“确定”。此时将回到调试窗口界面,如下图: Jboss EJB3.0实例教程 版权所有:黎活明 上图中可以看到调试指针已经落在了断点所在行,此后的调试方法就和普通的java应用程序一样,你可以使用 单步跳入,单步跳过,单步返回,运行至行等调试方式,这里就不作详细解说了,想了解的朋友可以参考Eclipse 关于调试方面的资料。 在这里需要说明下,调试指针(即绿色光亮行)的移动有时候比较慢,点击“单步跳入”等调试按钮十多秒后, 调试指针的移动才有反应。 为了方便调试EJB3,建议使用Ant执行编绎、打包、发布等操作。调试ejb3 时不需要反复重启Jboss Server。 Jboss EJB3.0实例教程 版权所有:黎活明 第四章 会话Bean(Session Bean) 4.1 Stateless Session Beans(无状态bean)开发 4.1.1 开发只存在Remote 接口的无状态Session Bean 步骤如下: 第一步:要定义一个会话Bean,首先需要定义一个包含他所有业务方法的接口。这个接口不需要任何注释,就像 普通的java 接口那样定义。调用EJB的客户端通过使用这个接口引用从EJB容器得到的会话Bean对象stub。接 口的定义如下: HelloWorld.java //author:lihuoming package com.foshanshop.ejb3; public interface HelloWorld { public String SayHello(String name); } 第二步:实现上面的接口并加入两个注释@Stateless , @Remote,第一个注释定义这是一个无状态会话Bean,第 二个注释指明这个无状态Bean的remote接口。在使用这两个注释时需要使用一些EJB的类包,这些类包都可以 在jboss安装目录的client,/server/all/deploy/jboss-aop-jdk50.deployer,/server/all/deploy/ejb3.deployer, /lib/endorsed等文件夹下找到,或者在源代码的Lib 文件夹下获得(下载地址:http://www.foshanshop.net/)。 经过上面的步骤一个只存在Remote接口的无状态会话Bean就开发完成。无状态会话Bean是一个简单的POJO(纯 粹的面向对象思想的java 对象),EJB3.0容器自动地实例化及管理这个Bean。下面是HelloWorld会话Bean的实 现代码: HelloWorldBean.java 。实现类的命名规则是:接口+Bean ,如: HelloWorldBean //author:lihuoming package com.foshanshop.ejb3.impl; import com.foshanshop.ejb3.HelloWorld; import javax.ejb.Remote; import javax.ejb.Stateless; @Stateless @Remote ({HelloWorld.class}) public class HelloWorldBean implements HelloWorld { public String SayHello(String name) { return name +"说:你好!世界,这是我的第一个EJB3 哦."; } } HelloWorld会话Bean开发完了,现在我们把她发布到Jboss中。在发布前需要把她打成Jar 包或EAR包 (如何打 包请考照 第三章:如何进行EJB打包)。 Jboss EJB3.0实例教程 版权所有:黎活明 打完包后,启动Jboss,把发布包拷贝到[jboss安装目录]/server/all/deploy/目录下。观察Jboss控制台输出,如果没 有抛出例外并看到下面的输出界面,发布就算成功了。 一旦发布成功,你就可以在jboss 的管理平台查看她们的JNDI名,输入下面URL http://localhost:8080/jmx-console/ 点击service=JNDIView,查看EJB的JNDI名称。(如下图) 在出现的页面中,找到“List of MBean operations:”栏。点击”Invoke”按钮,出现如下界面: 在上图中可以看见HelloWorld会话Bean的JNDI名,JNDI名的组成规则是“上层名称/下层名称“,每层之间以”/” 分隔。HelloWorld会话Bean的JNDI名是:HelloWorldBean/remote 。 HelloWorld会话Bean发布成功后,接下来介绍客户端如何访问她。 当一个无状态会话Bean发布到EJB容器时,容器就会为她创建一个对象stub,并把她注册进容器的JNDI目录, 客户端代码使用她的JNDI名从容器获得他的stub。通过这个stub,客户端可以调用她的业务方法。例子代码如 下: Test.jsp <%@ page contentType="text/html; charset=GBK"%> <%@ page import="com.foshanshop.ejb3.HelloWorld, javax.naming.*, java.util.Properties"%> <% Properties props = new Properties(); Jboss EJB3.0实例教程 版权所有:黎活明 props.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory"); props.setProperty("java.naming.provider.url", "localhost:1099"); props.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming"); InitialContext ctx; try { ctx = new InitialContext(props); HelloWorld helloworld = (HelloWorld) ctx.lookup("HelloWorldBean/remote"); out.println(helloworld.SayHello("佛山人")); } catch (NamingException e) { out.println(e.getMessage()); } %> ctx = new InitialContext(props);是设置JNDI访问的环境,本例使用JBoss,所以把环境设为JBoss的上下文环境。 在这里作者要重点说明一下EJB JNDI名称默认的命名规则,命名规则如下: 1> 如果EJB打包进后缀为*.ear 的J2EE 发布文件,默认的JNDI 名称是 访问本地接口:EAR-FILE-BASE-NAME/EJB-CLASS-NAME/local 访问远程接口:EAR-FILE-BASE-NAME/EJB-CLASS-NAME/remote 例:EJB HelloWorld打包进名为HelloWorld.ear 的J2EE 应用,访问她远程接口的JNDI 名是: HelloWorld/HelloWorldBean/remote 2> 如果EJB应用打包成后缀为*.jar 的发布文件, 默认的JNDI 名称是 访问本地接口:EJB-CLASS-NAME/local 访问远程接口:EJB-CLASS-NAME/remote 例: HelloWorld应用打包成HelloWorld.jar 文件,访问她远程接口的JNDI名称是:HelloWorldBean/remote 另外有一点要注意:EJB-CLASS-NAME 是不带包名的,如com.foshanshop.ejb3.impl.HelloWorldBean只需取 HelloWorldBean。 目前网上很多教材获取JNDI名的方式都过时了,如: HelloWorld helloworld = (HelloWorld) ctx.lookup(HelloWorld.class.getName()); 我们把上面的客户端应用打成war 文件。然后把她拷贝到“[jboss安装目录]/server/all/deploy”目录下。如果war 文件的文件名为 EJBTest.war ,我们可以通过http://localhost:8080/EJBTest/Test.jsp访问客户端。 本例子的EJB源代码在HelloWorld文件夹(源代码下载:http://www.foshanshop.net/),项目使用到的类库在上级目 录lib 文件夹下。要恢复HelloWorld项目的开发环境请参考第三章”如何恢复本书配套例子的开发环境”,要发布本 例子(确保配置了环境变量JBOSS_HOME 及启动了Jboss),你可以执行Ant 的deploy任务。 本例子的客户端代码在EJBTest文件夹,要发布客户端应用,你可以执行Ant 的deploy任务。通过 http://localhost:8080/EJBTest/Test.jsp访问客户端。 Jboss EJB3.0实例教程 版权所有:黎活明 4.1.2 开发只存在Local 接口的无状态Session Bean 开发只存在Local接口的无状态会话Bean的步骤和上节开发只存在Remote接口的无状态会话Bean的步骤相同, 两者唯一不同之处是,前者使用@Remote 注释指明实现的接口是远程接口,后者使用@Local 注释指明实现的接口 是本地接口。当@Local 和@Remote 注释都不存在时,会话 Bean 实现的接口默认为Local 接口。如果在本机调用 EJB(确保客户端与EJB 容器运行在同一个JVM),采用Local 接口访问EJB 优于Remote 接口,因为Remote接口 访问EJB 需要经过远程方法调用(RPCs)环节,而Local 接口访问EJB 直接从JVM 中返回EJB 的引用。下面是只存 在Local 接口的无状态会话Bean 代码。 LocalHelloWorld.java //author:lihuoming package com.foshanshop.ejb3; public interface LocalHelloWorld { public String SayHello(String name); } LocalHelloWorldBean.java //author:lihuoming package com.foshanshop.ejb3.impl; import javax.ejb.Local; import javax.ejb.Stateless; import com.foshanshop.ejb3.LocalHelloWorld; @Stateless @Local ({LocalHelloWorld.class}) public class LocalHelloWorldBean implements LocalHelloWorld { public String SayHello(String name) { return name +"说:你好!世界,这是一个只具有Local接口的无状态Bean"; } } 客户端调用代码: LocalSessionBeanTest.jsp <%@ page contentType="text/html; charset=GBK"%> <%@ page import="com.foshanshop.ejb3.LocalHelloWorld, javax.naming.*, java.util.Properties"%> <% Properties props = new Properties(); props.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory"); props.setProperty("java.naming.provider.url", "localhost:1099"); props.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming"); InitialContext ctx; try { ctx = new InitialContext(props); LocalHelloWorld helloworld = (LocalHelloWorld) Jboss EJB3.0实例教程 版权所有:黎活明 ctx.lookup("LocalHelloWorldBean/local"); out.println(helloworld.SayHello("佛山人")); } catch (NamingException e) { out.println(e.getMessage()); } %> 上面的客户端代码打包成war文件发布到jboss中。如果你试图在独立的Tomcat服务器中执行客户端代码(如何 在独立的Tomcat环境中调用EJB请考照 第二章:在独立的Tomcat 中调用EJB),你将获得如下例外: java.lang.NullPointerException org.jboss.ejb3.stateless.StatelessLocalProxy.invoke(StatelessLocalProxy.java:74) 产生此例外的原因是,调用Local接口的客户端与EJB容器不在同一个VM(虚拟内存堆)。相对于发布到jboss deploy目录下的客户端应用而言,他与EJB容器运行在同一个VM。如果客户端与EJB容器在不同的VM,只能 通过其Remote接口进行访问。 本例子的EJB源代码在LocalSessionBean文件夹(源代码下载:http://www.foshanshop.net/),项目中使用到的类库 在上级目录lib 文件夹下。要恢复LocalSessionBean项目的开发环境请参考第三章”如何恢复本书配套例子的开发 环境”,要发布本例子EJB (确保配置了环境变量JBOSS_HOME及启动了Jboss),你可以执行Ant 的deploy 任务。 本例子的客户端代码在EJBTest文件夹,要发布客户端应用,你可以执行Ant 的deploy任务。通过 http://localhost:8080/EJBTest/LocalSessionBeanTest.jsp访问客户端。 4.1.3 开发存在Remote 与Local 接口的无状态Session Bean 在实际应用中,一个无状态Session Bean都应该实现Remote与Local接口。当会话Bean的某些方法只供EJB 容器内部调用而不对外暴露时,可以把他定义在Local接口。本节介绍如何开发一个具有Remote与Local 接口的 无状态Session Bean。开发前先介绍一下两个接口具有的方法,两者都含有方法Add(int a, int b),而Local接口含 有一个自己的方法:getResult()。下面是例子的源代码。 定义远程接口:Operation.java //author:lihuoming package com.foshanshop.ejb3; public interface Operation { public int Add(int a, int b); } 定义本地接口:LocalOperation.java,本地接口具有远程接口的所有方法,另外有自己的方法getResult() //author:lihuoming package com.foshanshop.ejb3; public interface LocalOperation extends Operation { public int getResult(); } 实现本地接口与远程接口的会话Bean:OperationBean.java Jboss EJB3.0实例教程 版权所有:黎活明 //author:lihuoming package com.foshanshop.ejb3.impl; import javax.ejb.Local; import javax.ejb.Remote; import javax.ejb.Stateless; import com.foshanshop.ejb3.LocalOperation; import com.foshanshop.ejb3.Operation; @Stateless @Remote ({Operation.class}) @Local ({LocalOperation.class}) public class OperationBean implements Operation, LocalOperation { private int total = 0; private int addresult = 0; public int Add(int a, int b) { addresult = a + b; return addresult; } public int getResult() { total += addresult; return total; } } JSP客户端:OperationBeanTest.jsp <%@ page contentType="text/html; charset=GBK"%> <%@ page import="com.foshanshop.ejb3.Operation, com.foshanshop.ejb3.LocalOperation, javax.naming.*, java.util.Properties"%> <% Properties props = new Properties(); props.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory"); props.setProperty("java.naming.provider.url", "localhost:1099"); props.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming"); InitialContext ctx = new InitialContext(props); try { Operation operation = (Operation) ctx.lookup("OperationBean/remote"); out.println("通过远程接口调用EJB成功"); out.println(" (通过远程接口调用EJB)相加的结果是:"+ operation.Add(1,1)); } catch (Exception e) { out.println(" 远程接口调用失败"); } out.println(" =============================================="); Jboss EJB3.0实例教程 版权所有:黎活明 try { //通过本地接口调用EJB LocalOperation A = (LocalOperation) ctx.lookup("OperationBean/local"); out.println(" (通过本地接口调用EJB)调用A.Add()的结果是:"+ A.Add(1,1)); out.println(" 调用A.getResult()的结果是:"+ A.getResult()); LocalOperation B = (LocalOperation) ctx.lookup("OperationBean/local"); out.println(" (通过本地接口调用EJB)调用B.Add()的结果是:"+ B.Add(1,1)); out.println(" 调用B.getResult()的结果是:"+ B.getResult() + " "); } catch (Exception e) { out.println(" 本地接口调用失败"); } %> 把会话Bean及客户端Web应用发布到jboss,客户端的调用结果如下: 通过远程接口调用EJB成功 (通过远程接口调用EJB)相加的结果是:2 ============================================== (通过本地接口调用EJB)调用A.Add()的结果是:2 调用A.getResult()的结果是:2 (通过本地接口调用EJB)调用B.Add()的结果是:2 调用B.getResult()的结果是:4 细心的你可能会发现调用Local接口时,两次累加的结果都不一样,一个是2,一个是4。调用本地接口的客户端 代码片段如下: //通过本地接口调用EJB LocalOperation A = (LocalOperation) ctx.lookup("OperationBean/local"); out.println(" (通过本地接口调用EJB)调用A.Add()的结果是:"+ A.Add(1,1)); out.println(" 调用A.getResult()的结果是:"+ A.getResult()); LocalOperation B = (LocalOperation) ctx.lookup("OperationBean/local"); out.println(" (通过本地接口调用EJB)调用B.Add()的结果是:"+ B.Add(1,1)); out.println(" 调用B.getResult()的结果是:"+ B.getResult() + " "); 你可能认为A和B得到的应该是两个不同的对象,根据OperationBean.java 的代码判断,他们的结果都是2才对。 为何一个为2,另一个为4 呢。 这是因为Stateless Session Bean不负责记录使用者状态,Stateless Session Bean一旦实例化就被加进会话池中,各 个用户都可以共用。即使用户已经消亡,Stateless Session Bean的生命期也不一定结束,它可能依然存在于会话池 中,供其他用户调用。如果它有自己的属性(变量),那么这些变量就会受到所有调用它的用户的影响。 本例子的EJB源代码在LocalRemoteBean文件夹(源代码下载:http://www.foshanshop.net/),项目中使用到的类库 在上级目录lib 文件夹下。要恢复LocalRemoteBean项目的开发环境请参考第三章”如何恢复本书配套例子的开发 环境”,要发布本例子EJB (确保配置了环境变量JBOSS_HOME及启动了Jboss),你可以执行Ant 的deploy 任务。 本例子的客户端代码在EJBTest文件夹,要发布客户端应用,你可以执行Ant 的deploy任务。通过 http://localhost:8080/EJBTest/OperationBeanTest.jsp访问客户端。 Jboss EJB3.0实例教程 版权所有:黎活明 4.2 Stateful Session Beans(有状态bean)开发 有状态Bean是一个可以维持自身状态的会话Bean。每个用户都有自己的一个实例,在用户的生存期内,Stateful Session Bean保持了用户的信息,即“有状态”;一旦用户灭亡(调用结束或实例结束),Stateful Session Bean的 生命期也告结束。即每个用户最初都会得到一个初始的Stateful Session Bean。 Stateful Session Bean 的开发步骤与Stateless Session Bean的开发步骤相同。先定义业务接口,例子代码如下: MyAccount.java //author:lihuoming package com.foshanshop.ejb3; import java.io.Serializable; public interface MyAccount extends Serializable { public int Add(int a, int b); public int getResult() ; } 大家注意stateful session bean必须实现Serializable接口,这样EJB容器才能在她们不再使用时序列化存储她们的 状态信息. MyAccountBean.java //author:lihuoming package com.foshanshop.ejb3.impl; import javax.ejb.Remote; import javax.ejb.Stateful; import com.foshanshop.ejb3.MyAccount; @SuppressWarnings("serial") @Stateful @Remote(MyAccount.class) public class MyAccountBean implements MyAccount{ private int total = 0; private int addresult = 0; public int Add(int a, int b) { addresult = a + b; return addresult; } public int getResult() { total += addresult; return total; } } 上面MyAccountBean直接实现MyAccount 接口,通过@Stateful 注释定义这是一个有状态会话Bean,@Remote 注释指明有状态Bean的remote接口。@SuppressWarnings("serial") 注释屏蔽缺少serialVersionUID 定义的警告。 下面是MyAccountBean的JSP客户端代码: StatefulBeanTest.jsp Jboss EJB3.0实例教程 版权所有:黎活明 <%@ page contentType="text/html; charset=GBK"%> <%@ page import="com.foshanshop.ejb3.MyAccount,javax.naming.*, java.util.Properties"%> <% Properties props = new Properties(); props.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory"); props.setProperty("java.naming.provider.url", "localhost:1099"); props.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming:org.jnp.interfaces"); InitialContext ctx = new InitialContext(props); try { MyAccount A = (MyAccount) ctx.lookup("MyAccountBean/remote"); out.println("调用A.Add()的结果是:"+ A.Add(1,1)); out.println(" 调用A.getResult()的结果:"+ A.getResult()); out.println(" ========================================"); MyAccount B = (MyAccount) ctx.lookup("MyAccountBean/remote"); out.println(" 调用B.Add()的结果是:"+ B.Add(1,1)); out.println(" 调用B.getResult()的结果:"+ B.getResult()); } catch (Exception e) { out.println(e.getMessage()); } %> 上面JSP客户端,有A和B两个用户在使用MyAccountBean,他们的执行结果如下: 调用A.Add()的结果是:2 调用A.getResult()的结果:2 ======================================== 调用B.Add()的结果是:2 调用B.getResult()的结果:2 因为stateful session bean的每个用户都有自己的一个实例,所以两者对stateful session bean的操作不会影响对方。 另外注意:如果后面需要操作某个用户的实例,你必须在客户端缓存Bean的Stub对象(JSP通常的做法是用Session 缓存),这样在后面每次调用中,容器才知道要提供相同的bean实例。 本例子的EJB源代码在StatefulBean文件夹(源代码下载:http://www.foshanshop.net/),项目中使用到的类库在上 级目录lib 文件夹下。要恢复StatefulBean项目的开发环境请参考第三章”如何恢复本书配套例子的开发环境”,要发 布本例子EJB (确保配置了环境变量JBOSS_HOME 及启动了Jboss),你可以执行Ant 的deploy任务。 本例子的客户端代码在EJBTest文件夹,要发布客户端应用,你可以执行Ant 的deploy任务。通过 http://localhost:8080/EJBTest/StatefulBeanTest.jsp访问客户端。 4.3 Stateless Session Bean 与Stateful Session Bean 的区别 这两种Session Bean都可以将系统逻辑放在方法之中执行,不同的是Stateful Session Bean可以记录呼叫者的状态, 因此一个使用者会有自己的一个实例。Stateless Session Bean虽然也是逻辑组件,但是他却不负责记录使用者状态, Jboss EJB3.0实例教程 版权所有:黎活明 也就是说当使用者呼叫 Stateless Session Bean的时候,EJB 容器并不会寻找特定的Stateless Session Bean的实体 来执行这个method。换言之,很可能数个使用者在执行某个Stateless Session Bean的methods时,会是同一个Bean 的实例在执行。从内存方面来看,Stateful Session Bean与Stateless Session Bean比较,Stateful Session Bean会消 耗J2EE Server 较多的内存,然而Stateful Session Bean的优势却在于他可以维持使用者的状态。 4.4 如何改变Session Bean 的JNDI 名称 默认的JNDI命名规则前面已经介绍过,但有些情况下需要自定义名称。要自定义JNDI名称,可以使用 @LocalBinding 和 @RemoteBinding 注释,@LocalBinding注释指定Session Bean的Local接口的JNDI名称, @RemoteBinding注释指定Session Bean的Remote接口的JNDI名称,下面的代码展示了如何自定义JNDI名: //author:lihuoming package com.foshanshop.ejb3.impl; import javax.ejb.Local; import javax.ejb.Remote; import javax.ejb.Stateless; import com.foshanshop.ejb3.LocalOperation; import com.foshanshop.ejb3.Operation; import org.jboss.annotation.ejb.LocalBinding; import org.jboss.annotation.ejb.RemoteBinding; @Stateless @Remote ({Operation.class}) @RemoteBinding (jndiBinding="foshanshop/RemoteOperation") @Local ({LocalOperation.class}) @LocalBinding (jndiBinding="foshanshop/LocalOperation") public class OperationBean implements Operation, LocalOperation { private int total = 0; private int addresult = 0; public int Add(int a, int b) { addresult = a + b; return addresult; } public int getResult() { total += addresult; return total; } } 在JSP客户端调用上面EJB的代码片断如下: InitialContext ctx = new InitialContext(props); Operation operation = (Operation) ctx.lookup("foshanshop/RemoteOperation"); Jboss EJB3.0实例教程 版权所有:黎活明 4.5 Session Bean 的生命周期 EJB容器创建和管理session bean实例,有些时候,你可能需要定制session bean的管理过程。例如,你可能想在创 建session bean实例的时候初始化字段变量,或在bean实例被销毁的时候关掉外部资源。上述这些,你都可能通过 在bean 类中定义生命周期的回调方法来实现。这些方法将会被容器在生命周期的不同阶段调用(如:创建或销 毁时)。通过使有下面所列的注释,EJB 3.0允许你将任何方法指定为回调方法。这不同于EJB 2.1,EJB 2.1 中, 所有的回调方法必须实现,即使是空的。EJB 3.0中,bean可以有任意数量,任意名字的回调方法。 ·@PostConstruct:当bean对象完成实例化后,使用了这个注释的方法会被立即调用。这个注释同时适用于 有状态和无状态的会话bean。 ·@PreDestroy:使用这个注释的方法会在容器从它的对象池中销毁一个无用的或者过期的bean 实例之前调 用。这个注释同时适用于有状态和无状态的会话bean。 ·@PrePassivate:当一个有状态的session bean实例空闲过长的时间,容器将会钝化(passivate)它,并把它的 状态保存在缓存当中。使用这个注释的方法会在容器钝化bean实例之前调用。这个注释适用于有状态的会话bean。 当钝化后,又经过一段时间该bean 仍然没有被操作,容器将会把它从存储介质中删除。以后,任何针对该bean 方法的调用容器都会抛出例外。 ·@PostActivate:当客户端再次使用已经被钝化的有状态session bean时,新的实例被创建,状态被恢复。 使用此注释的session bean会在bean的激活完成时调用。这个注释只适用于有状态的会话bean。 ·@Init:这个注释指定了有状态session bean初始化的方法。它区别于@PostConstruct注释在于:多个@Init 注释方法可以同时存在于有状态session bean 中,但每个bean实例只会有一个@Init注释的方法会被调用。这取 决于bean是如何创建的(细节请看EJB 3.0规范)。@PostConstruct在@Init之后被调用。 另一个有用的生命周期方法注释是@Remove,特别是对于有状态session bean。当应用通过存根对象调用使用了 @Remove注释的方法时,容器就知道在该方法执行完毕后,要把bean实例从对象池中移走。 下面是这些生命周期方法注释在LifeCycleBean中的一个示例: LifeCycleBean.java //author:lihuoming package com.foshanshop.ejb3.impl; import com.foshanshop.ejb3.LifeCycle; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.ejb.Init; import javax.ejb.PostActivate; import javax.ejb.PrePassivate; import javax.ejb.Remote; import javax.ejb.Remove; import javax.ejb.Stateful; @Stateful @Remote ({LifeCycle.class}) public class LifeCycleBean implements LifeCycle { public String Say() { try { Jboss EJB3.0实例教程 版权所有:黎活明 Thread.sleep(1000*30); } catch (InterruptedException e) { e.printStackTrace(); } return "这是会话Bean生命周期应用例子"; } @Init public void initialize () { System.out.println("initialize()方法被调用"); } @PostConstruct public void Construct () { System.out.println("Construct()方法被调用"); } @PreDestroy public void exit () { System.out.println("exit()方法被调用"); } @PrePassivate public void serialize () { System.out.println("serialize()方法被调用"); } @PostActivate public void activate () { System.out.println("activate()方法被调用"); } @Remove public void stopSession () { System.out.println("stopSession()方法被调用"); //调用该方法以通知容器移除该bean实例、终止会话。方法体可以是空的。 } } 本例子的运行结果输出在Jboss控制台,请仔细观察。 下面是LifeCycleBean的Remote接口 LifeCycle.java //author:lihuoming package com.foshanshop.ejb3; public interface LifeCycle { Jboss EJB3.0实例教程 版权所有:黎活明 public String Say(); public void stopSession (); } 下面是Session Bean的JSP 客户端代码: LifeCycleTest.jsp <%@ page contentType="text/html; charset=GBK"%> <%@ page import="com.foshanshop.ejb3.LifeCycle, javax.naming.*, java.util.Properties"%> <% Properties props = new Properties(); props.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory"); props.setProperty("java.naming.provider.url", "localhost:1099"); props.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming"); try { LifeCycle lifecycle = (LifeCycle) session.getAttribute("lifecycle"); if (lifecycle == null) { InitialContext ctx = new InitialContext(props); lifecycle = (LifeCycle) ctx.lookup("LifeCycleBean/remote"); session.setAttribute ("lifecycle", lifecycle); } out.println(lifecycle.Say()); out.println(" 请注意观察Jboss控制台输出.等待9分钟左右,容器将会钝化此会话 Bean,@PrePassivate注释的方法将会执行 "); out.println("你可以执行会话Bean的stopSession方法,把会话Bean实例从 对象池中移走。在销毁这个会话Bean之前将会执行 @PreDestroy注释的方法 "); /* lifecycle.stopSession(); */ } catch (Exception e) { out.println(e.getMessage()); } %> 本例子的EJB源代码在SessionBeanLifeCycle文件夹(源代码下载:http://www.foshanshop.net/),项目中使用到的 类库在上级目录lib 文件夹下。要恢复SessionBeanLifeCycle项目的开发环境请参考第三章”如何恢复本书配套例 子的开发环境”,要发布本例子EJB (确保配置了环境变量JBOSS_HOME及启动了Jboss),你可以执行Ant的deploy 任务。 本例子的客户端代码在EJBTest文件夹,要发布客户端应用,你可以执行Ant 的deploy任务。通过 http://localhost:8080/EJBTest/LifeCycleTest.jsp访问客户端。 Jboss EJB3.0实例教程 版权所有:黎活明 4.6 拦截器(Interceptor) 拦截器可以监听程序的一个或所有方法。拦截器对方法调用流提供了细粒度控制。可以在无状态会话 bean、有 状态会话 bean 和消息驱动 bean 上使用它们。拦截器可以是同一 bean 类中的方法或是一个外部类。 下面介绍如何在Session Bean类中使用外部拦截器类。 HelloChinaBean.java //author:lihuoming package com.foshanshop.ejb3.impl; import com.foshanshop.ejb3.HelloChina; import com.foshanshop.ejb3.HelloChinaRemote; import javax.ejb.Local; import javax.ejb.Remote; import javax.ejb.Stateless; import javax.interceptor.Interceptors; @Stateless @Remote ({HelloChinaRemote.class}) @Local(HelloChina.class) @Interceptors({HelloInterceptor.class}) public class HelloChinaBean implements HelloChina,HelloChinaRemote { public String SayHello(String name) { return name +"说:你好!中国."; } public String Myname() { return "我是佛山人"; } } @Interceptors 注释指定一个或多个在外部类中定义的拦截器。上面拦截器HelloInterceptor 对HelloChinaBean中 的所有方法进行监听。 拦截器HelloInterceptor.java //author:lihuoming package com.foshanshop.ejb3.impl; import javax.interceptor.AroundInvoke; import javax.interceptor.InvocationContext; public class HelloInterceptor { @AroundInvoke public Object log(InvocationContext ctx) throws Exception { System.out.println("*** HelloInterceptor intercepting"); long start = System.currentTimeMillis(); Jboss EJB3.0实例教程 版权所有:黎活明 try{ if (ctx.getMethod().getName().equals("SayHello")){ System.out.println("*** SayHello 已经被调用! *** " ); } if (ctx.getMethod().getName().equals("Myname")){ System.out.println("*** Myname 已经被调用! *** " ); } return ctx.proceed(); }catch (Exception e) { throw e; }finally { long time = System.currentTimeMillis() - start; System.out.println("用时:"+ time + "ms"); } } } @AroundInvoke 注释指定了要用作拦截器的方法。用@AroundInvoke注释指定的方法必须遵守以下格式: public Object XXX(InvocationContext ctx) throws Exception XXX 代表方法名可以任意。 下面是HelloChinaBean的本地及远程业务接口 HelloChina.java //author:lihuoming package com.foshanshop.ejb3; public interface HelloChina extends HelloChinaRemote{ } HelloChinaRemote.java //author:lihuoming package com.foshanshop.ejb3; public interface HelloChinaRemote { public String SayHello(String name); public String Myname(); } 下面是Session Bean的JSP 客户端代码: InterceptorTest.jsp <%@ page contentType="text/html; charset=GBK"%> <%@ page import="com.foshanshop.ejb3.HelloChinaRemote, javax.naming.*, java.util.Properties"%> <% Properties props = new Properties(); props.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory"); props.setProperty("java.naming.provider.url", "localhost:1099"); props.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming"); Jboss EJB3.0实例教程 版权所有:黎活明 InitialContext ctx; try { ctx = new InitialContext(props); HelloChinaRemote hellochinaremote = (HelloChinaRemote) ctx.lookup("HelloChinaBean/remote"); out.println(hellochinaremote.SayHello("佛山人")); out.println(" "+ hellochinaremote.Myname()); } catch (NamingException e) { out.println(e.getMessage()); } %> 除了可以在外部定义拦截器之外,还可以将Session Bean 中的一个或多个方法定义为拦截器。下面以上面的 HelloChinaBean为例,介绍在Session Bean中如何定义拦截器。 HelloChinaBean.java //author:lihuoming package com.foshanshop.ejb3.impl; import com.foshanshop.ejb3.HelloChina; import com.foshanshop.ejb3.HelloChinaRemote; import javax.ejb.Local; import javax.ejb.Remote; import javax.ejb.Stateless; import javax.interceptor.AroundInvoke; import javax.interceptor.InvocationContext; @Stateless @Remote ({HelloChinaRemote.class}) @Local(HelloChina.class) public class HelloChinaBean implements HelloChina,HelloChinaRemote { public String SayHello(String name) { return name +"说:你好!中国."; } public String Myname() { return "我是佛山人"; } @AroundInvoke public Object log(InvocationContext ctx) throws Exception { try{ if (ctx.getMethod().getName().equals("SayHello")){ System.out.println("*** HelloChinaBean.SayHello() 已经被调用! *** " ); } Jboss EJB3.0实例教程 版权所有:黎活明 if (ctx.getMethod().getName().equals("Myname")){ System.out.println("*** HelloChinaBean.Myname() 已经被调用! *** " ); } return ctx.proceed(); }catch (Exception e) { throw e; } } } 上面只需一个@AroundInvoke 注释就指定了要用作拦截器的方法。 本例子的EJB源代码在Interceptor 文件夹(源代码下载:http://www.foshanshop.net/),项目中使用到的类库在上级 目录lib 文件夹下。要恢复Interceptor 项目的开发环境请参考第三章”如何恢复本书配套例子的开发环境”,要发布 本例子EJB (确保配置了环境变量JBOSS_HOME 及启动了Jboss),你可以执行Ant 的deploy任务。 本例子的客户端代码在EJBTest文件夹,要发布客户端应用,你可以执行Ant 的deploy任务。通过 http://localhost:8080/EJBTest/InterceptorTest.jsp访问客户端。 4.7 依赖注入(dependency injection) 上面,你学到了如何开发藕合松散的服务组件。但是,为了存取那些服务对象,你需要通过服务器的JNDI 来查 找存根对象(session bean)或消息队列(MDB)。JNDI查找是把客户端与实际的服务端实现解藕的关键步骤。但 是,直接使用一个字符串来进行JNDI查找并不优雅。有这样几个原因: ·客户端与服务端必须有一致的基于字符串的名字。它没有在编译时得到认证或在布署时得到检查。 ·从JNDI返回的服务对象的类型没有在编译时进行检查,有可能在运行时出现转换(casting)错误。 ·冗长的查找代码,有着自己的try-catch代码块,在应用之间是重复的和杂乱的 EJB 3.0,对任何POJO,提供了一个简单的和优雅的方法来解藕服务对象和资源。使用@EJB注释,你可以将EJB 存根对象注入到任何EJB 3.0容器管理的POJO 中。如果注释用在一个属性变量上,容器将会在它被第一次访问 之前赋值给它。在Jboss下一版本中@EJB注释从javax.annotation包移到了javax.ejb 。 下面的例子演示了怎样把HelloWorldBean无状态session bean的存根注入到InjectionBean类中。 InjectionBean.java //author:lihuoming package com.foshanshop.ejb3.impl; import com.foshanshop.ejb3.HelloWorld; import com.foshanshop.ejb3.Injection; import javax.annotation.EJB; import javax.ejb.Remote; import javax.ejb.Stateless; @Stateless @Remote ({Injection.class}) public class InjectionBean implements Injection { @EJB (beanName="HelloWorldBean") Jboss EJB3.0实例教程 版权所有:黎活明 HelloWorld helloworld; public String SayHello() { return helloworld.SayHello("注入者"); } } @EJB注释的beanName属性指定EJB的类名(不带包名),他的另一个属性mappedName指定Bean实例的JNDI名。 下面的片断演示了如何使用beanName 或mappedName属性查找HelloWorldBean 会话bean public class InjectionBean implements Injection { @EJB (beanName="HelloWorldBean") //@EJB (mappedName="HelloWorldBean/remote") HelloWorld helloworld; ….. @EJB注释如果被用在JavaBean风格的setter 方法上时,容器会在属性第一次使用之前,自动地用正确的参数调 用bean的setter 方法。下面的片断演示了这是如何做的 public class InjectionBean implements Injection { HelloWorld helloworld; @EJB (beanName="HelloWorldBean") public void setHelloworld(HelloWorld helloworld) { this.helloworld = helloworld; } ….. 下面是InjectionBean的Remote业务接口 Injection.java //author:lihuoming package com.foshanshop.ejb3; public interface Injection { public String SayHello(); } 下面是Session Bean的JSP 客户端代码: InjectionTest.jsp <%@ page contentType="text/html; charset=GBK"%> <%@ page import="com.foshanshop.ejb3.Injection, javax.naming.*, java.util.Properties"%> <% Properties props = new Properties(); props.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory"); props.setProperty("java.naming.provider.url", "localhost:1099"); props.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming"); InitialContext ctx; try { Jboss EJB3.0实例教程 版权所有:黎活明 ctx = new InitialContext(props); Injection injection = (Injection) ctx.lookup("InjectionBean/remote"); out.println(injection.SayHello()); } catch (NamingException e) { out.println(e.getMessage()); } %> @EJB注释只能注入EJB存根对象,除@EJB注释之外,EJB 3.0也支持@Resource注释来注入来自JNDI的任何 资源。下面的例子中演示了如何注入数据源。"java:/DefaultMySqlDS"是数据源DefaultMySqlDS的全局JNDI名。 有关数据源的配置请参考后面章节“JBoss数据源的配置” //author:lihuoming package com.foshanshop.ejb3.impl; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import com.foshanshop.ejb3.HelloWorld; import com.foshanshop.ejb3.Injection; import javax.annotation.EJB; import javax.annotation.Resource; import javax.ejb.Remote; import javax.ejb.Stateless; import javax.sql.DataSource; @Stateless @Remote ({Injection.class}) public class InjectionBean implements Injection { @EJB (beanName="HelloWorldBean") HelloWorld helloworld; @Resource (mappedName="java:/DefaultMySqlDS") DataSource myDb; public String SayHello() { String str = ""; try { Connection conn = myDb.getConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT studentName FROM student"); if (rs.next()) { str = rs.getString(1); } rs.close(); Jboss EJB3.0实例教程 版权所有:黎活明 stmt.close(); } catch (SQLException e) { e.printStackTrace(); } return helloworld.SayHello(str); } } 如果JNDI对象在本地(java:comp/env)JNDI目录中,你只需给定他的映谢名称即可,不需要带前缀,如下面例子注 入一个消息connection factory和一个messaging queue @Resource (mappedName="ConnectionFactory") QueueConnectionFactory factory; @Resource (mappedName="queue/A") Queue queue; 对于"well-known"对象,@Resource注释可以不指定JNDI名就能注入他们,他通过变量的类型就能获得他的JNDI 名。下面是一些例子。 @Resource TimerService tms; @Resource SessionContext ctx; 和@EJB注释相同, @Resource注释也可以被用在JavaBean风格的setter 方法上。 本例子的EJB源代码在DependencyInjection文件夹(源代码下载:http://www.foshanshop.net/),项目中使用到的类 库在上级目录lib 文件夹下。要恢复DependencyInjection项目的开发环境请参考第三章”如何恢复本书配套例子的 开发环境”,要发布本例子EJB,你可以执行Ant 的deploy任务。发布前确保HelloWorld.jar 已经发布到Jboss中。 本例子的客户端代码在EJBTest文件夹,要发布客户端应用,你可以执行Ant 的deploy任务。通过 http://localhost:8080/EJBTest/InjectionTest.jsp访问客户端。 4.8 定时服务(Timer Service) 定时服务用作在一段特定的时间后执行某段程序,估计各位在不同的场合中已经使用过。下面就直接介绍EJB3.0 定时服务的开发过程。定时服务的开发过程与会话Bean的开发过程大致相同,但比会话Bean多了几个操作,那 就是使用容器对象SessionContext创建定时器,并使用@Timeout 注释声明定时器方法。 下面定义一个每隔3 秒触发一次事件的定时器,当定时事件触发次数超过5 次的时候便终止定时器的执行。 TimerServiceBean.java //author:lihuoming package com.foshanshop.ejb3.impl; import java.util.Date; import com.foshanshop.ejb3.TimerService; import javax.annotation.Resource; import javax.ejb.Remote; Jboss EJB3.0实例教程 版权所有:黎活明 import javax.ejb.SessionContext; import javax.ejb.Stateless; import javax.ejb.Timeout; import javax.ejb.Timer; @Stateless @Remote ({TimerService.class}) public class TimerServiceBean implements TimerService { private int count = 1; private @Resource SessionContext ctx; public void scheduleTimer(long milliseconds){ count = 1; ctx.getTimerService().createTimer(new Date(new Date().getTime() + milliseconds),milliseconds, "大 家好,这是我的第一个定时器"); } @Timeout public void timeoutHandler(Timer timer) { System.out.println("---------------------"); System.out.println("定时器事件发生,传进的参数为: " + timer.getInfo()); System.out.println("---------------------"); if (count>=5){ timer.cancel();//如果定时器触发5 次,便终止定时器 } count++; } } 通过依赖注入@Resource SessionContext ctx,我们获得SessionContext对象,调用ctx.getTimerService().createTimer (Date arg0, long arg1, Serializable arg2)方法创建定时器,三个参数的含义如下: Date arg0 定时器启动时间,如果传入时间小于现在时间,定时器会立刻启动。 long arg1 间隔多长时间后再次触发定时事件。单位:毫秒 Serializable arg2 你需要传给定时器的参数,该参数必须实现Serializable接口。 当定时器创建完成后,我们还需声明定时器方法。定时器方法的声明很简单,只需在方法上面加入@Timeout 注 释,另外定时器方法必须遵守如下格式: void XXX(Timer timer) 在定时事件发生时,此方法将被执行。 下面是TimerServiceBean的Remote业务接口 TimerService.java //author:lihuoming package com.foshanshop.ejb3; Jboss EJB3.0实例教程 版权所有:黎活明 public interface TimerService { public void scheduleTimer(long milliseconds); } 下面是TimerServiceBean的JSP 客户端代码: TimerServiceTest.jsp <%@ page contentType="text/html; charset=GBK"%> <%@ page import="com.foshanshop.ejb3.TimerService, javax.naming.*, java.util.Properties"%> <% Properties props = new Properties(); props.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory"); props.setProperty("java.naming.provider.url", "localhost:1099"); props.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming"); try { InitialContext ctx = new InitialContext(props); TimerService timer = (TimerService) ctx.lookup("TimerServiceBean/remote"); timer.scheduleTimer((long)3000); out.println("定时器已经启动,请观察Jboss控制台输出,如果定时器触发5次,便终止定时器 "); } catch (Exception e) { out.println(e.getMessage()); } %> 本例子的EJB源代码在TimerService文件夹(源代码下载:http://www.foshanshop.net/),项目中使用到的类库在上 级目录lib 文件夹下。要恢复TimerService项目的开发环境请参考第三章”如何恢复本书配套例子的开发环境”,要 发布本例子EJB (确保配置了环境变量JBOSS_HOME 及启动了Jboss),你可以执行Ant 的deploy任务。 本例子的客户端代码在EJBTest文件夹,要发布客户端应用,你可以执行Ant 的deploy任务。通过 http://localhost:8080/EJBTest/TimerServiceTest.jsp访问客户端。 4.9 安全服务(Security service) 当公司网络外部的用户需要访问你编写的应用程序或受安全保护的资源时,很多Java 程序员为他们的应用程序创 建自己的安全模块。大多数这些模块都是针对具体的应用程序,这样在下一个应用程序需要安全保护时,程序员 又重新开始所有工作。构建自己的安全模块的另外一个缺点是,在应用程序变得越来越复杂时,安全需求也会变 得很复杂。 使用Java 验证和授权服务(JAAS)可以很好地解决上面的问题,你可以用它来管理应用程序的安全性。JAAS 具有两个特性:验证(Authentication)和授权(authorization),认证是完成用户名和密码的匹配校验;授权是决 定用户可以访问哪些资源,授权是基于角色的。Jboss服务器提供了安全服务来进行用户认证和根据用户规则来 限制对POJO的访问。对每一个POJO 来说,你可以通过使用@SecurityDomain注释为它指定一个安全域, 安全域 Jboss EJB3.0实例教程 版权所有:黎活明 告诉容器到哪里去找密码和用户角色列表。JBoss中的other 域表明文件是classpath中的users.propertes 和 roles.properties。这样,对每一个方法来说,我们可以使用一个安全限制注释来指定谁可以运行这个方法。比如, 下面的例子,容器对所有试图调用AdminUserMethod()的用户进行认证,只允许拥有AdminUser 角色的用户运行 它。如果你没有登录或者没有以管理员的身份登录,一个安全意外将会抛出。 本例定义了三种角色,各角色的含义如下: 角色名称 说明 AdminUser 系统管理员角色 DepartmentUser 各事业部用户角色 CooperateUser 公司合作伙伴角色 本例设置了三个用户,各用户名及密码如下: 用户名 密码 lihuoming 123456 zhangfeng 111111 wuxiao 123 本例使用了Jboss默认的安全域”other”, “other”安全域告诉容器到classpath中的users.propertes 和roles.properties 找密码和用户角色列表。”other”安全域的定义在[jboss安装目录]/server/all/conf/login-config.xml文件。内容如下: “other”安全域默认情况下是不允许匿名用户(不提供用户名及密码)访问的,如果你想使匿名用户也可访问通过 @PermitAll 注释定义的资源,可以修改”other”安全域配置,修改片断如下(蓝色部分): AnonymousUser 注意:本例子采用的是“other”域默认配置,没有修改过login-config.xml文件。匿名用户无法访问本例子@PermitAll 注释的方法。要访问@PermitAll 注释的方法,必须提供users.propertes文件中的用户。 Jboss EJB3.0实例教程 版权所有:黎活明 下面我们就开始安全服务的具体开发: 开发的第一步是定义安全域,安全域的定义有两种方法: 第一种方法:通过Jboss发布文件(jboss.xml)进行定义(本例采用的方法),定义内容如下: jboss.xml(本例使用Jboss默认的安全域”other”) other AnonymousUser jboss.xml必须打进Jar 文件的META-INF目录。 第二种方法:通过@SecurityDomain注释进行定义,注释代码片断如下: import org.jboss.annotation.security.SecurityDomain; @Stateless @Remote ({SecurityAccess.class}) @SecurityDomain("other") public class SecurityAccessBean implements SecurityAccess{ 由于使用的是Jboss安全注释,程序采用了硬编码,不利于日后迁移到其他J2EE 服务器(如: WebLogic),所以 作者不建议使用这种方法定义安全域。 安全域定义好了,因为我们使用Jboss默认的安全域”other”,所以必须使用users.propertes 和roles.properties存储 用户名/密码及用户角色。 现在开发的第二步就是定义用户名,密码及用户的角色。用户名和密码定义在users.propertes文件,用户所属角 色定义在roles.properties文件。以下是这两个文件的具体配置: users.propertes(定义了本例使用的三个用户) lihuoming=123456 zhangfeng=111111 wuxiao=123 roles.properties(定义了三个用户所具有的角色,其中用户lihuoming 具有三种角色) lihuoming=AdminUser,DepartmentUser,CooperateUser zhangfeng=DepartmentUser wuxiao=CooperateUser 以上两个文件必须存放于类路径下。在进行用户验证时,Jboss容器会自动寻找这两个文件。 开发的第三步就是为业务方法定义访问角色。本例定义了三个方法:AdminUserMethod(), DepartmentUserMethod(),AnonymousUserMethod(),第一个方法只允许具有AdminUser 角色的用户访问,第二个方 法只允许具有DepartmentUser 角色的用户访问,第三个方法允许所有角色的用户访问。下面是Session Bean代码。 SecurityAccessBean.java Jboss EJB3.0实例教程 版权所有:黎活明 package com.foshanshop.ejb3.impl; import com.foshanshop.ejb3.SecurityAccess; import javax.annotation.security.PermitAll; import javax.annotation.security.RolesAllowed; import javax.ejb.Remote; import javax.ejb.Stateless; @Stateless @Remote ({SecurityAccess.class}) public class SecurityAccessBean implements SecurityAccess{ @RolesAllowed({"AdminUser"}) public String AdminUserMethod() { return "具有管理员角色的用户才可以访问AdminUserMethod()方法"; } @RolesAllowed({"DepartmentUser"}) public String DepartmentUserMethod() { return "具有事业部门角色的用户才可以访问DepartmentUserMethod()方法"; } @PermitAll public String AnonymousUserMethod() { return "任何角色的用户都可以访问AnonymousUserMethod()方法, 注:用户必须存在 users.properties文件哦"; } } @RolesAllowed 注释定义允许访问方法的角色列表,如角色为多个,可以用逗号分隔。@PermitAll注释定义所有 的角色都可以访问此方法。 下面是SecurityAccessBean 的Remote 接口 SecurityAccess.java package com.foshanshop.ejb3; public interface SecurityAccess { public String AdminUserMethod(); public String DepartmentUserMethod(); public String AnonymousUserMethod(); } 到目前为止一个安全服务例子就开发完成。下面我们看看客户端如何访问具有安全验证的SecurityAccessBean。 SecurityAccessTest.jsp <%@ page contentType="text/html; charset=GBK"%> <%@ page import="com.foshanshop.ejb3.SecurityAccess, javax.naming.*, org.jboss.security.*, java.util.*"%> Jboss EJB3.0实例教程 版权所有:黎活明 <% Properties props = new Properties(); props.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory"); props.setProperty("java.naming.provider.url", "localhost:1099"); props.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming"); InitialContext ctx = new InitialContext(props); String user = request.getParameter("user"); String pwd = request.getParameter("pwd"); if (user!=null && !"".equals(user.trim())){ SecurityAssociation.setPrincipal(new SimplePrincipal(user.trim())); SecurityAssociation.setCredential(pwd.trim().toCharArray()); } SecurityAccess securityaccess = (SecurityAccess) ctx.lookup("SecurityAccessBean/remote"); try{ out.println("调用结果: "+ securityaccess.AdminUserMethod()+ " "); }catch(Exception e){ out.println(user+ "没有权限访问AdminUserMethod方法 "); } out.println("========================== "); try{ out.println("调用结果: "+ securityaccess.DepartmentUserMethod()+ " "); }catch(Exception e){ out.println(user+ "没有权限访问DepartmentUserMethod方法 "); } out.println("========================== "); try{ out.println("调用结果: "+ securityaccess.AnonymousUserMethod()+ " "); }catch(Exception e){ out.println(user+ "没有权限访问AnonymousUserMethod方法 "); } SecurityAssociation.clear(); %> Jboss EJB3.0实例教程 版权所有:黎活明 安全访问测试 安全访问测试 请输入你的用户名及密码 管理员 用户名: lihuoming 密码: 123456
事业部 用户名: zhangfeng 密码 111111
合作伙伴 用户名: wuxiao 密码 123
本例子的EJB源代码在SecurityWithPropertiesFile文件夹(源代码下载:http://www.foshanshop.net/),项目中使用 到的类库在上级目录lib 文件夹下。要恢复SecurityWithPropertiesFile项目的开发环境请参考第三章”如何恢复本 书配套例子的开发环境”,要发布本例子EJB (确保配置了环境变量JBOSS_HOME及启动了Jboss),你可以执行Ant 的deploy任务。 本例子的客户端代码在EJBTest文件夹,要发布客户端应用,你可以执行Ant 的deploy任务。通过 http://localhost:8080/EJBTest/SecurityAccessTest.jsp访问客户端。 4.9.1 自定义安全域 把用户名/密码及角色存放在users.propertes 和roles.properties文件,不便于日后的管理。大多数情况下我们都希 望把用户名/密码及角色存放在数据库中。为此,我们需要自定义安全域,下面的例子定义了一个名为foshanshop 的安全域,他采用数据库存储用户名及角色。 安全域在[jboss安装目录]/server/all/conf/login-config.xml文件中定义,本例配置片断如下: java:/DefaultMySqlDS Jboss EJB3.0实例教程 版权所有:黎活明 select password from sys_user where name=? select rolename,'Roles' from sys_userrole where username=? AnonymousUser 上面使用了Jboss数据库登录模块(org.jboss.security.auth.spi.DatabaseServerLoginModule),他的dsJndiName属性(数 据源JNDI名)使用DefaultMySqlDS 数据源(本教程自定义的数据源,关于数据源的配置请参考后面章节:JBoss 数据源的配置), principalsQuery属性定义Jboss通过给定的用户名如何获得密码, rolesQuery属性定义Jboss 通过给定的用户名如何获得角色列表,注意:SQL中的'Roles'常量字段不能去掉。 unauthenticatedIdentity属 性允许匿名用户(不提供用户名及密码)访问。 “foshanshop”安全域使用的sys_user和sys_userrole表是自定义表,实际项目开发中你可以使用别的表名。 sys_user 表必须含有用户名及密码两个字段,字段类型为字符型,至于字符长度视你的应用而定。 sys_userrole 表必须含有用户名及角色两个字段,字段类型为字符型,字符长度也视你的应用而定。 下面是sys_user,sys_userrole 表的具体定义(实际项目开发中字段名可以自定义) sys_user 表: 字段名 属性 name(主键) varchar(45) NOT NULL password varchar(45) NOT NULL DDL: CREATE TABLE `sys_user` ( `name` varchar(45) NOT NULL, `password` varchar(45) NOT NULL, PRIMARY KEY (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=gb2312; sys_userrole 表: 字段名 属性 username(主键) varchar(45) NOT NULL rolename(主键) varchar(45) NOT NULL DDL: CREATE TABLE `sys_userrole` ( `username` varchar(45) NOT NULL, `rolename` varchar(45) NOT NULL, PRIMARY KEY (`username`,`rolename`) ) ENGINE=InnoDB DEFAULT CHARSET=gb2312; 为了使用本例子,你还需往sys_user,sys_userrole 表添加数据: name password lihuoming 123456 zhangfeng 111111 Jboss EJB3.0实例教程 版权所有:黎活明 wuxiao 123 username rolename lihuoming AdminUser lihuoming DepartmentUser lihuoming CooperateUser wuxiao CooperateUser zhangfeng DepartmentUser 在完成上面的配置后,我们就可以使用”foshanshop“安全域了,配置内容如下: jboss.xml foshanshop AnonymousUser jboss.xml必须打进Jar 文件的META-INF目录。 本节我们仍然使用上节的SecurityAccessBean 及Jsp客户端,代码这里不再展示。 本例子的EJB源代码在SecurityWithDB文件夹(源代码下载:http://www.foshanshop.net/),项目中使用到的类库在 上级目录lib文件夹下。要恢复SecurityWithDB项目的开发环境请参考第三章”如何恢复本书配套例子的开发环境”, 要发布本例子EJB (确保配置了环境变量JBOSS_HOME 及启动了Jboss),你可以执行Ant 的deploy任务。 本例子的客户端代码在EJBTest文件夹,要发布客户端应用,你可以执行Ant 的deploy任务。通过 http://localhost:8080/EJBTest/SecurityAccessTest.jsp访问客户端。 注意:别忘了配置数据源、“foshanshop”安全域、数据库表及例子所需数据。 第五章 消息驱动Bean (Message Driven Bean) 消息驱动Bean(MDB)是设计用来专门处理基于消息请求的组件。一个MDB类必须实现MessageListener 接口。当 容器检测到bean守候的队列一条消息时,就调用onMessage()方法,将消息作为参数传入。MDB在OnMessage() 中决定如何处理该消息。你可以用注释来配置MDB 监听哪一条队列。当MDB 部署时,容器将会用到其中的注 释信息。 当一个业务执行的时间很长,而执行结果无需实时向用户反馈时,很适合使用消息驱动Bean。如订单成功后给用 户发送一封电子邮件或发送一条短信等。下面的例子在用户下订单完成后,打印一份配送单。好让配送员根据地 址把商品送到客人手中。代码如下: PrintBean.java Jboss EJB3.0实例教程 版权所有:黎活明 //author:lihuoming package com.foshanshop.ejb3.impl; import javax.ejb.ActivationConfigProperty; import javax.ejb.MessageDriven; import javax.jms.Message; import javax.jms.MessageListener; import javax.jms.TextMessage; @MessageDriven(activationConfig = { @ActivationConfigProperty(propertyName="destinationType", propertyValue="javax.jms.Queue"), @ActivationConfigProperty(propertyName="destination", propertyValue="queue/foshanshop") }) public class PrintBean implements MessageListener { public void onMessage(Message msg) { try { TextMessage tmsg = (TextMessage) msg; this.print(tmsg.getText()); } catch (Exception e){ e.printStackTrace(); } } private void print(String content){ System.out.println(content); } } 上面通过@MessageDriven注释指明这是一个消息驱动Bean,并使用@ActivationConfigProperty注释配置消息的 各种属性,其中destinationType属性指定消息的类型,消息有两种类型topics 和queues,下面是这两种消息类型 的介绍: Topics 可以有多个客户端。用topic发布允许一对多,或多对多通讯通道。消息的产生者被叫做publisher, 消息接 受者叫做subscriber。destinationType属性对应值:javax.jms.Topic Queue 仅仅允许一个消息传送给一个客户。一个发送者将消息放入消息队列,接受者从队列中抽取并得到消息, 消息就会在队列中消失。第一个接受者抽取并得到消息后,其他人就不能再得到它。destinationType属性对应值: javax.jms.Queue destination属性用作指定消息路径,消息驱动Bean在发布时,如果路径不存在,容器会自动创建该路径,当容器 关闭时该路径会自动被删除。 运行本例子,当一个消息到达queue/foshanshop队列时,就会触发onMessage方法,消息作为一个参数传入,在 onMessage方法里面得到消息体并调用print 方法把消息内容打印到控制台上。 MessageDrivenBeanTest.jsp Jboss EJB3.0实例教程 版权所有:黎活明 <%@ page contentType="text/html; charset=GBK"%> <%@ page import="javax.naming.*, java.text.*, javax.jms.*, java.util.Properties"%> <% QueueConnection cnn = null; QueueSender sender = null; QueueSession sess = null; Queue queue = null; try { Properties props = new Properties(); props.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory"); props.setProperty("java.naming.provider.url", "localhost:1099"); props.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming"); InitialContext ctx = new InitialContext(props); QueueConnectionFactory factory = (QueueConnectionFactory) ctx.lookup("ConnectionFactory"); cnn = factory.createQueueConnection(); sess = cnn.createQueueSession(false, QueueSession.AUTO_ACKNOWLEDGE); queue = (Queue) ctx.lookup("queue/foshanshop"); } catch (Exception e) { out.println(e.getMessage()); } TextMessage msg = sess.createTextMessage("佛山人您好,这是我的第一个消息驱动Bean"); sender = sess.createSender(queue); sender.send(msg); sess.close (); out.println("消息已经发送出去了,你可以到JBoss控制台查看Bean的输出"); %> 上面的JSP客户端用来向queue/foshanshop消息队列发送一条消息。客户端发送消息一般有以下步骤: (1) 得到一个JNDI初始化上下文(Context); 例子对应代码: Properties props = new Properties(); props.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory"); props.setProperty("java.naming.provider.url", "localhost:1099"); props.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming"); InitialContext ctx = new InitialContext(props); (2) 根据上下文来查找一个连接工厂TopicConnectFactory/ QueueConnectionFactory (有两种连接工厂,根据是 topic/queue来使用相应的类型); 例子对应代码: QueueConnectionFactory factory = (QueueConnectionFactory) ctx.lookup("ConnectionFactory"); (3) 从连接工厂得到一个连接(Connect 有两种[TopicConnection/ QueueConnection]); 例子对应代码:cnn = factory.createQueueConnection(); (4) 通过连接来建立一个会话(Session); Jboss EJB3.0实例教程 版权所有:黎活明 例子对应代码:sess = cnn.createQueueSession(false, QueueSession.AUTO_ACKNOWLEDGE); 这句代码意思是:建立不需要事务的并且能自动接收消息收条的会话,在非事务Session 中,消息传递的方 式有三种: Session.AUTO_ACKNOWLEDGE :当客户机调用的receive方法成功返回,或当MessageListenser 成功处理 了消息,session将会自动接收消息的收条。 Session.CLIENT_ACKNOWLEDGE :客户机通过调用消息的acknowledge方法来接收消息。接收发生在 session层。接收到一个被消费的消息时,将自动接收该session已经消费的所有消息。例如:如果消息的消 费者消费了10 条消息,然后接收15 个被传递的消息,则前面的10 个消息的收据都会在这15 个消息中被接 收。 Session.DUPS_ACKNOWLEDGE :指示session缓慢接收消息。 (5) 查找目的地(Topic/ Queue); 例子对应代码:queue = (Queue) ctx.lookup("queue/foshanshop"); (6) 根据会话以及目的地来建立消息制造者(TopicPublisher/QueueSender)和消费者(TopicSubscriber/ QueueReceiver). 例子对应代码: TextMessage msg = sess.createTextMessage("佛山人您好,这是我的第一个消息驱动Bean"); sender = sess.createSender(queue); sender.send(msg); 下面是消息驱动Bean发布及JSP发送消息后的控制台输出。 本例子的EJB源代码在MessageDrivenBean文件夹(源代码下载:http://www.foshanshop.net/),项目中使用到的类 库在上级目录lib 文件夹下。要恢复MessageDrivenBean项目的开发环境请参考第三章”如何恢复本书配套例子的 开发环境”,要发布本例子EJB (确保配置了环境变量JBOSS_HOME 及启动了Jboss),你可以执行Ant 的deploy任 务。 本例子的客户端代码在EJBTest文件夹,要发布客户端应用,你可以执行Ant 的deploy任务。通过 http://localhost:8080/EJBTest/MessageDrivenBeanTest.jsp访问客户端。 第六章 实体Bean(Entity Bean) 现在EJB3 实体Bean是纯粹的POJO,可以像开发一般的java bean一样编程,只需做少量的注释来定义实体关系 及O/R映射等。 Jboss EJB3.0实例教程 版权所有:黎活明 6.1 实体Bean 的组成文件persistence.xml 配置 一个实体Bean由实体类和persistence.xml文件组成。persistence.xml文件在Jar 文件的META-INF目录。 persistence.xml文件指定实体Bean使用的数据源及EntityManager 对象的默认行为。persistence.xml文件的配置说 明如下: java:/DefaultMySqlDS persistence-unit 节点可以有一个或多个,每个persistence-unit 节点定义了持久化内容名称、使用的数据源名称及 Hibernate属性。name 属性用作设置持久化名称。jta-data-source 节点用作指定实体Bean使用的数据源名称(如 何配置数据源请参考下节 “Jboss数据源的配置”),指定数据源名称时java:/ 前缀不能缺少,数据源名称大小写敏 感。properties 节点用作指定Hibernate的各项属性,如果hibernate.hbm2ddl.auto的值设为create-drop,在实体Bean 发布及卸载时将自动创建及删除相应数据库表(注意:Jboss服务器启动或关闭时会引发实体Bean的发布及卸载)。 Properties 节点的可用属性及默认值你可以在 [Jboss安装目录] /server/all/deploy/ejb3.deployer/META-INF/persistence.properties文件中看见. 小提示:如果你的表已经存在,并且想保留数据,发布实体bean时可以把hibernate.hbm2ddl.auto的值设为none或 update,以后为了实体bean 的改动能反应到数据表,建议使用update,这样实体Bean添加一个属性时能同时在数 据表增加相应字段。 6.2 JBoss数据源的配置 Jboss有一个默认的数据源DefaultDS,他使用Jboss内置的HSQLDB数据库。实际应用中你可能使用不同的 数据库,如MySql、MsSqlServer、Oracle等。各种数据库的数据源配置模版你可以在[Jboss安装目 录]/docs/examples/jca 目录中找到,默认名称为:数据库名+ -ds.xml 。 不管你使用那种数据库都需要把他的驱动类Jar 包放置在[Jboss 安装目录]/server/all/lib 目录下,放置后需要启 动Jboss服务器。 本教程使用的数据库是mysql-5.0.22 和Ms Sql Server2000 ,使用驱动Jar 包如下: Mysql :mysql-connector-java-3.1.13-bin.jar Ms Sql Server2000 :msbase.jar, mssqlserver.jar, msutil.jar 上面的Jar 文件你可以在网上下载或在例子源码的lib 文件夹下得到(例子源码下载地址: http://www.foshanshop.net/)。 下面介绍Mysql和Ms Sql Server2000的数据源配置,数据源配置文件的取名格式必须为 xxx–ds.xml ,如: mysql-ds.xml ,mssqlserver-ds.xml,oracle-ds.xml 。 数据源文件配置好后需要放置在[jboss安装目录]/server/config-name/deploy目录下,本教程采用的配置名为:all, 所以路径为[jboss安装目录]/server/all/deploy目录 Jboss EJB3.0实例教程 版权所有:黎活明 6.2.1 MySql 数据源的配置 下面定义一个名为DefaultMySqlDS的Mysql数据源,连接数据库为foshanshop,数据库登录用户名为root,密码 为123456,数据库驱动类为org.gjt.mm.mysql.Driver。大家只需修改数据库名及登录用户名密码就可以直接使用。 mysql-ds.xml DefaultMySqlDS jdbc:mysql://localhost:3306/foshanshop?useUnicode=true&characterEncoding=GBK org.gjt.mm.mysql.Driver root 123456 org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter mySQL 6.2.2 Ms Sql Server2000 数据源的配置 下面定义一个名为MSSQLDS的Ms Sql Server 数据源,连接数据库为foshanshop,数据库登录用户名为sa,密码 为123456,数据库驱动类为com.microsoft.jdbc.sqlserver.SQLServerDriver。大家只需修改数据库名及登录用户名 密码就可以直接使用。 mssqlserver-ds.xml MSSQLDS jdbc:microsoft:sqlserver:// localhost:1433;DatabaseName=foshanshop com.microsoft.jdbc.sqlserver.SQLServerDriver sa 123456 MS SQLSERVER2000 Jboss EJB3.0实例教程 版权所有:黎活明 6.3 实体Bean 发布前的准备工作 1. 配置数据源并放置在[jboss 安装目录]/server/all/deploy 目录,把数据库驱动Jar 包放置在[Jboss 安装目 录]/server/all/lib 目录下,放置后需要重启Jboss服务器。如果数据源已经存在就不需要配置。 2. 配置persistence.xml文件,在文件中指定使用的源据源及各项参数。 3. 把实体类和persistence.xml文件打成Jar,persistence.xml 放在jar 文件的META-INF目录 6.4 单表映射的实体Bean 开发前先介绍需要映射的数据库表 person 字段名称 字段类型属性 描述 personid (主键) Int(11) not null 人员ID PersonName Varchar(32) not null 姓名 sex Tinyint(1) not null 性别 age Smallint(6) not null 年龄 birthday datetime null 出生日期 建立与Person表进行映射的实体Bean Person.java package com.foshanshop.ejb3.bean; import java.util.Date; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name = "Person") public class Person { private Integer personid; private String name; private boolean sex; private Short age; private Date birthday; @Id @GeneratedValue public Integer getPersonid() { return personid; Jboss EJB3.0实例教程 版权所有:黎活明 } public void setPersonid(Integer personid) { this.personid = personid; } @Column(name = "PersonName",nullable=false,length=32) public String getName() { return name; } public void setName(String name) { this.name = name; } @Column(nullable=false) public boolean getSex() { return sex; } public void setSex(boolean sex) { this.sex = sex; } @Column(nullable=false) public Short getAge() { return age; } public void setAge(Short age) { this.age = age; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } } 通过Person类可以看到开发实体Bean非常简单,就像开发一般的java bean一样。@Entity注释指明这是一个实 体Bean,每个实体Bean类映射数据库中的一个表,@Table注释的name属性指定映射的数据表名称,Person类 映射的数据表为Person。实体Bean的每个实例代表数据表中的一行数据,行中的一列对应实例中的一个属性。 @Column注释定义了映射到列的所有属性,如列名是否唯一,是否允许为空,是否允许更新等,他的属性介绍如 下: ·name: 映射的列名。如:映射Person表的PersonName列,可以在name属性的getName 方法上面加入 @Column(name = "PersonName"),如果不指定映射列名,容器将属性名称作为默认的映射列名。 ·unique: 是否唯一 Jboss EJB3.0实例教程 版权所有:黎活明 ·nullable: 是否允许为空 ·length: 对于字符型列,length属性指定列的最大字符长度 ·insertable: 是否允许插入 ·updatable: 是否允许更新 ·columnDefinition: 定义建表时创建此列的DDL ·secondaryTable: 从表名。如果此列不建在主表上(默认建在主表),该属性定义该列所在从表的名字。 @Id 注释指定personid属性为表的主键,它可以有多种生成方式: ·TABLE:容器指定用底层的数据表确保唯一。 ·SEQUENCE:使用数据库的SEQUENCE 列来保证唯一 ·IDENTITY:使用数据库的INDENTIT列来保证唯一 ·AUTO:由容器挑选一个合适的方式来保证唯一 ·NONE:容器不负责主键的生成,由调用程序来完成。 @GeneratedValue注释定义了标识字段的生成方式,本例personid的值由MySQL数据库自动生成。 为了使用上面的实体Bean,我们定义一个Session Bean作为他的使用者。下面是Session Bean 的业务接口,他定 义了两个业务方法insertPerson和getPersonNameByID,insertPerson用作添加一个Person,getPersonNameByID 根 据personid获取人员的姓名。 PersonDAO.java package com.foshanshop.ejb3; import java.util.Date; public interface PersonDAO { public boolean insertPerson(String name, boolean sex,short age, Date birthday); public String getPersonNameByID(int personid); } 下面是Session Bean的实现 PersonDAOBean.java package com.foshanshop.ejb3.impl; import java.util.Date; import javax.ejb.Remote; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import com.foshanshop.ejb3.PersonDAO; import com.foshanshop.ejb3.bean.Person; @Stateless @Remote ({PersonDAO.class}) public class PersonDAOBean implements PersonDAO { @PersistenceContext protected EntityManager em; Jboss EJB3.0实例教程 版权所有:黎活明 public String getPersonNameByID(int personid) { Person person = em.find(Person.class, Integer.valueOf(personid)); return person.getName(); } public boolean insertPerson(String name, boolean sex,short age, Date birthday) { try { Person person = new Person(); person.setName(name); person.setSex(sex); person.setAge(Short.valueOf(age)); person.setBirthday(birthday); em.persist(person); } catch (Exception e) { e.printStackTrace(); return false; } return true; } } 上面我们使用到了一个对象:EntityManager em,EntityManager 是由EJB容器自动地管理和配置的,不需要用户 自己创建,他用作操作实体Bean。关于他的更多介绍请参考持久化实体管理器EntityManager。 上面em.find()方法用作查询主键ID 为personid的记录。em.persist()方法用作向数据库插入一条记录。大家可能感 觉奇怪,在类中并没有看到对EntityManager em进行赋值,后面却可以直接使用他。这是因为在实体Bean加载 时,容器通过@PersistenceContext注释动态注入EntityManager 对象。 如果persistence.xml文件中配置了多个不同的持久化内容。你需要指定持久化名称注入EntityManager 对象,可以 通过@PersistenceContext注释的unitName属性进行指定,例: @PersistenceContext(unitName="foshanshop") EntityManager em; 如果只有一个持久化内容配置,不需要明确指定。 下面是persistence.xml文件的配置: java:/DefaultMySqlDS 到目前为此,实体bean应用已经开发完成。我们按照上节“实体Bean发布前的准备工作”介绍的步骤把他打成 Jar 文件并发布到Jboss中。 在发布前请检查persistence.xml 文件中使用的数据源是否配置(如何配置数据源请参考 “Jboss 数据源的配置”), Jboss EJB3.0实例教程 版权所有:黎活明 数据库驱动Jar 文件是否放进了[Jboss安装目录]/server/all/lib 目录下(放进后需要重启Jboss)。 因为在persistence.xml文件中指定的Hibernate属性是 ,该属性值指定在实体Bean发布及卸载时将自动创建及删除表。当实体bean发布成功后, 我们可以查看数据库中是否生成了Person表。生成的表如下图: 下面是JSP客户端代码: EntityBeanTest.jsp <%@ page contentType="text/html; charset=GBK"%> <%@ page import="com.foshanshop.ejb3.PersonDAO, javax.naming.*, java.util.Properties, java.util.Date, java.text.SimpleDateFormat"%> <% Properties props = new Properties(); props.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory"); props.setProperty("java.naming.provider.url", "localhost:1099"); props.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming"); InitialContext ctx = new InitialContext(props); try { PersonDAO persondao = (PersonDAO) ctx.lookup("PersonDAOBean/remote"); SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); persondao.insertPerson("黎活明", true, (short)26,formatter.parse("1980-9-30"));// 添加一个人 out.println(persondao.getPersonNameByID(1)); //取personid为1的人 } catch (Exception e) { out.println(e.getMessage()); Jboss EJB3.0实例教程 版权所有:黎活明 } %> 上面代码往数据库添加一个人,然后取personid为1 的人员姓名。 本例子的EJB源代码在EntityBean文件夹(源代码下载:http://www.foshanshop.net/),项目中使用到的类库在上级 目录lib 文件夹下。要恢复EntityBean项目的开发环境请参考第三章”如何恢复本书配套例子的开发环境”,要发布 本例子EJB (确保配置了环境变量JBOSS_HOME 及启动了Jboss),你可以执行Ant 的deploy任务。例子使用的数 据源配置文件是mysql-ds.xml,你可以在下载的文件中找到。数据库名为foshanshop 本例子的客户端代码在EJBTest文件夹,要发布客户端应用,你可以执行Ant 的deploy任务。通过 http://localhost:8080/EJBTest/EntityBeanTest.jsp访问客户端。 6.5 持久化实体管理器EntityManager EntityManager 是用来对实体Bean进行操作的辅助类。他可以用来产生/删除持久化的实体Bean,通过主键查找 实体bean,也可以通过EJB3 QL语言查找满足条件的实体Bean。EntityManager 的获取前面已经介绍过,可以通 过@PersistenceContext注释由EJB容器动态注入,例: @PersistenceContext(unitName="foshanshop") EntityManager em; 下面介绍EntityManager 常用的API 6.5.1 Entity 获取find() 如果知道Entity的唯一标示符,我们可以用find()方法来获得Entity。 @PersistenceContext protected EntityManager em; … Person person = em.find(Person.class, Integer.valueOf(personid)); 6.5.2 添加persist() 保存Entity到数据库。 @PersistenceContext protected EntityManager em; … Person person = new Person(); person.setName(name); //把数据保存进数据库中 em.persist(person); Jboss EJB3.0实例教程 版权所有:黎活明 6.5.3 更新Merge() 把Entity更新到数据库。 @PersistenceContext protected EntityManager em; … Person person = em.find(Person.class, Integer.valueOf(personid)); //更新数据 em.merge (person); 6.5.4 删除Remove() 把Entity从到数据库中删除。 @PersistenceContext protected EntityManager em; … Person person = em.find(Person.class, Integer.valueOf(personid)); //如果级联关系cascade=CascadeType.ALL,在删除person时候,也会把级联对象删除。把cascade 属性设为cascade=CascadeType.REMOVE 有同样的效果。 em.remove (person); 6.5.5 执行EJB3 QL 操作createQuery() @PersistenceContext protected EntityManager em; … Query query = em.createQuery("select p from Person p where p. name=’黎明’"); List result = query.getResultList(); Iterator iterator = result.iterator(); while( iterator.hasNext() ){ //处理Person } … // 执行更新语句 Query query = em.createQuery("update Person as p set p.name =?1 where p. personid=?2"); query.setParameter(1, “黎明” ); query.setParameter(2, new Integer(1) ); int result = query.executeUpdate(); //影响的记录数 … // 执行更新语句 Query query = em.createQuery("delete from Person"); Jboss EJB3.0实例教程 版权所有:黎活明 int result = query.executeUpdate(); //影响的记录数 6.5.6 执行SQL 操作createNativeQuery() 注意这里操作的是SQL语句,并非EJB3 QL,千万别搞晕了。 @PersistenceContext protected EntityManager em; … //我们可以让EJB3 Persistence运行环境将列值直接填充入一个Entity的实例,并将实例作为结果 返回. Query query = em.createNativeQuery("select * from person", Person.class); List result = query.getResultList(); if (result!=null){ Iterator iterator = result.iterator(); while( iterator.hasNext() ){ Person person= (Person)iterator.next(); ….. } } … // 直接通过SQL执行更新语句 Query query = em.createNativeQuery("update person set age=age+2"); query.executeUpdate(); 6.6 关系/对象映射 6.6.1 映射的表名或列名与数据库保留字同名时的处理 如果应用采用的数据库是Mysql,当映射的表名或列名与数据库保留字同名时,Hibernate 转绎成的SQL 在执行 时将会出错。 如: @Entity @Table(name = "Order") public class Order implements Serializable { 表名Order 与排序保留字 “Order“相同,导致SQL语法出错。 针对上面的情况,作者在Hibernate 文档中没有找到相关的解决方案。在此作者采用了一种变通的方法来解决此 问题。该方法针对具体数据库,不利于数据库移植。建议大家在不得已的情况下使用。 可以用``字符把Order 括起来。如下: Jboss EJB3.0实例教程 版权所有:黎活明 @Entity @Table(name = "`Order`") public class Order implements Serializable { 列名与保留字同名的处理方法如上,如列名为group @Column(name = "`group`") public String getGroup() { return group; } 如果数据库是Sqlserver 可以用 [] 把表名或列名括起来。Sqlserver 不加[]也能执行成功,建议在出错的情况下使 用[]。 6.6.2 一对多及多对一映射 现实应用中存在很多一对多的情况,如一项订单中存在一个或多个订购项。下面就以订单为例介绍存在一对多及 多对一双向关系的实体bean开发。 需要映射的数据库表 orders 字段名称 字段类型属性 描述 orderid Int 订单号 amount float 订单金额 createdate datetime 订单创建日期 orderitems 字段名称 字段类型属性 描述 id Int 订单项ID productname Varchar(255) 订购产品名称 price float 产品价格 order_id Int 订单号 双向一对多关系,一是关系维护端(owner side),多是关系被维护端(inverse side)。在关系被维护端建立外键列 指向关系维护端的主键列。 Order.java //author:lihuoming package com.foshanshop.ejb3.bean; import java.io.Serializable; import java.util.HashSet; import java.util.Date; import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.OrderBy; Jboss EJB3.0实例教程 版权所有:黎活明 import javax.persistence.Table; @SuppressWarnings("serial") @Entity @Table(name = "Orders") public class Order implements Serializable { private Integer orderid; private Float amount; private Set orderItems = new HashSet(); private Date createdate; @Id @GeneratedValue public Integer getOrderid() { return orderid; } public void setOrderid(Integer orderid) { this.orderid = orderid; } public Float getAmount() { return amount; } public void setAmount(Float amount) { this.amount = amount; } @OneToMany(mappedBy="order",cascade = CascadeType.ALL, fetch = FetchType.LAZY) @OrderBy(value = "id ASC") public Set getOrderItems() { return orderItems; } public void setOrderItems(Set orderItems) { this.orderItems = orderItems; } public Date getCreatedate() { return createdate; } public void setCreatedate(Date createdate) { this.createdate = createdate; } public void addOrderItem(OrderItem orderitem) { if (!this.orderItems.contains(orderitem)) { Jboss EJB3.0实例教程 版权所有:黎活明 this.orderItems.add(orderitem); orderitem.setOrder(this); } } public void removeOrderItem(OrderItem orderitem) { orderitem.setOrder(null); this.orderItems.remove(orderitem); } } 上面声明一个Set变量orderItems 用来存放多个OrderItem对象, 注释@OneToMany(mappedBy="order",cascade = CascadeType.ALL, fetch = FetchType.LAZY)指明Order 与OrderItem关联关系为一对多关系,下面是@OneToMany 注释的属性介绍: 1>targetEntity Class类型的属性。 定义关系类的类型,默认是该成员属性对应的类类型,所以通常不需要提供定义。 2>mappedBy String类型的属性。 定义类之间的双向关系。如果类之间是单向关系,不需要提供定义,如果类和类之间形成双向关系,我们就需要 使用这个属性进行定义,否则可能引起数据一致性的问题。 3>cascade CascadeType[]类型。 该属性定义类和类之间的级联关系。定义的级联关系将被容器视为对当前类对象及其关联类对象采取相同的操 作,而且这种关系是递归调用的。举个例子:Order 和OrderItem有级联关系,那么删除Order 时将同时删除它所 对应的OrderItem对象。而如果OrderItem还和其他的对象之间有级联关系,那么这样的操作会一直递归执行下去。 cascade的值只能从CascadeType.PERSIST(级联新建)、CascadeType.REMOVE(级联删除)、CascadeType.REFRESH (级联刷新)、CascadeType.MERGE(级联更新)中选择一个或多个。还有一个选择是使用CascadeType.ALL,表 示选择全部四项。 4>fatch FetchType类型的属性。 可选择项包括:FetchType.EAGER和FetchType.LAZY。前者表示关系类(本例是OrderItem类)在主类(本例是Order 类)加载的时候同时加载,后者表示关系类在被访问时才加载。默认值是FetchType. LAZY。 @OrderBy(value = "id ASC")注释指明加载OrderItem时按id的升序排序 addOrderItem和removeOrderItem方法用来添加/删除订单项。 OrderItem.java //author:lihuoming package com.foshanshop.ejb3.bean; import java.io.Serializable; import javax.persistence.CascadeType; import javax.persistence.Entity; Jboss EJB3.0实例教程 版权所有:黎活明 import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; @SuppressWarnings("serial") @Entity @Table(name = "OrderItems") public class OrderItem implements Serializable { private Integer id; private String productname; private Float price; private Order order; public OrderItem() { } public OrderItem(String productname, Float price) { this.productname = productname; this.price = price; } @Id @GeneratedValue public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getProductname() { return productname; } public void setProductname(String productname) { this.productname = productname; } public Float getPrice() { return price; } public void setPrice(Float price) { Jboss EJB3.0实例教程 版权所有:黎活明 this.price = price; } @ManyToOne(cascade=CascadeType.ALL,optional=false) @JoinColumn(name = "order_id") public Order getOrder() { return order; } public void setOrder(Order order) { this.order = order; } } 注释@ManyToOne指明OrderItem和Order之间为多对一关系,多个OrderItem实例关联的都是同一个Order对象。 @ManyToOne注释有四个属性:targetEntity、cascade、fetch和optional,前三个属性的具体含义和@OneToMany 注释的同名属性相同,但@ManyToOne注释的fetch属性默认值是FetchType.EAGER。 optional属性是定义该关联类对是否必须存在,值为false时,关联类双方都必须存在,如果关系被维护端不存在, 查询的结果为null。值为true时, 关系被维护端可以不存在,查询的结果仍然会返回关系维护端,在关系维护端 中指向关系被维护端的属性为null。optional属性的默认值是true。举个例:某项订单(Order)中没有订单项 (OrderItem),如果optional属性设置为false,获取该项订单(Order)时,得到的结果为null,如果optional属性 设置为true,仍然可以获取该项订单,但订单中指向订单项的属性为null。实际上在解释Order 与OrderItem的关 系成SQL时,optional属性指定了他们的联接关系optional=false联接关系为inner join, optional=true联接关系为 left join。 @JoinColumn(name = "order_id")注释指定OrderItem映射表的order_id列作为外键与Order 映射表的主键列关联。 为了使用上面的实体Bean,我们定义一个Session Bean作为他的使用者。下面是Session Bean 的业务接口,他定 义了三个业务方法insertOrder,getOrderByID 和getAllOrder,三个方法的业务功能是: insertOrder 添加一个订单(带两个订单项)进数据库 getOrderByID 获取指定订单号的订单 getAllOrder 获取所有订单 下面是Session Bean的业务接口及实现类 OrderDAO.java //author:lihuoming package com.foshanshop.ejb3; import java.util.List; import com.foshanshop.ejb3.bean.Order; public interface OrderDAO { public void insertOrder(); public Order getOrderByID(Integer orderid); public List getAllOrder(); } OrderDAOBean.java Jboss EJB3.0实例教程 版权所有:黎活明 //author:lihuoming package com.foshanshop.ejb3.impl; import java.util.Date; import java.util.List; import javax.ejb.Remote; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; import com.foshanshop.ejb3.OrderDAO; import com.foshanshop.ejb3.bean.Order; import com.foshanshop.ejb3.bean.OrderItem; @Stateless @Remote ({OrderDAO.class}) public class OrderDAOBean implements OrderDAO { @PersistenceContext protected EntityManager em; public void insertOrder(){ Order order = new Order(); order.setCreatedate(new Date()); order.addOrderItem(new OrderItem("笔记本电脑", new Float(13200.5))); order.addOrderItem(new OrderItem("U盘", new Float(620))); order.setAmount(new Float(13200.5+620)); em.persist(order); } public Order getOrderByID(Integer orderid) { Order order = em.find(Order.class, orderid); order.getOrderItems().size(); //因为是延迟加载,通过执行size()这种方式获取订单下的所有订单项 return order; } public List getAllOrder() { Query query = em.createQuery("select o from Order o inner join fetch o.orderItems order by o.orderid"); List result = query.getResultList(); return result; } } 上面有一点需要强调:当业务方法需要把一个实体Bean作为参数返回给客户端时,除了实体Bean本身需要实现 Serializable接口之外,如果关联类(OrderItem)是延迟加载,还需在返回实体Bean之前通过访问关联类的方式加载 Jboss EJB3.0实例教程 版权所有:黎活明 关联类。否则在客户端访问关联类时将会抛出加载例外。另外不管是否延迟加载,通过join fetch关联语句都可显 式加载关联类,如业务方法getAllOrder 。 下面是Session Bean的JSP 客户端代码: OneToManyTest.jsp <%@ page contentType="text/html; charset=GBK"%> <%@ page import="com.foshanshop.ejb3.OrderDAO, com.foshanshop.ejb3.bean.*, javax.naming.*, java.util.*"%> <% Properties props = new Properties(); props.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory"); props.setProperty("java.naming.provider.url", "localhost:1099"); props.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming"); InitialContext ctx = new InitialContext(props); try { OrderDAO orderdao = (OrderDAO) ctx.lookup("OrderDAOBean/remote"); orderdao.insertOrder(); /* Order order = orderdao.getOrderByID(new Integer(1)); out.println("订单总费用:"+ order.getAmount() +" ==============订单项 ================= "); if (order!=null){ Iterator iterator = order.getOrderItems().iterator(); while (iterator.hasNext()){ OrderItem SubOrder = (OrderItem) iterator.next(); out.println("订购产品:"+ SubOrder.getProductname() +" "); } }else{ out.println("没有找到相关订单"); } */ List list = orderdao.getAllOrder(); if (list!=null){ Integer orderid = null; for(int i=0; i"); Jboss EJB3.0实例教程 版权所有:黎活明 Iterator iterator = od.getOrderItems().iterator(); while (iterator.hasNext()){ OrderItem SubOrder = (OrderItem) iterator.next(); out.println("订购产品:"+ SubOrder.getProductname() +" "); } } } } }else{ out.println("获取不到订单列表"); } } catch (Exception e) { out.println(e.getMessage()); } %> 本例子的EJB源代码在OneToMany文件夹(源代码下载:http://www.foshanshop.net/),项目中使用到的类库在上 级目录lib 文件夹下。要恢复OneToMany项目的开发环境请参考第三章”如何恢复本书配套例子的开发环境”,要发 布本例子EJB (确保配置了环境变量JBOSS_HOME 及启动了Jboss),你可以执行Ant 的deploy任务。例子使用的 数据源配置文件是mysql-ds.xml,你可以在下载的文件中找到。数据库名为foshanshop 本例子的客户端代码在EJBTest文件夹,要发布客户端应用,你可以执行Ant 的deploy任务。通过 http://localhost:8080/EJBTest/OneToManyTest.jsp访问客户端。 6.6.3 一对一映射 一个人(Person)只有唯一的身份证号 (IDCard),Person 与IDCard 是一对一关系。下面就以他们为例介绍存在 一对一关系的实体Bean开发过程 需要映射的数据库表 person 字段名称 字段类型属性 描述 personid (主键) Int(11) not null 人员ID PersonName Varchar(32) not null 姓名 sex Tinyint(1) not null 性别 age Smallint(6) not null 年龄 birthday Datetime null 出生日期 idcard 字段名称 字段类型属性 描述 id (主键) Int(11) not null 流水号 cardno Varchar(18) not null 身份证号 Person_ID Int(11) not null 作为外键指向person 表的 personid Jboss EJB3.0实例教程 版权所有:黎活明 一对一关系需要在关系维护端(owner side)的@OneToOne注释中定义mappedBy属性。在关系被维护端(inverse side)建立外键列指向关系维护端的主键列。 下面是关系维护端Person.java 的源代码: //author:lihuoming package com.foshanshop.ejb3.bean; import java.io.Serializable; import java.util.Date; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.OneToOne; import javax.persistence.Table; @SuppressWarnings("serial") @Entity @Table(name = "Person") public class Person implements Serializable{ private Integer personid; private String name; private boolean sex; private Short age; private Date birthday; private IDCard idcard; @Id @GeneratedValue public Integer getPersonid() { return personid; } public void setPersonid(Integer personid) { this.personid = personid; } @Column(name = "PersonName",nullable=false,length=32) public String getName() { return name; } public void setName(String name) { this.name = name; } Jboss EJB3.0实例教程 版权所有:黎活明 @Column(nullable=false) public boolean getSex() { return sex; } public void setSex(boolean sex) { this.sex = sex; } @Column(nullable=false) public Short getAge() { return age; } public void setAge(Short age) { this.age = age; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } @OneToOne(optional = true,cascade = CascadeType.ALL, mappedBy = "person") public IDCard getIdcard() { return idcard; } public void setIdcard(IDCard idcard) { this.idcard = idcard; } } @OneToOne注释指明Person 与IDCard为一对一关系,@OneToOne注释五个属性:targetEntity、cascade、fetch、 optional和mappedBy, 前四个属性的具体含义与@ManyToOne注释的同名属性一一对应,请大家参考前面章节中 的内容,fetch属性默认值是FetchType.EAGER。mappedBy属性的具体含义与@OneToMany注释的同名属性相同。 上面的optional = true设置idcard属性可以为null,也就是允讦没有身份证,未成年人就是没有身份证的。 IDCard.java //author:lihuoming package com.foshanshop.ejb3.bean; import java.io.Serializable; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; Jboss EJB3.0实例教程 版权所有:黎活明 import javax.persistence.JoinColumn; import javax.persistence.OneToOne; import javax.persistence.Table; @SuppressWarnings("serial") @Entity @Table(name = "IDCard") public class IDCard implements Serializable{ private Integer id; private String cardno; private Person person; public IDCard() { } public IDCard(String cardno) { this.cardno = cardno; } @Id @GeneratedValue public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } @Column(nullable=false,length=18,unique = true) public String getCardno() { return cardno; } public void setCardno(String cardno) { this.cardno = cardno; } @OneToOne(optional = false, cascade = CascadeType.ALL) @JoinColumn(name = "Person_ID", referencedColumnName = "personid",unique = true) public Person getPerson() { return person; } public void setPerson(Person person) { this.person = person; } Jboss EJB3.0实例教程 版权所有:黎活明 } @OneToOne注释指明IDCard与Person为一对一关系,IDCard是关系被维护端,optional = false设置person属性 值不能为null,也就是身份证必须有对应的主人。@JoinColumn(name = "Person_ID", referencedColumnName = "personid",unique = true)指明IDCard对应表的Person_ID列作为外键与Person对应表的personid列进行关联, unique = true 指明Person_ID 列的值不可重复。 为了使用上面的实体Bean,我们定义一个Session Bean作为他的使用者。下面是Session Bean 的业务接口,他定 义了四个业务方法insertPerson,getPersonByID,updatePersonInfo和deletePerson, 四个方法的业务功能是: insertPerson添加一个人员(带一个身份证)进数据库 getPersonByID 获取指定编号的人员 updatePersonInfo 更新人名及身份证号 deletePerson 删除人员,连同其身份证一起删除 下面是Session Bean的业务接口及实现类 OneToOneDAO.java //author:lihuoming package com.foshanshop.ejb3; import java.util.Date; import com.foshanshop.ejb3.bean.Person; public interface OneToOneDAO { public void insertPerson(String name, boolean sex,short age, Date birthday,String cardID); public Person getPersonByID(Integer orderid); public void updatePersonInfo(Integer personid, String newname, String newIDcard); public void deletePerson(Integer personid); } OneToOneDAOBean.java package com.foshanshop.ejb3.impl; import java.util.Date; import javax.ejb.Remote; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import com.foshanshop.ejb3.OneToOneDAO; import com.foshanshop.ejb3.bean.IDCard; import com.foshanshop.ejb3.bean.Person; @Stateless @Remote ({OneToOneDAO.class}) public class OneToOneDAOBean implements OneToOneDAO { @PersistenceContext protected EntityManager em; Jboss EJB3.0实例教程 版权所有:黎活明 public void insertPerson(String name, boolean sex,short age, Date birthday,String cardID) { Person person = new Person(); person.setName(name); person.setSex(sex); person.setAge(Short.valueOf(age)); person.setBirthday(birthday); IDCard idcard = new IDCard(cardID); idcard.setPerson(person); person.setIdcard(idcard); em.persist(person); } public Person getPersonByID(Integer personid) { Person person = em.find(Person.class, personid); return person; } public void updatePersonInfo(Integer personid, String newname, String newIDcard) { Person person = em.find(Person.class, personid); if (person!=null) { person.setName(newname); if (person.getIdcard()!=null){ person.getIdcard().setCardno(newIDcard); } em.merge(person); } } public void deletePerson(Integer personid) { Person person = em.find(Person.class, personid); if (person!=null) em.remove(person); } } 下面是Session Bean的JSP 客户端代码: OneToOneTest.jsp <%@ page contentType="text/html; charset=GBK"%> <%@ page import="com.foshanshop.ejb3.OneToOneDAO, com.foshanshop.ejb3.bean.*, javax.naming.*, java.util.Date, java.text.SimpleDateFormat, java.util.*"%> Jboss EJB3.0实例教程 版权所有:黎活明 <% Properties props = new Properties(); props.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory"); props.setProperty("java.naming.provider.url", "localhost:1099"); props.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming"); InitialContext ctx = new InitialContext(props); try { String outformate = "CMD>>Out>> "; OneToOneDAO oneToonedao = (OneToOneDAO) ctx.lookup("OneToOneDAOBean/remote"); SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); SimpleDateFormat formatter1 = new SimpleDateFormat("MMddhhmmss"); String endno = formatter1.format(new Date()).toString(); oneToonedao.insertPerson("黎活明", true, (short)26,formatter.parse("1980-9-30"), "44011"+endno); //添加时请注意,身份证号不要重复,因为数据库字段身份证号是唯一的 Person person = oneToonedao.getPersonByID(new Integer(1)); if (person!=null){ out.println(outformate +"寻找编号为1的人员 "); out.println("姓名:"+ person.getName() +" 身份证:"+ person.getIdcard().getCardno() +" "); }else{ out.println("没有找到编号为1的人员 "); } out.println(outformate +"更新编号为1的人员的姓名为李明,身份证号为33012" +endno +" "); oneToonedao.updatePersonInfo(new Integer(1), "李明", "33012" +endno); out.println("================删除编号为3的人员============== "); oneToonedao.deletePerson(new Integer(3)); } catch (Exception e) { out.println(e.getMessage()); } %> 本例子的EJB源代码在OneToOne文件夹(源代码下载:http://www.foshanshop.net/),项目中使用到的类库在上级 目录lib 文件夹下。要恢复OneToOne项目的开发环境请参考第三章”如何恢复本书配套例子的开发环境”,要发布 本例子EJB (确保配置了环境变量JBOSS_HOME 及启动了Jboss),你可以执行Ant 的deploy任务。例子使用的数 据源配置文件是mysql-ds.xml,你可以在下载的文件中找到。数据库名为foshanshop 注意:在发布本例子EJB时,请御载前面带有Person类的例子 (如: EntityBean.jar),否则会引起类型冲突。 本例子的客户端代码在EJBTest文件夹,要发布客户端应用,你可以执行Ant 的deploy任务。通过 Jboss EJB3.0实例教程 版权所有:黎活明 http://localhost:8080/EJBTest/OneToOneTest.jsp访问客户端。 6.6.4 多对多映射 学生和老师就是多对多的关系。一个学生有多个老师,一个老师教多个学生。多对多映射采取中间表连接的映射 策略,建立的中间表将分别引入两边的主键作为外键。EJB3 对于中间表的元数据提供了可配置的方式,用户可 以自定义中间表的表名,列名。 下面就以学生和老师为例介绍多对多关系的实体Bean开发 需要映射的数据库表 student 字段名称 字段类型属性 描述 studentid (主键) Int(11) not null 学生ID studentName varchar(32) not null 学生姓名 teacher 字段名称 字段类型属性 描述 teacherid (主键) Int(11) not null 教师ID teacherName varchar(32) not null 教师姓名 中间表teacher_student 字段名称 字段类型属性 描述 Student_ID Int(11) not null 是student表studentid列的 外键 Teacher_ID Int(11) not null 是teacher表teacherid列的 外键 Student.java //author:lihuoming package com.foshanshop.ejb3.bean; import java.io.Serializable; import java.util.HashSet; import java.util.Set; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.ManyToMany; import javax.persistence.Table; @SuppressWarnings("serial") @Entity @Table(name = "Student") public class Student implements Serializable{ private Integer studentid; private String StudentName; Jboss EJB3.0实例教程 版权所有:黎活明 private Set teachers = new HashSet(); public Student() {} public Student(String studentName) { StudentName = studentName; } @Id @GeneratedValue public Integer getStudentid() { return studentid; } public void setStudentid(Integer studentid) { this.studentid = studentid; } @Column(nullable=false, length=32) public String getStudentName() { return StudentName; } public void setStudentName(String studentName) { StudentName = studentName; } @ManyToMany(mappedBy = "students") public Set getTeachers() { return teachers; } public void setTeachers(Set teachers) { this.teachers = teachers; } } @ManyToMany 注释表示Student 是多对多关系的一边,mappedBy 属性定义了Student 为双向关系的维护端 (owning side)。 Teacher.java //author:lihuoming package com.foshanshop.ejb3.bean; import java.io.Serializable; import java.util.HashSet; import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; Jboss EJB3.0实例教程 版权所有:黎活明 import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; import javax.persistence.Table; @SuppressWarnings("serial") @Entity @Table(name = "Teacher") public class Teacher implements Serializable{ private Integer teacherid; private String TeacherName; private Set students = new HashSet(); public Teacher() {} public Teacher(String teacherName) { TeacherName = teacherName; } @Id @GeneratedValue public Integer getTeacherid() { return teacherid; } public void setTeacherid(Integer teacherid) { this.teacherid = teacherid; } @Column(nullable=false, length=32) public String getTeacherName() { return TeacherName; } public void setTeacherName(String teacherName) { TeacherName = teacherName; } @ManyToMany(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY) @JoinTable(name = "Teacher_Student", joinColumns = {@JoinColumn(name = "Teacher_ID", referencedColumnName = "teacherid")}, inverseJoinColumns = {@JoinColumn(name = "Student_ID", referencedColumnName = "studentid")}) Jboss EJB3.0实例教程 版权所有:黎活明 public Set getStudents() { return students; } public void setStudents(Set students) { this.students = students; } public void addStudent(Student student) { if (!this.students.contains(student)) { this.students.add(student); } } public void removeStudent(Student student) { this.students.remove(student); } } @ManyToMany 注释表示Teacher 是多对多关系的一端。@JoinTable 描述了多对多关系的数据表关系。name 属 性指定中间表名称,joinColumns 定义中间表与Teacher 表的外键关系。上面的代码中,中间表Teacher_Student 的Teacher_ID 列是Teacher 表的主键列对应的外键列,inverseJoinColumns 属性定义了中间表与另外一端(Student) 的外键关系。 为了使用上面的实体Bean,我们定义一个Session Bean作为他的使用者。下面是Session Bean 的业务接口,他定 义了三个业务方法insertTeacher,getTeacherByID,和getStudentByID, 三个方法的业务功能是: insertTeacher 添加一个教师(带学生)进数据库 getTeacherByID 获取指定编号的教师 getStudentByID 获取指定编号的学生 下面是Session Bean的业务接口及实现类 TeacherDAO.java //author:lihuoming package com.foshanshop.ejb3; import com.foshanshop.ejb3.bean.Student; import com.foshanshop.ejb3.bean.Teacher; public interface TeacherDAO { public void insertTeacher(String name, String[] studentnames); public Teacher getTeacherByID(Integer teacherid); public Student getStudentByID(Integer studentid); } TeacherDAOBean.java //author:lihuoming package com.foshanshop.ejb3.impl; import javax.ejb.Remote; Jboss EJB3.0实例教程 版权所有:黎活明 import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import com.foshanshop.ejb3.TeacherDAO; import com.foshanshop.ejb3.bean.Student; import com.foshanshop.ejb3.bean.Teacher; @Stateless @Remote ({TeacherDAO.class}) public class TeacherDAOBean implements TeacherDAO { @PersistenceContext protected EntityManager em; public void insertTeacher(String name, String[] studentnames) { Teacher teacher = new Teacher(name); if (studentnames!=null){ for(int i=0;i <%@ page import="com.foshanshop.ejb3.TeacherDAO, com.foshanshop.ejb3.bean.*, javax.naming.*, java.util.*"%> <% Properties props = new Properties(); Jboss EJB3.0实例教程 版权所有:黎活明 props.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory"); props.setProperty("java.naming.provider.url", "localhost:1099"); props.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming"); InitialContext ctx = new InitialContext(props); try { TeacherDAO teacherdao = (TeacherDAO) ctx.lookup("TeacherDAOBean/remote"); teacherdao.insertTeacher("李老师",new String[]{"张小红","朱小光","龚利"}); Teacher teacher = teacherdao.getTeacherByID(new Integer(1)); if (teacher!=null){ out.println("======= 获取编号为1的老师姓名:"+ teacher.getTeacherName() +" ====== "); Iterator iterator = teacher.getStudents().iterator(); while (iterator.hasNext()){ Student student = (Student) iterator.next(); out.println(" 他的学生:"+ student.getStudentName() +" "); } }else{ out.println("没有找到编号为1的老师 "); } Student student = teacherdao.getStudentByID(new Integer(1)); if (student!=null){ out.println("======= 获取编号为1的学生姓名:"+ student.getStudentName() +" ====== "); Iterator iterator = student.getTeachers().iterator(); while (iterator.hasNext()){ Teacher tc = (Teacher) iterator.next(); out.println(" 他的老师:"+ tc.getTeacherName() +" "); } }else{ out.println("没有找到编号为1的学生 "); } } catch (Exception e) { out.println(e.getMessage()); } %> 本例子的EJB源代码在ManyToMany文件夹(源代码下载:http://www.foshanshop.net/),项目中使用到的类库在上 级目录lib 文件夹下。要恢复ManyToMany项目的开发环境请参考第三章”如何恢复本书配套例子的开发环境”,要 Jboss EJB3.0实例教程 版权所有:黎活明 发布本例子EJB (确保配置了环境变量JBOSS_HOME及启动了Jboss),你可以执行Ant 的deploy任务。例子使用 的数据源配置文件是mysql-ds.xml,你可以在下载的文件中找到。数据库名为foshanshop 本例子的客户端代码在EJBTest文件夹,要发布客户端应用,你可以执行Ant 的deploy任务。通过 http://localhost:8080/EJBTest/ManyToManyTest.jsp访问客户端。 6.7 使用参数查询 参数查询也和SQL中的参数查询类似。EJB3 QL支持两种方式的参数定义方式: 命名参数和位置参数。在同一个 查询中只允许使用一种参数定义方式。 6.7.1 命名参数查询 命令参数的格式为:“: +参数名” @PersistenceContext protected EntityManager em; … private String NameQuery(){ //获取指定personid的人员 Query query = em.createQuery("select p from Person p where p.personid=:Id"); query.setParameter("Id",new Integer(1)); List result = query.getResultList(); StringBuffer out = new StringBuffer("*************** NameQuery 结果打印 **************** "); if (result!=null){ Iterator iterator = result.iterator(); while( iterator.hasNext() ){ Person person= (Person)iterator.next(); out.append(person.getName()+ " "); } } return out.toString(); } 6.7.2 位置参数查询 位置参数的格式为“?+位置编号” @PersistenceContext protected EntityManager em; … private String PositionQuery(){ //获取指定personid的人员 Query query = em.createQuery("select p from Person p where p.personid=?1"); Jboss EJB3.0实例教程 版权所有:黎活明 query.setParameter(1,new Integer(1)); List result = query.getResultList(); StringBuffer out = new StringBuffer("*************** PositionQuery 结果打印 **************** "); if (result!=null){ Iterator iterator = result.iterator(); while( iterator.hasNext() ){ Person person= (Person)iterator.next(); out.append(person.getName()+ " "); } } return out.toString(); } 6.8 EJB3 QL 语言 Jboss EJB3.0采用Hibernate的O/R映射模型,Hibernate装备了一种极为有力的查询语言,看上去很像SQL。 但是别被语法蒙蔽,EJB3 QL是完全面向对象的,具备继承、多态和关联等特性。本教程只介绍常用的语法,要 了解更全面的知识请参考Hibernate使用手册。 本小节把程序的测试框架搭建起来,方便各位后面学习EJB3 QL语句。本例子的实体Bean有Person,Order, OrderItem,他们之间的关系是:一个Person有多个Order,一个Order 有多个OrderItem。 下面是各实体Bean的代码: Person.java package com.foshanshop.ejb3.bean; import java.io.Serializable; import java.util.Date; import java.util.HashSet; import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.OrderBy; import javax.persistence.Table; @SuppressWarnings("serial") @Entity @Table(name = "Person") public class Person implements Serializable{ private Integer personid; Jboss EJB3.0实例教程 版权所有:黎活明 private String name; private boolean sex; private Short age; private Date birthday; private Set orders = new HashSet(); public Person(){} public Person(String name, boolean sex, Short age, Date birthday) { this.name = name; this.sex = sex; this.age = age; this.birthday = birthday; } @Id @GeneratedValue public Integer getPersonid() { return personid; } public void setPersonid(Integer personid) { this.personid = personid; } @Column(name = "PersonName",nullable=false,length=32) public String getName() { return name; } public void setName(String name) { this.name = name; } @Column(nullable=false) public boolean getSex() { return sex; } public void setSex(boolean sex) { this.sex = sex; } @Column(nullable=false) public Short getAge() { return age; } public void setAge(Short age) { Jboss EJB3.0实例教程 版权所有:黎活明 this.age = age; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } @OneToMany(mappedBy="ower",cascade = CascadeType.ALL, fetch = FetchType.LAZY) @OrderBy(value = "orderid ASC") public Set getOrders() { return orders; } public void setOrders(Set orders) { this.orders = orders; } } Order.java package com.foshanshop.ejb3.bean; import java.io.Serializable; import java.util.HashSet; import java.util.Date; import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import javax.persistence.OrderBy; import javax.persistence.Table; @SuppressWarnings("serial") @Entity @Table(name = "Orders") public class Order implements Serializable { private int hashCode = Integer.MIN_VALUE; private Integer orderid; private Float amount; private Person ower; Jboss EJB3.0实例教程 版权所有:黎活明 private Set orderItems = new HashSet(); private Date createdate; public Order(){} public Order(Float amount, Person ower, Date createdate) { this.amount = amount; this.ower = ower; this.createdate = createdate; } @Id @GeneratedValue public Integer getOrderid() { return orderid; } public void setOrderid(Integer orderid) { this.orderid = orderid; } public Float getAmount() { return amount; } public void setAmount(Float amount) { this.amount = amount; } @ManyToOne(cascade=CascadeType.ALL,optional=false) @JoinColumn(name = "person_id") public Person getOwer() { return ower; } public void setOwer(Person ower) { this.ower = ower; } @OneToMany(mappedBy="order",cascade = CascadeType.ALL, fetch = FetchType.LAZY) @OrderBy(value = "id ASC") public Set getOrderItems() { return orderItems; } public void setOrderItems(Set orderItems) { this.orderItems = orderItems; } Jboss EJB3.0实例教程 版权所有:黎活明 public void addOrderItem(OrderItem orderitem) { if (!this.orderItems.contains(orderitem)) { this.orderItems.add(orderitem); orderitem.setOrder(this); } } public void removeOrderItem(OrderItem orderitem) { orderitem.setOrder(null); this.orderItems.remove(orderitem); } public Date getCreatedate() { return createdate; } public void setCreatedate(Date createdate) { this.createdate = createdate; } public boolean equals (Object obj) { if (null == obj) return false; if (!(obj instanceof Order)) return false; else { Order mObj = (Order) obj; if (null == this.getOrderid() || null == mObj.getOrderid()) return false; else return (this.getOrderid().equals(mObj.getOrderid())); } } public int hashCode () { if (Integer.MIN_VALUE == this.hashCode) { if (null == this.getOrderid()) return super.hashCode(); else { String hashStr = this.getClass().getName() + ":" + this.getOrderid().hashCode(); this.hashCode = hashStr.hashCode(); } } return this.hashCode; } } OrderItem.java package com.foshanshop.ejb3.bean; import java.io.Serializable; import javax.persistence.CascadeType; Jboss EJB3.0实例教程 版权所有:黎活明 import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; @SuppressWarnings("serial") @Entity @Table(name = "OrderItems") public class OrderItem implements Serializable { private Integer id; private String productname; private Float price; private Order order; public OrderItem() { } public OrderItem(String productname, Float price) { this.productname = productname; this.price = price; } @Id @GeneratedValue public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getProductname() { return productname; } public void setProductname(String productname) { this.productname = productname; } public Float getPrice() { return price; } public void setPrice(Float price) { Jboss EJB3.0实例教程 版权所有:黎活明 this.price = price; } @ManyToOne(cascade=CascadeType.ALL,optional=false) @JoinColumn(name = "order_id") public Order getOrder() { return order; } public void setOrder(Order order) { this.order = order; } } 下面是实体Bean的使用者Session Bean Session Bean的接口 package com.foshanshop.ejb3; public interface QueryDAO { public String ExecuteQuery(int index); public void initdate(); } Session Bean的程序片断 package com.foshanshop.ejb3.impl; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import javax.ejb.Remote; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; import com.foshanshop.ejb3.QueryDAO; import com.foshanshop.ejb3.bean.Order; import com.foshanshop.ejb3.bean.OrderItem; import com.foshanshop.ejb3.bean.Person; import com.foshanshop.ejb3.bean.SimplePerson; @Stateless @Remote ({QueryDAO.class}) public class QueryDAOBean implements QueryDAO { @PersistenceContext protected EntityManager em; Jboss EJB3.0实例教程 版权所有:黎活明 public void initdate() { try { Query query = em.createQuery("select count(*) from Person p"); Object result = query.getSingleResult(); if (result == null || Integer.parseInt(result.toString()) == 0) { // 没有数据时,插入几条数据用作测试 // =================================== SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); Person person = new Person("liujun", true, new Short("26"), formatter.parse("1980-9-30")); Set orders = new HashSet(); Order order1 = new Order(new Float("105.5"), person, new Date()); order1.addOrderItem(new OrderItem("U盘", new Float("105.5"))); Order order2 = new Order(new Float("780"), person, new Date()); order2.addOrderItem(new OrderItem("MP4", new Float("778"))); order2.addOrderItem(new OrderItem("矿泉水", new Float("2"))); orders.add(order1); orders.add(order2); person.setOrders(orders); Person person1 = new Person("yunxiaoyi", false, new Short("23"), formatter.parse("1983-10-20")); orders = new HashSet(); order1 = new Order(new Float("360"), person1, new Date()); order1.addOrderItem(new OrderItem("香水", new Float("360"))); order2 = new Order(new Float("1806"), person1, new Date()); order2.addOrderItem(new OrderItem("照相机", new Float("1800"))); order2.addOrderItem(new OrderItem("5 号电池", new Float("6"))); orders.add(order1); orders.add(order2); person1.setOrders(orders); // ===================================== Person person2 = new Person("zhangming", false, new Short("21"), formatter.parse("1985-11-25")); orders = new HashSet(); order1 = new Order(new Float("620"), person2, new Date()); order1.addOrderItem(new OrderItem("棉被", new Float("620"))); Jboss EJB3.0实例教程 版权所有:黎活明 order2 = new Order(new Float("3"), person2, new Date()); order2.addOrderItem(new OrderItem("可乐", new Float("3"))); orders.add(order1); orders.add(order2); person2.setOrders(orders); em.persist(person2); em.persist(person1); em.persist(person); } } catch (Exception e) { e.printStackTrace(); } } public String ExecuteQuery(int index) { String result = ""; switch(index){ case 1: result = this.NameQuery(); break; case 2: result = this.PositionQuery(); break; case 3: result = this.QueryOrderBy(); break; case 4: result = this.QueryPartAttribute(); break; case 5: result = this.QueryConstructor(); break; case 6: result = this.QueryAggregation(); break; case 7: result = this.QueryGroupBy(); break; case 8: result = this.QueryGroupByHaving(); break; case 9: result = this.QueryLeftJoin(); Jboss EJB3.0实例教程 版权所有:黎活明 break; case 10: result = this.QueryInnerJoin(); break; case 11: result = this.QueryInnerJoinLazyLoad(); break; case 12: result = this.QueryJoinFetch(); break; case 13: result = this.QueryEntityParameter(); break; case 14: result = this.QueryBatchUpdate(); break; case 15: result = this.QueryBatchRemove(); break; case 16: result = this.QueryNOTOperate(); break; case 17: result = this.QueryBETWEENOperate(); break; case 18: result = this.QueryINOperate(); break; case 19: result = this.QueryLIKEOperate(); break; case 20: result = this.QueryISNULLOperate(); break; case 21: result = this.QueryISEMPTYOperate(); break; case 22: result = this.QueryEXISTSOperate(); break; case 23: result = this.QueryStringOperate(); break; case 24: Jboss EJB3.0实例教程 版权所有:黎活明 result = this.QueryMathLOperate(); break; case 25: result = this.QuerySubQueryOperate(); break; case 26: result = this.QueryNoneReturnValueStoreProcedure(); break; case 27: result = this.QuerySingleObjectStoreProcedure(); break; case 28: result = this.QueryStoreProcedure(); break; case 29: result = this.QueryPartColumnStoreProcedure(); break; } return result; } …这里省略后面的代码 } 下面是Session Bean的JSP 客户端代码: QueryTest.jsp <%@ page contentType="text/html; charset=GBK"%> <%@ page import="com.foshanshop.ejb3.QueryDAO, javax.naming.*, java.util.Date, java.text.SimpleDateFormat, java.util.*"%> <% Properties props = new Properties(); props.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory"); props.setProperty("java.naming.provider.url", "localhost:1099"); props.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming"); try { String index = request.getParameter("index"); if (index!=null && !"".equals(index.trim())){ InitialContext ctx = new InitialContext(props); QueryDAO querydao = (QueryDAO) ctx.lookup("QueryDAOBean/remote"); querydao.initdate(); out.println(querydao.ExecuteQuery(Integer.parseInt(index))); Jboss EJB3.0实例教程 版权所有:黎活明 } } catch (Exception e) { out.println(e.getMessage()); } %> 本例子的EJB源代码在Query文件夹(源代码下载:http://www.foshanshop.net/),项目中使用到的类库在上级目录 lib 文件夹下。要恢复Query项目的开发环境请参考第三章”如何恢复本书配套例子的开发环境”,要发布本例子EJB (确保配置了环境变量JBOSS_HOME 及启动了Jboss),你可以执行Ant 的deploy任务。例子使用的数据源配置文 件是mysql-ds.xml,你可以在下载的文件中找到。数据库名为foshanshop 注意:在发布本例子EJB时,请御载前面带有Person,Order,OrderItem类的例子 (如: EntityBean.jar,OneToMany.jar, OneToOne.jar),否则会引起类型冲突。 本例子的客户端代码在EJBTest文件夹,要发布客户端应用,你可以执行Ant 的deploy任务。通过 http://localhost:8080/EJBTest/QueryTest.jsp?index=1(index 参数值可以是1 到25)访问客户端。 6.8.1 大小写敏感性(Case Sensitivity) 除了Java 类和属性名称外,查询都是大小写不敏感的。 所以,SeLeCT 和 sELEct 以及 SELECT 相同的, 但是com.foshanshop.ejb3.bean.Person 和 com.foshanshop.ejb3.bean.PERSon 是不同的, person.name 和 person.NAME 也是不同的。 6.8.2 排序(order by) 下面是一个简单查询的例子,可以看到EJB3 QL和SQL的使用方法很类似。"ASC"和"DESC"分别为升序和降序, 如果不显式注明,EJB3 QL中默认为asc升序。(例子的源代码在Query文件夹) @PersistenceContext protected EntityManager em; … private String QueryOrderBy(){ //先按年龄降序排序,然后按出生日期升序排序 Query query = em.createQuery("select p from Person p order by p.age desc, p.birthday asc"); List result = query.getResultList(); StringBuffer out = new StringBuffer("*************** QueryOrderBy 结果打印 **************** "); if (result!=null){ Iterator iterator = result.iterator(); while( iterator.hasNext() ){ Person person= (Person)iterator.next(); out.append(person.getName()+ " "); } } return out.toString(); } Jboss EJB3.0实例教程 版权所有:黎活明 6.8.3 查询部分属性 在前面的例子中,都是对针对Entity类的查询,返回的也是被查询的Entity类的实体。EJB3 QL也允许我们直接 查询返回我们需要的属性,而不是返回整个Entity。在一些Entity 中属性特别多的情况,这样的查询可以提高性 能。(例子的源代码在Query文件夹) @PersistenceContext protected EntityManager em; … private String QueryPartAttribute(){ //直接查询我们感兴趣的属性(列) Query query = em.createQuery("select p.personid, p.name from Person p order by p.personid desc "); //集合中的元素不再是Person,而是一个Object[]对象数组 List result = query.getResultList(); StringBuffer out = new StringBuffer("*************** QueryPartAttribute 结果打印 **************** "); if (result!=null){ Iterator iterator = result.iterator(); while( iterator.hasNext() ){ //取每一行 Object[] row = ( Object[]) iterator.next(); //数组中的第一个值是personid int personid = Integer.parseInt(row[0].toString()); String PersonName = row[1].toString(); out.append("personid="+ personid+ "; Person Name="+PersonName+ " "); } } return out.toString(); } 6.8.4 查询中使用构造器(Constructor) EJB3 QL支持将查询的属性结果直接作为一个java class的构造器参数,并产生实体作为结果返回。(例子的源代 码在Query文件夹) SimplePerson.java package com.foshanshop.ejb3.bean; public class SimplePerson { private String name; private boolean sex; public SimplePerson() { } Jboss EJB3.0实例教程 版权所有:黎活明 public SimplePerson(String name, boolean sex) { this.name = name; this.sex = sex; } public String getDescription() { return sex ? name+"是男孩" : name+"是女孩"; } } 下面将查询的属性结果直接作为SimplePerson的构造器参数。(例子的源代码在Query文件夹) @PersistenceContext protected EntityManager em; … private String QueryConstructor(){ //我们把需要的两个属性作为SimplePerson的构造器参数,并使用new函数。 Query query = em.createQuery("select new com.foshanshop.ejb3.bean.SimplePerson(p.name,p.sex) from Person p order by p.personid desc"); //集合中的元素是SimplePerson对象 List result = query.getResultList(); StringBuffer out = new StringBuffer("*************** QueryConstructor 结果打印 **************** "); if (result!=null){ Iterator iterator = result.iterator(); while( iterator.hasNext() ){ SimplePerson simpleperson = (SimplePerson) iterator.next(); out.append("人员介绍:"+ simpleperson.getDescription()+ " "); } } return out.toString(); } 6.8.5 聚合查询(Aggregation) 象大部分的SQL一样, EJB3 QL也支持查询中的聚合函数。目前EJB3 QL支持的聚合函数包括: 1. AVG() 2. SUM() 3. COUNT() 4. MAX() 5. MIN() (例子的源代码在Query文件夹) Jboss EJB3.0实例教程 版权所有:黎活明 @PersistenceContext protected EntityManager em; … private String QueryAggregation(){ //获取最大年龄 Query query = em.createQuery("select max(p.age) from Person p"); Object result = query.getSingleResult(); String maxAge = result.toString(); //获取平均年龄 query = em.createQuery("select avg(p.age) from Person p"); result = query.getSingleResult(); String avgAge = result.toString(); //获取最小年龄 query = em.createQuery("select min(p.age) from Person p"); result = query.getSingleResult(); String minAge = result.toString(); //获取总人数 query = em.createQuery("select count(*) from Person p"); result = query.getSingleResult(); String countperson = result.toString(); //获取年龄总和 query = em.createQuery("select sum(p.age) from Person p"); result = query.getSingleResult(); String sumage = result.toString(); StringBuffer out = new StringBuffer("*************** QueryConstructor 结果打印 **************** "); out.append("最大年龄:"+ maxAge+ " "); out.append("平均年龄:"+ avgAge+ " "); out.append("最小年龄:"+ minAge+ " "); out.append("总人数:"+ countperson+ " "); out.append("年龄总和:"+ sumage+ " "); return out.toString(); } 和SQL一样,如果聚合函数不是select...from的唯一一个返回列,需要使用"GROUP BY"语句。"GROUP BY"应 该包含select语句中除了聚合函数外的所有属性。(例子的源代码在Query文件夹) @PersistenceContext protected EntityManager em; … private String QueryGroupBy(){ //返回男女生各自的总人数 Query query = em.createQuery("select p.sex, count(*) from Person p group by p.sex"); //集合中的元素不再是Person,而是一个Object[]对象数组 Jboss EJB3.0实例教程 版权所有:黎活明 List result = query.getResultList(); StringBuffer out = new StringBuffer("*************** QueryGroupBy 结果打印 **************** "); if (result!=null){ Iterator iterator = result.iterator(); while( iterator.hasNext() ){ //取每一行 Object[] row = (Object[]) iterator.next(); //数组中的第一个值是sex boolean sex = Boolean.parseBoolean(row[0].toString()); //数组中的第二个值是聚合函数COUNT返回值 String sextotal = row[1].toString(); out.append((sex ? "男生":"女生")+ "总共有"+ sextotal+ "人 "); } } return out.toString(); } 如果还需要加上查询条件,需要使用"HAVING"条件语句而不是"WHERE"语句。(例子的源代码在Query文件夹) @PersistenceContext protected EntityManager em; … private String QueryGroupByHaving(){ //返回人数超过1 人的性别 Query query = em.createQuery("select p.sex, count(*) from Person p group by p.sex having count(*) >?1"); //设置查询中的参数 query.setParameter(1, new Long(1)); //集合中的元素不再是Person,而是一个Object[]对象数组 List result = query.getResultList(); StringBuffer out = new StringBuffer("*************** QueryGroupByHaving 结果打印 **************** "); if (result!=null){ Iterator iterator = result.iterator(); while( iterator.hasNext() ){ //取每一行 Object[] row = (Object[]) iterator.next(); //数组中的第一个值是sex boolean sex = Boolean.parseBoolean(row[0].toString()); //数组中的第二个值是聚合函数COUNT返回值 String sextotal = row[1].toString(); out.append((sex ? "男生":"女生")+ "总共有"+ sextotal+ "人 "); } } Jboss EJB3.0实例教程 版权所有:黎活明 return out.toString(); } 6.8.6 关联(join) 在EJB3 QL中,仍然支持和SQL中类似的关联语法: left out join/left join inner join left join/inner join fetch left out join/left join等,都是允许符合条件的右边表达式中的Entiies 为空。(例子的源代码在Query文件夹) @PersistenceContext protected EntityManager em; … private String QueryLeftJoin(){ //获取26 岁人的订单,不管Order 中是否有OrderItem Query query = em.createQuery("select o from Order o left join o.orderItems where o.ower.age=26 order by o.orderid"); List result = query.getResultList(); StringBuffer out = new StringBuffer("*************** QueryLeftJoin 结果打印 **************** "); if (result!=null){ Iterator iterator = result.iterator(); Integer orderid = null; while( iterator.hasNext() ){ Order order = (Order) iterator.next(); if (orderid==null || !orderid.equals(order.getOrderid())){ orderid = order.getOrderid(); out.append("订单号:"+ orderid+ " "); } } } return out.toString(); } 这个句EJB3 QL编译成以下的SQL。 select order0_.orderid as orderid18_, order0_.amount as amount18_, order0_.person_id as person4_18_, order0_.createdate as createdate18_ from Orders order0_ left outer join OrderItems orderitems1_ on order0_.orderid=orderitems1_.order_id, Person person2_ where order0_.person_id=person2_.personid and person2_.age=26 order by order0_.orderid 需要显式使用left join/left outer join的情况会比较少。 inner join要求右边的表达式必须返回Entities。(例子的源代码在Query文件夹) Jboss EJB3.0实例教程 版权所有:黎活明 @PersistenceContext protected EntityManager em; … private String QueryInnerJoin(){ //获取26 岁人的订单,Order 中必须要有OrderItem Query query = em.createQuery("select o from Order o inner join o.orderItems where o.ower.age=26 order by o.orderid"); List result = query.getResultList(); StringBuffer out = new StringBuffer("*************** QueryInnerJoin 结果打印 **************** "); if (result!=null){ Iterator iterator = result.iterator(); Integer orderid = null; while( iterator.hasNext() ){ Order order = (Order) iterator.next(); if (orderid==null || !orderid.equals(order.getOrderid())){ orderid = order.getOrderid(); out.append("订单号:"+ orderid+ " "); } } } return out.toString(); } 这个句EJB3 QL编译成以下的SQL。 select order0_.orderid as orderid18_, order0_.amount as amount18_, order0_.person_id as person4_18_, order0_.createdate as createdate18_ from Orders order0_ inner join OrderItems orderitems1_ on order0_.orderid=orderitems1_.order_id, Person person2_ where order0_.person_id=person2_.personid and person2_.age=26 order by order0_.orderid left/left out/inner join fetch提供了一种灵活的查询加载方式来提高查询的性能。在默认的查询中,Entity中的集合 属性默认不会被关联,集合属性默认是缓加载( lazy-load )。如下:(例子的源代码在Query文件夹) @PersistenceContext protected EntityManager em; … private String QueryInnerJoinLazyLoad(){ // 默认EJB3 QL编译后不关联集合属性变量(orderItems)对应的表 Query query = em.createQuery("select o from Order o inner join o.orderItems where o.ower.age=26 order by o.orderid"); List result = query.getResultList(); StringBuffer out = new StringBuffer("*************** QueryInnerJoinLazyLoad 结果打印 **************** "); if (result!=null){ if( result.size()>0){ Jboss EJB3.0实例教程 版权所有:黎活明 //这时获得Order 实体中orderItems( 集合属性变量 )为空 Order order = (Order) result.get(0); //当应用需要时,EJB3 Runtime 才会执行一条SQL语句来加载属于当前Order 的 OrderItems Set list = order.getOrderItems(); Iterator iterator = list.iterator(); if (iterator.hasNext()){ OrderItem orderItem =iterator.next(); out.append("订购产品名:"+ orderItem.getProductname()+ " "); } } } return out.toString(); } 在执行"select o from Order o inner join o.orderItems where o.ower.age=26 order by o.orderid"时编译成的SQL如下: 他不包含集合属性变量(orderItems)对应表的字段 select order0_.orderid as orderid6_, order0_.amount as amount6_, order0_.person_id as person4_6_, order0_.createdate as createdate6_ from Orders order0_ inner join OrderItems orderitems1_ on order0_.orderid=orderitems1_.order_id, Person person2_ where order0_.person_id=person2_.personid and person2_.age=26 order by order0_.orderid 当执行到Set list = order.getOrderItems();时才会执行一条SQL语句来加载属于当前Order 的 OrderItems,编译成的SQL如下: select orderitems0_.order_id as order4_1_, orderitems0_.id as id1_, orderitems0_.id as id7_0_, orderitems0_.order_id as order4_7_0_, orderitems0_.productname as productn2_7_0_, orderitems0_.price as price7_0_ from OrderItems orderitems0_ where orderitems0_.order_id=? order by orderitems0_.id ASC 这样的查询性能上有不足的地方。为了查询N 个Order,我们需要一条SQL语句获得所有的Order 的原始对象属 性, 但需要另外N 条语句获得每个Order 的orderItems 集合属性。为了避免N+1的性能问题,我们可以利用join fetch一次过用一条SQL语句把Order 的所有信息查询出来。如下:(例子的源代码在Query文件夹) @PersistenceContext protected EntityManager em; … private String QueryJoinFetch(){ //获取26 岁人的订单,Order 中必须要有OrderItem Query query = em.createQuery("select o from Order o inner join fetch o.orderItems where o.ower.age=26 order by o.orderid"); List result = query.getResultList(); StringBuffer out = new StringBuffer("*************** QueryJoinFetch 结果打印 **************** "); if (result!=null){ Iterator iterator = result.iterator(); Integer orderid = null; while( iterator.hasNext() ){ Order order = (Order) iterator.next(); Jboss EJB3.0实例教程 版权所有:黎活明 if (orderid==null || !orderid.equals(order.getOrderid())){ orderid = order.getOrderid(); out.append("订单号:"+ orderid+ " "); } } } return out.toString(); } 这个句EJB3 QL编译成以下的SQL。 select order0_.orderid as orderid18_0_, orderitems1_.id as id19_1_, order0_.amount as amount18_0_, order0_.person_id as person4_18_0_, order0_.createdate as createdate18_0_, orderitems1_.order_id as order4_19_1_, orderitems1_.productname as productn2_19_1_, orderitems1_.price as price19_1_, orderitems1_.order_id as order4_0__, orderitems1_.id as id0__ from Orders order0_ inner join OrderItems orderitems1_ on order0_.orderid=orderitems1_.order_id, Person person2_ where order0_.person_id=person2_.personid and person2_.age=26 order by order0_.orderid, orderitems1_.id ASC 上面由于使用了fetch,这个查询只会产生一条SQL语句,比原来需要N+1条SQL语句在性能上有了极大的提升。 6.8.7 比较Entity 在查询中使用参数查询时,参数类型除了String, 原始数据类型( int, double等)和它们的对象类型( Integer, Double 等),也可以是Entity的实例。(例子的源代码在Query文件夹) @PersistenceContext protected EntityManager em; … private String QueryEntityParameter(){ //查询某人的所有订单 Query query = em.createQuery("select o from Order o where o.ower =?1 order by o.orderid"); Person person = new Person(); person.setPersonid(new Integer(1)); //设置查询中的参数 query.setParameter(1,person); List result = query.getResultList(); StringBuffer out = new StringBuffer("*************** QueryEntityParameter 结果打印 **************** "); if (result!=null){ Iterator iterator = result.iterator(); Integer orderid = null; while( iterator.hasNext() ){ Order order = (Order) iterator.next(); if (orderid==null || !orderid.equals(order.getOrderid())){ orderid = order.getOrderid(); out.append("订单号:"+ orderid+ " "); Jboss EJB3.0实例教程 版权所有:黎活明 } } } return out.toString(); } 6.8.8 批量更新(Batch Update) EJB3 QL支持批量更新. (例子的源代码在Query文件夹) @PersistenceContext protected EntityManager em; … private String QueryBatchUpdate(){ //把所有订单的金额加10 Query query = em.createQuery("update Order as o set o.amount=o.amount+10"); //update的记录数 int result = query.executeUpdate(); StringBuffer out = new StringBuffer("*************** QueryBatchUpdate 结果打印 **************** "); out.append("更新操作影响的记录数:"+ result+ "条 "); return out.toString(); } 6.8.9 批量删除(Batch Remove) EJB3 QL支持批量删除。(例子的源代码在Query文件夹) @PersistenceContext protected EntityManager em; … private String QueryBatchRemove(){ //把金额小于100 的订单删除 Query query = em.createQuery("delete from Order as o where o.amount <100"); //delete的记录数 int result = query.executeUpdate(); StringBuffer out = new StringBuffer("*************** QueryBatchRemove 结果打印 **************** "); out.append("删除操作影响的记录数:"+ result+ "条 "); return out.toString(); } Jboss EJB3.0实例教程 版权所有:黎活明 6.8.10 使用操作符NOT (例子的源代码在Query文件夹) @PersistenceContext protected EntityManager em; … private String QueryNOTOperate(){ //查询除了指定人之外的所有订单 Query query = em.createQuery("select o from Order o where not(o.ower =?1) order by o.orderid"); Person person = new Person(); person.setPersonid(new Integer(2)); //设置查询中的参数 query.setParameter(1,person); List result = query.getResultList(); StringBuffer out = new StringBuffer("*************** QueryNOTOperate 结果打印 **************** "); if (result!=null){ Iterator iterator = result.iterator(); Integer orderid = null; while( iterator.hasNext() ){ Order order = (Order) iterator.next(); if (orderid==null || !orderid.equals(order.getOrderid())){ orderid = order.getOrderid(); out.append("订单号:"+ orderid+ " "); } } } return out.toString(); } 6.8.11 使用操作符BETWEEN (例子的源代码在Query文件夹) @PersistenceContext protected EntityManager em; … private String QueryBETWEENOperate(){ //查询金额在300 到1000 之间的订单 Query query = em.createQuery("select o from Order as o where o.amount between 300 and 1000"); List result = query.getResultList(); StringBuffer out = new StringBuffer("*************** QueryBETWEENOperate 结果打印 **************** "); Jboss EJB3.0实例教程 版权所有:黎活明 if (result!=null){ Iterator iterator = result.iterator(); Integer orderid = null; while( iterator.hasNext() ){ Order order = (Order) iterator.next(); if (orderid==null || !orderid.equals(order.getOrderid())){ orderid = order.getOrderid(); out.append("订单号:"+ orderid+ " "); } } } return out.toString(); } 6.8.12 使用操作符IN (例子的源代码在Query文件夹) @PersistenceContext protected EntityManager em; … private String QueryINOperate(){ //查找年龄为26,21 的Person Query query = em.createQuery("select p from Person as p where p.age in(26,21)"); List result = query.getResultList(); StringBuffer out = new StringBuffer("*************** QueryINOperate 结果打印 **************** "); if (result!=null){ Iterator iterator = result.iterator(); while( iterator.hasNext() ){ Person person= (Person)iterator.next(); out.append(person.getName()+ " "); } } return out.toString(); } 6.8.13 使用操作符LIKE (例子的源代码在Query文件夹) @PersistenceContext protected EntityManager em; … Jboss EJB3.0实例教程 版权所有:黎活明 private String QueryLIKEOperate(){ //查找以字符串"li"开头的Person Query query = em.createQuery("select p from Person as p where p.name like 'li%'"); List result = query.getResultList(); StringBuffer out = new StringBuffer("*************** QueryLIKEOperate 结果打印 **************** "); if (result!=null){ out.append("---------- 查找以字符串/"li/"开头的Person ---------- "); Iterator iterator = result.iterator(); while( iterator.hasNext() ){ Person person= (Person)iterator.next(); out.append(person.getName()+ " "); } } //可以结合NOT一起使用,比如查询所有name 不以字符串"ming"结尾的Person query = em.createQuery("select p from Person as p where p.name not like '%ming'"); result = query.getResultList(); if (result!=null){ out.append("---------- 查询所有name 不以字符串/"ming/"结尾的Person ---------- "); Iterator iterator = result.iterator(); while( iterator.hasNext() ){ Person person= (Person)iterator.next(); out.append(person.getName()+ " "); } } return out.toString(); } 6.8.14 使用操作符IS NULL (例子的源代码在Query文件夹) @PersistenceContext protected EntityManager em; … private String QueryISNULLOperate(){ //查询含有购买者的所有Order Query query = em.createQuery("select o from Order as o where o.ower is not null order by o.orderid"); List result = query.getResultList(); StringBuffer out = new StringBuffer("*************** QueryISNULLOperate 结果打印 **************** "); if (result!=null){ Iterator iterator = result.iterator(); Jboss EJB3.0实例教程 版权所有:黎活明 out.append("--------------- 查询含有购买者的所有Order ------------- "); while( iterator.hasNext() ){ Order order = (Order) iterator.next(); out.append("订单号:"+ order.getOrderid()+ " "); } } //查询没有购买者的所有Order query = em.createQuery("select o from Order as o where o.ower is null order by o.orderid"); result = query.getResultList(); if (result!=null){ Iterator iterator = result.iterator(); out.append("--------------- 查询没有购买者的所有Order ------------- "); while( iterator.hasNext() ){ Order order = (Order) iterator.next(); out.append("订单号:"+ order.getOrderid()+ " "); } } return out.toString(); } 6.8.15 使用操作符IS EMPTY IS EMPTY 是针对集合属性(Collection)的操作符。可以和NOT一起使用。(例子的源代码在Query文件夹)。注: 低版权的Mysql不支持IS EMPTY @PersistenceContext protected EntityManager em; … private String QueryISEMPTYOperate(){ //查询含有订单项的所有Order Query query = em.createQuery("select o from Order as o where o.orderItems is not empty order by o.orderid"); List result = query.getResultList(); StringBuffer out = new StringBuffer("*************** QueryISEMPTYOperate 结果打印 **************** "); if (result!=null){ Iterator iterator = result.iterator(); out.append("--------------- 查询含有订单项的所有Order ------------- "); while( iterator.hasNext() ){ Order order = (Order) iterator.next(); out.append("订单号:"+ order.getOrderid()+ " "); } } Jboss EJB3.0实例教程 版权所有:黎活明 //查询没有订单项的所有Order query = em.createQuery("select o from Order as o where o.orderItems is empty order by o.orderid"); result = query.getResultList(); if (result!=null){ Iterator iterator = result.iterator(); out.append("--------------- 查询没有订单项的所有Order ------------- "); while( iterator.hasNext() ){ Order order = (Order) iterator.next(); out.append("订单号:"+ order.getOrderid()+ " "); } } return out.toString(); } 6.8.16 使用操作符EXISTS [NOT]EXISTS需要和子查询配合使用。(例子的源代码在Query文件夹)。注:低版权的Mysql不支持EXISTS @PersistenceContext protected EntityManager em; … private String QueryEXISTSOperate(){ //如果存在订单号为1 的订单,就获取所有OrderItem Query query = em.createQuery("select oi from OrderItem as oi where exists (select o from Order o where o.orderid=1)"); List result = query.getResultList(); StringBuffer out = new StringBuffer("*************** QueryEXISTSOperate 结果打印 **************** "); if (result!=null){ Iterator iterator = result.iterator(); out.append("--------------- 如果存在订单号1,就获取所有OrderItem ------------- "); while( iterator.hasNext() ){ OrderItem item = (OrderItem) iterator.next(); out.append("所有订购的产品名:"+ item.getProductname()+ " "); } } //如果不存在订单号为10 的订单,就获取id为1 的OrderItem query = em.createQuery("select oi from OrderItem as oi where oi.id=1 and not exists (select o from Order o where o.orderid=10)"); result = query.getResultList(); if (result!=null){ Iterator iterator = result.iterator(); out.append("--------------- 如果不存在订单号10,就获取id为1 的OrderItem Jboss EJB3.0实例教程 版权所有:黎活明 ------------- "); if( iterator.hasNext() ){ OrderItem item = (OrderItem) iterator.next(); out.append("订单项ID 为1 的订购产品名:"+ item.getProductname()+ " "); } } return out.toString(); } 6.8.17 字符串函数 EJB3 QL定义了内置函数方便使用。这些函数的使用方法和SQL中相应的函数方法类似。EJB3 QL中定义的字 符串函数包括: 1. CONCAT 字符串拼接 2. SUBSTRING 字符串截取 3. TRIM 去掉空格 4. LOWER 转换成小写 5. UPPER 装换成大写 6. LENGTH 字符串长度 7. LOCATE 字符串定位 (例子的源代码在Query文件夹) @PersistenceContext protected EntityManager em; … private String QueryStringOperate(){ //查询所有人员,并在姓名后面加上字符串"_foshan" Query query = em.createQuery("select p.personid, concat(p.name, '_foshan') from Person as p"); List result = query.getResultList(); StringBuffer out = new StringBuffer("*************** QueryStringOperate 结果打印 **************** "); if (result!=null){ out.append("---------- 查询所有人员,并在姓名后面加上字符串/"_foshan/" ---------- "); Iterator iterator = result.iterator(); while( iterator.hasNext() ){ //取每一行 Object[] row = ( Object[]) iterator.next(); //数组中的第一个值是personid int personid = Integer.parseInt(row[0].toString()); String PersonName = row[1].toString(); out.append("personid="+ personid+ "; Person Name="+PersonName+ " "); } } Jboss EJB3.0实例教程 版权所有:黎活明 //查询所有人员,只取姓名的前三个字符 query = em.createQuery("select p.personid, substring(p.name,1,3) from Person as p"); result = query.getResultList(); if (result!=null){ out.append("---------- 查询所有人员,只取姓名的前三个字符 ---------- "); Iterator iterator = result.iterator(); while( iterator.hasNext() ){ //取每一行 Object[] row = ( Object[]) iterator.next(); //数组中的第一个值是personid int personid = Integer.parseInt(row[0].toString()); String PersonName = row[1].toString(); out.append("personid="+ personid+ "; Person Name="+PersonName+ " "); } } return out.toString(); } 6.8.18 计算函数 EJB3 QL中定义的计算函数包括: ABS 绝对值 SQRT 平方根 MOD 取余数 SIZE 取集合的数量 (例子的源代码在Query文件夹) @PersistenceContext protected EntityManager em; … private String QueryMathLOperate(){ //查询所有Order 的订单号及其订单项的数量 Query query = em.createQuery("select o.orderid, size(o.orderItems) from Order as o group by o.orderid"); List result = query.getResultList(); StringBuffer out = new StringBuffer("*************** QueryMathLOperate 结果打印 **************** "); if (result!=null){ Iterator iterator = result.iterator(); out.append("--------------- 查询所有Order 的订单号及其订单项的数量 ------------- "); while( iterator.hasNext() ){ //取每一行 Object[] row = ( Object[]) iterator.next(); Jboss EJB3.0实例教程 版权所有:黎活明 out.append("订单号:"+ row[0].toString()+ "; 订单项共"+row[1].toString()+ "项 "); } } //查询所有Order 的订单号及其总金额/10的余数 query = em.createQuery("select o.orderid, mod(o.amount, 10) from Order as o"); result = query.getResultList(); if (result!=null){ Iterator iterator = result.iterator(); out.append("--------------- 查询所有Order的订单号及其总金额/10的余数 ------------- "); while( iterator.hasNext() ){ //取每一行 Object[] row = ( Object[]) iterator.next(); out.append("订单号:"+ row[0].toString()+ "; 总金额/10的余数:"+row[1].toString()+ " "); } } return out.toString(); } 6.8.19 子查询 子查询可以用于WHERE和HAVING 条件语句中。(例子的源代码在Query文件夹)。注:低版权的Mysql不支持 子查询 @PersistenceContext protected EntityManager em; … private String QuerySubQueryOperate(){ //查询年龄为26 岁的购买者的所有Order Query query = em.createQuery("select o from Order as o where o.ower in(select p from Person as p where p.age =26) order by o.orderid"); List result = query.getResultList(); StringBuffer out = new StringBuffer("*************** QuerySubQueryOperate 结果打印 **************** "); if (result!=null){ Iterator iterator = result.iterator(); out.append("--------------- 查询年龄为26 岁的购买者的所有Order ------------- "); while( iterator.hasNext() ){ Order order = (Order) iterator.next(); out.append("订单号:"+ order.getOrderid()+ " "); } } return out.toString(); } Jboss EJB3.0实例教程 版权所有:黎活明 6.9 调用存储过程 要调用存储过程,我们可以通过EntityManager 对象的createNativeQuery()方法执行SQL 语句(注意:这里说的是 SQL语句,不是EJB3 QL), 调用存储过程的SQL格式如下: {call 存储过程名称(参数1, 参数2, …)} 在EJB3 中你可以调用的存储过程有两种 1.无返回值的存储过程。 2.返回值为ResultSet(以select形式返回的值)的存储过程,EJB3 不能调用以OUT参数返回值的存储过程。 下面我们看看几种具有代表性的存储过程的调用方法. 6.9.1 调用无返回值的存储过程 我们首先创建一个名为AddPerson的存储过程,他的DDL如下(注:本例使用的是MySql数据库): CREATE PROCEDURE `AddPerson`() NOT DETERMINISTIC SQL SECURITY DEFINER COMMENT '' BEGIN INSERT into person(`PersonName`,`sex`,`age`) values('存储过程',1,25); END; 下面的代码片断展示了无返回值存储过程的调用方法: @PersistenceContext protected EntityManager em; … private String QueryNoneReturnValueStoreProcedure(){ //调用无返回参数的存储过程 Query query = em.createNativeQuery("{call AddPerson()}"); query.executeUpdate(); StringBuffer out = new StringBuffer("*************** QueryNoneReturnValueStoreProcedure 结 果打印 **************** "); return out.toString(); } 例子的源代码在Query文件夹,在运行本例子,你首先需要创建AddPerson存储过程,然后调用Ant 的deploy任 务。通过http://localhost:8080/EJBTest/QueryTest.jsp?index=26访问例子。 6.9.2 调用返回单值的存储过程 我们首先创建一个名为GetPersonName 的存储过程,他有一个INTEGER 类型的输入参数,存储过程的DDL 如 下(注:本例使用的是MySql数据库): Jboss EJB3.0实例教程 版权所有:黎活明 CREATE PROCEDURE `GetPersonName`(IN Pid INTEGER(11)) NOT DETERMINISTIC SQL SECURITY DEFINER COMMENT '' BEGIN select personname from person where `personid`=Pid; END; 上面的select语句不一定要从表中取数据,你也可以这样写:select ‘foshanren’ 下面的代码片断展示了返回单值的存储过程的调用方法: @PersistenceContext protected EntityManager em; … private String QuerySingleObjectStoreProcedure(){ //调用返回单个值的存储过程 Query query = em.createNativeQuery("{call GetPersonName(?)}"); query.setParameter(1, new Integer(1)); String result = query.getSingleResult().toString(); StringBuffer out = new StringBuffer("*************** QuerySingleObjectStoreProcedure 结果打 印 **************** "); out.append("返回值(人员姓名)为:"+ result+ " "); return out.toString(); } 例子的源代码在Query文件夹,在运行本例子,你首先需要创建GetPersonName存储过程,然后调用Ant 的deploy 任务。通过http://localhost:8080/EJBTest/QueryTest.jsp?index=27访问例子。 6.9.3 调用返回表全部列的存储过程 我们首先创建一个名为GetPersonList的存储过程,他的DDL如下(注:本例使用的是MySql数据库): CREATE PROCEDURE `GetPersonList`() NOT DETERMINISTIC SQL SECURITY DEFINER COMMENT '' BEGIN select * from person; END; 下面的代码片断展示了返回表全部列的存储过程的调用方法,我们可以让EJB3 Persistence 运行环境将列值直接 填充入一个Entity的实例(本例填充进Person对象),并将实例作为结果返回. @PersistenceContext protected EntityManager em; … private String QueryStoreProcedure(){ Jboss EJB3.0实例教程 版权所有:黎活明 //调用返回Person全部列的存储过程 Query query = em.createNativeQuery("{call GetPersonList()}", Person.class); List result = query.getResultList(); StringBuffer out = new StringBuffer("*************** QueryStoreProcedure 结果打印 **************** "); if (result!=null){ Iterator iterator = result.iterator(); while( iterator.hasNext() ){ Person person= (Person)iterator.next(); out.append(person.getName()+ " "); } } return out.toString(); } 例子的源代码在Query文件夹,在运行本例子,你首先需要创建GetPersonList存储过程,然后调用Ant 的deploy 任务。通过http://localhost:8080/EJBTest/QueryTest.jsp?index=28访问例子。 6.9.4 调用返回部分列的存储过程 我们首先创建一个名为GetPersonPartProperties的存储过程,他的DDL如下(注:本例使用的是MySql数据库): CREATE PROCEDURE `GetPersonPartProperties`() NOT DETERMINISTIC SQL SECURITY DEFINER COMMENT '' BEGIN SELECT personid, personname from person; END; 上面的select语句不一定要从表中取数据,你也可以这样写:select 3000, ‘foshanren’ 下面的代码片断展示了返回部分列的存储过程的调用方法. @PersistenceContext protected EntityManager em; … private String QueryPartColumnStoreProcedure(){ //调用返回部分列的存储过程 Query query = em.createNativeQuery("{call GetPersonPartProperties()}"); List result = query.getResultList(); StringBuffer out = new StringBuffer("*************** QueryPartColumnStoreProcedure 结果打印 **************** "); if (result!=null){ Iterator iterator = result.iterator(); while( iterator.hasNext() ){ //取每一行 Jboss EJB3.0实例教程 版权所有:黎活明 Object[] row = ( Object[]) iterator.next(); //数组中的第一个值是personid int personid = Integer.parseInt(row[0].toString()); String PersonName = row[1].toString(); out.append("人员ID="+ personid+ "; 姓名="+PersonName+ " "); } } return out.toString(); } 例子的源代码在Query文件夹,在运行本例子,你首先需要创建GetPersonPartProperties存储过程,然后调用Ant 的deploy任务。通过http://localhost:8080/EJBTest/QueryTest.jsp?index=29访问例子。 6.10 事务管理服务 最有用的容器服务可能就是事务管理服务,当应用出现失败或异常时,它保证了数据库的完整性。你可以简单地 将为一个POJO 方法申明它的事务属性。这样容器就可以在合适的上下文中运行这个方法。最常见的事务是定义 在session bean的方法上,方法中所有的数据库操作只有在方法正常退出时才会提交,如果方法抛出未捕获的异 常,事务管理将回滚所有的变更。 @TransactionAttribute 注释用作定义一个需要事务的方法。它可以有以下参数: 1.REQUIRED:方法在一个事务中执行,如果调用的方法已经在一个事务中,则使用该事务,否则将创建一个 新的事务。 2.MANDATORY:方法必须在一个事务中执行,也就是说调用的方法必须已经有一个事务,否则新抛出一个错 误(ERROR)。 3.REQUIRESNEW:方法将在一个新的事务中执行,如果调用的方法已经在一个事务中,则暂停旧的事务。 4.SUPPORTS:如果方法在一个事务中被调用,则使用该事务,否则不使用事务。 5.NOT_SUPPORTED:如果方法在一个事务中被调用,将抛出一个错误(ERROR) 如果没有指定参数,@TransactionAttribute 注释使用REQUIRED 作为默认参数。 下面的代码展示了事务管理的开发: TransactionDAOBean.java //author:lihuoming package com.foshanshop.ejb3.impl; import java.util.List; import javax.ejb.Remote; import javax.ejb.Stateless; import javax.ejb.TransactionAttribute; import javax.ejb.TransactionAttributeType; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; import com.foshanshop.ejb3.TransException; import com.foshanshop.ejb3.TransactionDAO; import com.foshanshop.ejb3.bean.Product; Jboss EJB3.0实例教程 版权所有:黎活明 @Stateless @Remote ({TransactionDAO.class}) public class TransactionDAOBean implements TransactionDAO { @PersistenceContext protected EntityManager em; @TransactionAttribute(TransactionAttributeType.REQUIRED) public void insertProduct(String name, Float price, boolean error) { try { for(int i=0;i<3; i++){ Product product = new Product(name+i,price*(i+1)); em.persist(product); } if (error) new Float("kkk"); //制造一个例外 } catch (Exception e) { throw new RuntimeException ("应用抛出运行时例外,为了使事务回滚,外部不要用try/catch 包围"); } } @TransactionAttribute(TransactionAttributeType.REQUIRED) public void ModifyProductName(String newname, boolean error) throws Exception { Query query = em.createQuery("select p from Product p"); List result = query.getResultList(); if (result!=null){ for(int i=0;i0) throw new TransException ("抛出应用例外"); } } } 上面定义了两个需要事务的方法,容器在运行这两个方法时将会创建一个事务,方法里的所有数据库操作都在此 事务中运行,当这两个方法正常退出时,事务将会提交,所有更改都会同步到数据库中。如果方法抛出 RuntimeException例外或ApplicationException例外,事务将会回滚。方法ModifyProductName中使用的 TransException类是一个自定义ApplicationException例外。代码如下: TransException.java package com.foshanshop.ejb3; import javax.ejb.ApplicationException; Jboss EJB3.0实例教程 版权所有:黎活明 @SuppressWarnings("serial") @ApplicationException(rollback=true) public class TransException extends Exception { public TransException (String message) { super(message); } } @ApplicationException注释定义了在例外抛出时将回滚事务。 下面是TransactionDAOBean的接口 TransactionDAO.java package com.foshanshop.ejb3; public interface TransactionDAO { public void insertProduct(String name, Float price, boolean error); public void ModifyProductName(String newname, boolean error) throws Exception ; } 下面是TransactionDAOBean使用的实体Bean Product.java package com.foshanshop.ejb3.bean; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name = "Products") public class Product { private int hashCode = Integer.MIN_VALUE; private Integer productid; private String name; private Float price; public Product() {} public Product(String name, Float price) { this.name = name; this.price = price; } @Id @GeneratedValue public Integer getProductid() { return productid; Jboss EJB3.0实例教程 版权所有:黎活明 } public void setProductid(Integer productid) { this.productid = productid; } @Column(name = "ProductName",nullable=false,length=50) public String getName() { return name; } public void setName(String name) { this.name = name; } @Column(nullable=false) public Float getPrice() { return price; } public void setPrice(Float price) { this.price = price; } public boolean equals (Object obj) { if (null == obj) return false; if (!(obj instanceof Product)) return false; else { Product mObj = (Product) obj; if (null == this.getProductid() || null == mObj.getProductid()) return false; else return (this.getProductid().equals(mObj.getProductid())); } } public int hashCode () { if (Integer.MIN_VALUE == this.hashCode) { if (null == this.getProductid()) return super.hashCode(); else { String hashStr = this.getClass().getName() + ":" + this.getProductid().hashCode(); this.hashCode = hashStr.hashCode(); } } return this.hashCode; } } 下面是Session Bean的JSP 客户端代码: Jboss EJB3.0实例教程 版权所有:黎活明 TransactionTest.jsp <%@ page contentType="text/html; charset=GBK"%> <%@ page import="com.foshanshop.ejb3.TransactionDAO, javax.naming.*, java.util.*"%> <% Properties props = new Properties(); props.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory"); props.setProperty("java.naming.provider.url", "localhost:1099"); props.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming"); try { InitialContext ctx = new InitialContext(props); TransactionDAO transactiondao = (TransactionDAO) ctx.lookup("TransactionDAOBean/remote"); transactiondao.insertProduct("电脑", new Float("1200"), false); transactiondao.ModifyProductName("数码相机", true); out.println("执行完成"); } catch (Exception e) { out.println(e.getMessage()); } %> 本例子的EJB源代码在TransactionService文件夹(源代码下载:http://www.foshanshop.net/),项目中使用到的类库 在上级目录lib 文件夹下。要恢复TransactionService 项目的开发环境请参考第三章”如何恢复本书配套例子的开发 环境”,要发布本例子EJB (确保配置了环境变量JBOSS_HOME及启动了Jboss),你可以执行Ant 的deploy 任务。 例子使用的数据源配置文件是mysql-ds.xml,你可以在下载的文件中找到。数据库名为foshanshop 本例子的客户端代码在EJBTest文件夹,要发布客户端应用,你可以执行Ant 的deploy任务。通过 http://localhost:8080/EJBTest/TransactionTest.jsp访问客户端。 6.11 Entity的生命周期和状态 在EJB3 中定义了四种Entity的状态: 1. 新实体(new)。Entity由应用产生,和EJB3 Persistence运行环境没有联系,也没有唯一的标示符(Identity)。 2. 持久化实体(managed)。新实体和EJB3 Persistence运行环境产生关联(通过persist(), merge()等方法),在EJB3 Persistence运行环境中存在和被管理,标志是在EJB3 Persistence运行环境中有一个唯一的标示(Identity)。 3. 分离的实体(detached)。Entity有唯一标示符,但它的标示符不被EJB3 Persistence运行环境管理, 同样的该 Entity也不被EJB3 Persistence运行环境管理。 4. 删除的实体(removed)。Entity被remove()方法删除,对应的纪录将会在当前事务提交的时候从数据库中删除。 Jboss EJB3.0实例教程 版权所有:黎活明 6.12 复合主键(Composite Primary Key) 当我们需要使用多个属性变量(表中的多列)联合起来作为主键,我们需要使用复合主键。复合主键要求我们编 写一个复合主键类( Composite Primary Key Class )。复合主键类需要符合以下一些要求: ·复合主键类必须是public和具备一个没有参数的构造函数 ·复合主键类的每个属性变量必须有getter/setter,如果没有,每个属性变量则必须是public或者protected ·复合主键类必须实现java.io.serializable ·复合主键类必须实现equals()和hashcode()方法 ·复合主键类中的主键属性变量的名字必须和对应的Entity中主键属性变量的名字相同 ·一旦主键值设定后,不要修改主键属性变量的值 本节以航线为例,介绍复合主键的开发过程,航线以出发地及到达地作为联合主键,航线与航班存在一对多的关 联关系,下面是他们的数据库表 airline 字段名称 字段类型属性 描述 leavecity (主键) char(3) not null 出发地 arrivecity (主键) char(3) not null 到达地 onoff tinyint(1) null 航线打开及关闭标志 flight 字段名称 字段类型属性 描述 Id (主键) int(11) not null 流水号 flightno varchar(10) not null 航班号 arrivetime varchar(10) null 到达时间 leavetime varchar(10) null 起飞时间 Leave_City char(3) not null 出发地 Arrive_City char(3) not null 到达地 按照复合主键类的要求,我们编写一个复合主键类AirtLinePK.java,代码如下: //author:lihuoming package com.foshanshop.ejb3.bean; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Embeddable; @SuppressWarnings("serial") @Embeddable public class AirtLinePK implements Serializable { private String leavecity; private String arrivecity; public AirtLinePK(){} public AirtLinePK(String leavecity, String arrivecity) { Jboss EJB3.0实例教程 版权所有:黎活明 this.leavecity = leavecity; this.arrivecity = arrivecity; } @Column(nullable=false,length=3) public String getLeavecity() { return leavecity; } public void setLeavecity(String leavecity) { this.leavecity = leavecity; } @Column(nullable=false,length=3) public String getArrivecity() { return arrivecity; } public void setArrivecity(String arrivecity) { this.arrivecity = arrivecity; } public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof AirtLinePK)) return false; final AirtLinePK airtLinePK = (AirtLinePK) o; if (!leavecity.equals(airtLinePK.getLeavecity())) return false; if (!arrivecity.equals(airtLinePK.getArrivecity())) return false; return true; } public int hashCode() { int result; result = leavecity.hashCode(); result = 29 * result + arrivecity.hashCode(); return result; } } 复合主键使用一个可嵌入的类作为主键表示,@Embeddable注释指明这是一个可嵌入的类。下面AirLine.java 用 复合主键类AirtLinePK 作为其主键。 AirLine.java //author:lihuoming package com.foshanshop.ejb3.bean; import java.io.Serializable; Jboss EJB3.0实例教程 版权所有:黎活明 import java.util.HashSet; import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.OrderBy; import javax.persistence.Table; @SuppressWarnings("serial") @Entity @Table(name = "AirLine") public class AirLine implements Serializable { private AirtLinePK id; private Boolean onoff; private Set flights = new HashSet(); public AirLine(){} public AirLine(AirtLinePK id, Boolean onoff){ this.id = id; this.onoff = onoff; } @Id public AirtLinePK getId() { return id; } public void setId(AirtLinePK id) { this.id = id; } public Boolean getOnoff() { return onoff; } public void setOnoff(Boolean onoff) { this.onoff = onoff; } @OneToMany(mappedBy="airline",cascade = CascadeType.ALL, fetch = FetchType.LAZY) @OrderBy(value = "id ASC") Jboss EJB3.0实例教程 版权所有:黎活明 public Set getFlights() { return flights; } public void setFlights(Set flights) { this.flights = flights; } public void addFlight(Flight flight) { if (!this.flights.contains(flight)) { this.flights.add(flight); flight.setAirline(this); } } public void removeFlight(Flight flight) { flight.setAirline(null); this.flights.remove(flight); } } Flight.java //author:lihuoming package com.foshanshop.ejb3.bean; import java.io.Serializable; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinColumns; import javax.persistence.ManyToOne; import javax.persistence.Table; @SuppressWarnings("serial") @Entity @Table(name = "Flight") public class Flight implements Serializable { private Integer id; private String flightno;//航班号 private String leavetime;//起飞时间 private String arrivetime;//到达时间 private AirLine airline; Jboss EJB3.0实例教程 版权所有:黎活明 public Flight(){} public Flight(String flightno, String leavetime, String arrivetime) { this.flightno = flightno; this.leavetime = leavetime; this.arrivetime = arrivetime; } @Id @GeneratedValue public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } @Column(nullable=false,length=10) public String getFlightno() { return flightno; } public void setFlightno(String flightno) { this.flightno = flightno; } @Column(length=10) public String getArrivetime() { return arrivetime; } public void setArrivetime(String arrivetime) { this.arrivetime = arrivetime; } @Column(length=10) public String getLeavetime() { return leavetime; } public void setLeavetime(String leavetime) { this.leavetime = leavetime; } Jboss EJB3.0实例教程 版权所有:黎活明 @ManyToOne(cascade=CascadeType.ALL,optional=true) @JoinColumns ({ @JoinColumn(name="Leave_City", referencedColumnName = "leavecity", nullable=false), @JoinColumn(name="Arrive_City", referencedColumnName = "arrivecity", nullable=false) }) public AirLine getAirline() { return airline; } public void setAirline(AirLine airline) { this.airline = airline; } } Flight 与AirLine存在多对一关系,Flight 含有两个外键指向AirLine,用@JoinColumns注释定义多个JoinColumn 的属性, Flight对应表中的Leave_City列作为外键指向AirLine对应表中的leavecity列, Flight对应表中Arrive_City 列指向AirLine对应表中的arrivecity列。 为了使用上面的实体Bean,我们定义一个Session Bean作为他的使用者。下面是Session Bean 的业务接口,他定 义了两个业务方法insertAirLine和getAirLineByID,两个方法的业务功能是: insertAirLine添加一条航线(含有三个航班)进数据库 getAirLineByID 获取指定出发地及到达地的航线信息。 下面是Session Bean的业务接口及实现类 AirLineDAO.java //author:lihuoming package com.foshanshop.ejb3; import com.foshanshop.ejb3.bean.AirLine; public interface AirLineDAO { public void insertAirLine(); public AirLine getAirLineByID(String leavecity, String arrivecity); } AirLineDAOBean.java //author:lihuoming package com.foshanshop.ejb3.impl; import javax.ejb.Remote; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; import com.foshanshop.ejb3.AirLineDAO; import com.foshanshop.ejb3.bean.AirLine; import com.foshanshop.ejb3.bean.AirtLinePK; import com.foshanshop.ejb3.bean.Flight; Jboss EJB3.0实例教程 版权所有:黎活明 @Stateless @Remote ({AirLineDAO.class}) public class AirLineDAOBean implements AirLineDAO { @PersistenceContext protected EntityManager em; public void insertAirLine() { Query query = em.createQuery("select count(*) from AirLine a where a.id.leavecity =?1 and a.id.arrivecity=?2"); query.setParameter(1, "PEK"); query.setParameter(2, "CAN"); int result = Integer.parseInt(query.getSingleResult().toString()); if (result==0){ AirLine airLine = new AirLine(new AirtLinePK("PEK","CAN"), true); //PEK 为首都机场三字码,CAN 为广州白云机场三字码 airLine.addFlight(new Flight("CA1321","08:45","11:50")); airLine.addFlight(new Flight("CZ3102","12:05","15:05")); airLine.addFlight(new Flight("HU7801","15:05","17:45")); em.persist(airLine); } } public AirLine getAirLineByID(String leavecity, String arrivecity) { AirLine airLine = em.find(AirLine.class, new AirtLinePK(leavecity,arrivecity)); airLine.getFlights().size(); //因为是延迟加载,通过执行size()这种方式获取航线下的所有航班 return airLine; } } 下面是Session Bean的JSP 客户端代码: CompositePKTest.jsp <%@ page contentType="text/html; charset=GBK"%> <%@ page import="com.foshanshop.ejb3.AirLineDAO, com.foshanshop.ejb3.bean.*, javax.naming.*, java.util.*"%> <% Properties props = new Properties(); props.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory"); props.setProperty("java.naming.provider.url", "localhost:1099"); props.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming"); Jboss EJB3.0实例教程 版权所有:黎活明 InitialContext ctx = new InitialContext(props); try { AirLineDAO airlinedao = (AirLineDAO) ctx.lookup("AirLineDAOBean/remote"); airlinedao.insertAirLine(); AirLine airLine = airlinedao.getAirLineByID("PEK","CAN"); out.println("航线:"+ airLine.getId().getLeavecity() +"--"+ airLine.getId().getArrivecity() +" "); out.println("============== 存在以下航班 ================= "); if (airLine!=null){ Iterator iterator = airLine.getFlights().iterator(); while (iterator.hasNext()){ Flight flight = (Flight) iterator.next(); out.println("航班:"+ flight.getFlightno() +" "); } }else{ out.println("没有找到相关航线"); } } catch (Exception e) { out.println(e.getMessage()); } %> 本例子的EJB源代码在CompositePK 文件夹(源代码下载:http://www.foshanshop.net/),项目中使用到的类库在上 级目录lib 文件夹下。要恢复CompositePK 项目的开发环境请参考第三章”如何恢复本书配套例子的开发环境”,要 发布本例子EJB (确保配置了环境变量JBOSS_HOME及启动了Jboss),你可以执行Ant 的deploy任务。例子使用 的数据源配置文件是mysql-ds.xml,你可以在下载的文件中找到。数据库名为foshanshop 本例子的客户端代码在EJBTest文件夹,要发布客户端应用,你可以执行Ant 的deploy任务。通过 http://localhost:8080/EJBTest/CompositePKTest.jsp访问客户端。 第七章 Web服务(Web Service) 7.1 Web Service的创建 在本章节,我们将创建一个基于JSR-181规范的Web Service及其对应的客户端。开发一个JSR-181 POJO Endpoint 的Web Service应遵守下面几个步骤: 1> 建立一个POJO endpoint 2> 把endpoint 定义成一个servlet 3> 把endpoint打包成一个Web应用(war 文件) Jboss EJB3.0实例教程 版权所有:黎活明 下面让我们看看如何建立一个POJO endpoint。 package com.foshanshop.ws; import javax.jws.WebMethod; import javax.jws.WebService; import javax.jws.soap.SOAPBinding; @WebService(name = "HelloWorld", targetNamespace = "http://com.foshanshop.ws", serviceName = "HelloWorldService") @SOAPBinding(style = SOAPBinding.Style.RPC) public class HelloWorldService { @WebMethod public String SayHello(String name) { return name+ "说:这是我的第一个web服务"; } } @WebService这个注释放置在 Java 类的前面,声明这个类的部分方法可以被发布为 Web 服务。 @WebService 的属性用于设置 Web 服务被发布时的一些配置信息,常用的属性说明如下 1. name Web服务的名字,WSDL中wsdl:portType元素的name属性和它保持一致,默认是Java 类或者接口的名字。 2. serviceName Web服务的服务名,WSDL 中wsdl:service元素的name属性和它保持一致,默认是Java 类的名字+”Service” 。 3. targetNamespace WSDL文件所使用的namespace,该Web服务中所产生的其他XML文档同样采用这个作为namespace 。 @SOAPBinding()表示这个服务可以映射到一个SOAP消息中。Style用于指定SOAP消息请求和回应的编码方式。 @WebMethod 这个注释放在需要被发布成 Web 服务的方法前面。 下面把POJO endpoint 定义成一个servlet. Web.xml HelloWorldService com.foshanshop.ws.HelloWorldService HelloWorldService /HelloWorldService/* Jboss EJB3.0实例教程 版权所有:黎活明 把endpoint打包成一个web应用(*.war),下面是Ant 配置文件build.xml 的片断: 注意:POJO endpoint文件及web.xml 都是必须的。 经过上面的步骤,完成了一个Web Service的开发,下面我们通过Jboss管理平台查看刚才发布的Web Service , 我们可以输入http://localhost:8080/jbossws/进入JbossWS的查看界面,如下: 点击”view”连接后,可以查看已经发布的web services,如下图: 在上图中你可以点击 ServiceEndpointAddress 下的路径http://nd:8080/Services/HelloWorldService?wsdl访问他的 wsdl描述,wsdl描述文件在应用发布时由容器自动生成,输出如下: Jboss EJB3.0实例教程 版权所有:黎活明 本例子Ant的配置文件(build.xml)如下: Jboss EJB3.0实例教程 版权所有:黎活明 Jboss EJB3.0实例教程 版权所有:黎活明 本例子的源代码在JWS文件夹(源代码下载:http://www.foshanshop.net/),项目中使用到的类库在上级目录lib 文 件夹下。要恢复JWS项目的开发环境请参考第三章”如何恢复本书配套例子的开发环境”,要发布本例子(确保配置 了环境变量JBOSS_HOME 及启动了Jboss),你可以执行Ant 的deploy任务。 7.2 Web Service的客户端调用 作为Web Service的客户端,你可以采用自己喜欢的语言进行开发。本节将介绍如何用java 和Asp两种语言进行 客户端开发,当然你也可以采用C#开发。 7.2.1 用java 语言调用Web Service JbossWS提供了一个叫WSTools的工具,他可以通过ServiceEndpointAddress(如: http://localhost:8080/Services/HelloWorldService?wsdl)生成JAX-RPC映射文件及ServiceEndpoint接口文件,客户 端通过JAX-RPC映射文件及ServiceEndpoint接口文件很容易地调用Web Service 。 JAX-RPC映射文件在WSDL与java 接口之间起到桥梁的作用。 下面介绍WSTools工具的使用: WSTools工具可以在命令行或Ant 任务中使用,两者都需要一个配置文件作为输入参数,下面是本例配置文件的 内容: wstools-config.xml package-namespace节点的package 属性指定生成java 接口的包名,namespace属性指定JAX-RPC映射文件中的 package-mapping:namespaceURI节点的值。 wsdl-java节点的file属性指定WSDL 文件的路径。 Jboss EJB3.0实例教程 版权所有:黎活明 mapping节点的file属性指定JAX-RPC映射文件的名称。 1. 在命令行下运行WSTools工具 >wstools.bak -cp {指定生成文件存放的路径} -config wstools-config.xml 2. 在Ant 任务中运行WSTools工具,下面是他的配置片断: dest属性指定生成文件存放的目录, config属性指定配置文件的路径。 现在我们通过WSTools工具生成HelloWorldService服务的客户端JAX-RPC映射文件及ServiceEndpoint接口文 件,生成的文件有jaxrpc-mapping.xml, HelloWorld.java, HelloWorldService.java,下面是他们的内容: JAX-RPC映射文件(jaxrpc-mapping.xml) com.foshanshop.client http://com.foshanshop.ws com.foshanshop.client.HelloWorldService serviceNS:HelloWorldService HelloWorldPort HelloWorldPort com.foshanshop.client.HelloWorld portTypeNS:HelloWorld bindingNS:HelloWorldBinding sayHello SayHello Jboss EJB3.0实例教程 版权所有:黎活明 0 java.lang.String wsdlMsgNS:HelloWorld_SayHello String_1 IN java.lang.String wsdlMsgNS:HelloWorld_SayHelloResponse result ServiceEndpoint接口文件,HelloWorld.java package com.foshanshop.client; public interface HelloWorld extends java.rmi.Remote { public java.lang.String sayHello(java.lang.String string_1) throws java.rmi.RemoteException; } ServiceEndpoint接口文件,HelloWorldService.java package com.foshanshop.client; import javax.xml.rpc.*; public interface HelloWorldService extends javax.xml.rpc.Service { public com.foshanshop.client.HelloWorld getHelloWorldPort() throws ServiceException; } 下面我们通过上面生成的文件调用HelloWorldService 服务, 应用代码如下: package com.foshanshop.AppTest; import java.net.URL; import javax.xml.namespace.QName; import javax.xml.rpc.Service; import org.jboss.ws.jaxrpc.ServiceFactoryImpl; import com.foshanshop.client.HelloWorld; public class TestHelloWorld { Jboss EJB3.0实例教程 版权所有:黎活明 public static void main(String[] args) { try { URL helloWsdlUrl = new URL("http://localhost:8080/Services/HelloWorldService?wsdl"); String nameSpaceUri = "http://com.foshanshop.ws"; URL mappingURL = new TestHelloWorld().getClass().getClassLoader().getResource("META-INF/jaxrpc-mapping.xml"); ServiceFactoryImpl factory = new ServiceFactoryImpl(); Service service = factory.createService(helloWsdlUrl, new QName(nameSpaceUri, "HelloWorldService"), mappingURL); HelloWorld port = (HelloWorld) service.getPort(HelloWorld.class); String out = port.sayHello("佛山人"); System.out.println("结果:"+out); } catch (Exception e) { e.printStackTrace(); } } } 上面我们借助于jboss提供的ServiceFactoryImpl 类很轻易地调用Web 服务。 本例子Ant的配置文件(build.xml)如下: Jboss EJB3.0实例教程 版权所有:黎活明 Jboss EJB3.0实例教程 版权所有:黎活明 本例子的源代码在WSClient文件夹(源代码下载:http://www.foshanshop.net/),项目中使用到的类库在上级目录lib 文件夹下。要运行本例子,你可以执行Ant 的run任务。 7.2.2 用asp调用Web Service 使用ASP调用Web Service的方法,可以用XMLHTTP组件发送SOAP请求,下面是调用HelloWorldService 服 务SayHello方法的SOAP message 佛山人 Jboss EJB3.0实例教程 版权所有:黎活明 下面的asp代码把上面的SOAP message 发送到http://localhost:8080/Services/HelloWorldService <% postUrl = "http://localhost:8080/Services/HelloWorldService" Set xmlhttp = server.CreateObject("Msxml2.XMLHTTP") xmlhttp.Open "POST",postUrl,false SoapRequest =" " SoapRequest = SoapRequest &" " SoapRequest = SoapRequest & " 佛山人 " SoapRequest = SoapRequest & " " SoapRequest = SoapRequest & " " xmlhttp.setRequestHeader "Content-Length",LEN(SoapRequest) xmlhttp.setRequestHeader "SOAPAction", postUrl xmlhttp.Send(SoapRequest) str = xmlhttp.responseText response.write str %> 执行上面的asp后输出的SOAP message如下: 佛山人说:这是我的第一个web服务 result节点的值就是SayHello方法的返回值。 第八章 使用EJB3.0构建轻量级应用框架 本章是一个独立的知识点,上面章节介绍的都是EJB3.0 的分布式应用,而本章介绍的却是EJB3.0 非分布式应用。 JBoss EJB 3.0除了可以运行在Jboss服务器中之外,可嵌入(embeddable)版本的Jboss EJB 3.0还支持独立运行 在junit tests, Tomcat或其他应用服务器中,当然并不是所有Jboss中间件都可用,用可的功能如下: ·Local JNDI ·Transaction Manager ·JMS ·Local TX datasource/connection pool ·Stateful, Stateless, Service, Consumer, Producer, and MDBs Jboss EJB3.0实例教程 版权所有:黎活明 ·EJB 3 Persistence ·Hibernate integration ·EJB Security 不可用的功能如下(引用文档原话): ·XA Connection pool is not available yet. ·When embedding into Tomcat, you still require a JBoss specific JNDI implementation. Tomcat's JNDI is read-only. ·You still must use the JBoss transaction manager even when embedding in another app server vendor. This will be remedied in the future when the JBoss AOP/Microcontainer integration is complete. ·Distributed remote communication is not supported yet. ·EJB Timer service not supported ·Even though @Remote interfaces are local, you can only communicate through local connections. ·You cannot access JMS remotely. Only locally. Thus, you have to lookup the "java:/ConnectionFactory". ·JNDI is not available remotely 在EJB3产品没有出来前, 主流的应用框架要算Spring,他和EJB3.0的区别是。前者不是标准,但非常流行,它 基于依赖注入模式,大量使用XML配置;后者是JCP规定的标准,可以预料所有主要的J2EE 厂商都会支持, 它大量使用annotation记录配置信息。他们两者之间可以互相替代。 在以前,作者一直使用Struts+velocity+Hibernate+Spring构建非分布式的WEB应用,实践证明效果是不错的。但 有个问题一直困扰着作者,那就是所有应用都基于Spring框架开发。因为项目是长期性的,也就意味着功能扩展 及维护也长期依赖于Spring框架。作者的担心也就来了,万一有一天,Spring不灵了,没人用了怎么办?毕竟他不 是一种规范,有这种可能性存在。如果真是那样,到时要招个搞Spring的开发人员可不容易(谁还喜欢摆弄个老 古董),技术的可持续性就会出现问题。EJB3.0产品的到来,终于让作者有了更好的选择,毕竟他是规范的东西, 值得我们依赖。另外使用EJB3.0 作为轻量级应用框架的另一个好处是,日后需要分布式应用时,移植到Jboss中 非常容易,不需要修改任何EJB组件的代码。当然客户端需要适当的修改,修改量视你的客户端设计而定。 在同事中有人问过作者为何使用velocity 使用velocity是作者的个人喜好,因为作者非常讨厌Struts中的Tiles,以前有个WEB网站项目采用了Tiles作为 页面布局,随着页面不断地增多,技术员的新老替换,修改一个页面常常在十多个配置文件中分析寻找,当你找 到的时候已经是眼花缭乱,痛苦万分,Tiles 严重地影响到了开发速度。velocity挺适合技术员的开发习惯,也便 于美工修改页面,在一些经常需要改变页面模版的场合表现的很出色,而且只要简单的设置就可以输出在页面或 文件中,用他生成静态网页也特别方便。 言归正题,现在还是让我们学学如何在WEB中使用EJB3.0框架吧,请看下节。 8.1 在WEB中使用EJB3.0框架 介绍如何在WEB中使用EJB3.0框架时,我们需要下载可嵌入(embeddable)版本的Jboss EJB 3.0,下载网址: http://sourceforge.net/project/showfiles.php?group_id=22866&package_id=132063,文件名称: jboss-EJB-3.0_Embeddable_ALPHA_8-patch1.zip 下载完后,我们解压文件,把lib文件夹下的三个Jar文件拷贝到Web应用的WEB-INF/lib下面(建议拷贝到Tomcat 的shared/lib 文件夹下,这样所有的WEB应用都可以共享), 把conf文件夹下的所有配置文件拷贝到 WEB-INF/classes 下。接着我们需要对WEB应用的web.xml文件进行修改,加入下面的配置项: Jboss EJB3.0实例教程 版权所有:黎活明 jboss-kernel-deployments embedded-jboss-beans.xml, jboss-jms-beans.xml org.jboss.ejb3.embedded.ServletBootstrapListener embedded-jboss-beans.xml, jboss-jms-beans.xml 会自动被ServletBootstrapListener 类载入,如果你不需要JMS服务, 可以把jboss-jms-beans.xml 配置文件去掉。org.jboss.ejb3.embedded.ServletBootstrapListener 用作启动EJB3.0框架, 如果你的监听器(listener)使用了EJB3.0框架的东西,请确保EJB3.0框架在你的监听器之前启动,否则将会抛 出异常。 ServletBootstrapListener 会自动扫描并发布 /WEB-INF/lib文件夹下的所有EJB和 Entity beans.如果你想关掉自动 扫描发布功能,可以在web.xml文件中加入下面的配置项: automatic-scan false 到目前为止,在WEB中使用EJB3.0 框架的配置已经完成,接下来我们就开始学习在这种框架中如何进行开发。 在实际的项目开发上,作者有几点建议: 1.为了日后移植到JBOSS,EJB组件最好同时实现他的Local及Remote接口。 2.对于Local及Remote接口的JNDI访问最好做成可配置方式,在本章介绍的WEB框架中尽量访问EJB的Local 接口,日后移植到JBOSS时,可以切换成Remote接口访问。 3.把EJB访问的上下文环境独立出来。 8.1.1 如何使用Session Bean 为了说明EJB可在两种EJB 3.0版本(一种是本章介绍的可嵌入版本,另一种是前面章节使用的运行在Jboss中 的版本)中通用,本节直接使用第四章介绍的HelloWorld 例子,把HelloWorld.jar 放入WEB应用的WEB-INF/lib 文件夹下,EJB 3.0容器将会自动发布该EJB。 EJB的JSP客户端代码如下: Test.jsp <%@ page contentType="text/html; charset=GBK"%> <%@ page import="com.foshanshop.ejb3.HelloWorld, javax.naming.*, com.foshanshop.conf.Constants"%> <% try { InitialContext ctx = Constants.getInitialContext(); HelloWorld helloworld = (HelloWorld) ctx.lookup("HelloWorldBean/remote"); out.println(helloworld.SayHello("佛山人")); } catch (NamingException e) { out.println(e.getMessage()); } Jboss EJB3.0实例教程 版权所有:黎活明 %> Test.jsp中使用的Constants类 package com.foshanshop.conf; import java.util.Properties; import javax.naming.InitialContext; public class Constants { private static InitialContext ctx = null; public static InitialContext getInitialContext() { if (ctx!=null){ return ctx; }else{ try { Properties props = new Properties(); props.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("jndi.properties")); ctx = new InitialContext(props); } catch (Exception e) { ctx = null; } return ctx; } } } jndi.properties文件内容 java.naming.factory.initial=org.jnp.interfaces.LocalOnlyContextFactory java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces 本例子的源代码在EmbeddedEJB3文件夹(源代码下载:http://www.foshanshop.net/)。要打包成*.war 文件,你可以 执行Ant的web-war任务。运行本例子时需要把EJB3可嵌入版本的三个jar文件(jboss-ejb3-all.jar, thirdparty-all.jar, hibernate-all.jar)放置在Tomcat的shared/lib文件夹下(或放置在WEB应用的WEB-INF/lib下),把EmbeddedEJB3.war 文件拷贝到Tomcat的webapps目录,通过http://localhost:8080/EmbeddedEJB3/Test.jsp访问客户端. 注意:由于后面章节共用本例子,发布前请检查你的数据源参数与例子所设参数是否一致,数据源在 embedded-jboss-beans.xml文件中配置,关于各项参数的具体配置请参考后面章节“如何使用Entity Bean”。 8.1.2 如何使用Message Driven Bean 在使用Message Driven Bean之前需要先初始化JBoss MQ 核心服务,方法是在web.xml文件的配置参数 jboss-kernel-deployments加入jboss-jms-beans.xml 配置文件 Jboss EJB3.0实例教程 版权所有:黎活明 jboss-kernel-deployments embedded-jboss-beans.xml, jboss-jms-beans.xml 另外还要配置你自己使用的Queue/Topic,下面是队列名为foshanshop的Queue队列配置: foshanshop-jms.xml foshanshop 配置好的foshanshop-jms.xml文件放在WEB-INF/classes 下,同时在web.xml文件的配置参数 jboss-kernel-deployments加入foshanshop-jms.xml,内容如下: jboss-kernel-deployments embedded-jboss-beans.xml,jboss-jms-beans.xml,foshanshop-jms.xml 本节使用第五章的“消息驱动Bean”例子,把MessageDrivenBean.jar 放入WEB应用的WEB-INF/lib 文件夹下, EJB 3.0容器将会自动发布该消息驱动Bean。 消息驱动Bean的JSP客户端代码如下: MessageDrivenBeanTest.jsp <%@ page contentType="text/html; charset=GBK"%> <%@ page import="javax.naming.*, java.text.*, javax.jms.*, com.foshanshop.conf.Constants"%> <% QueueConnection cnn = null; QueueSender sender = null; QueueSession sess = null; Queue queue = null; try { InitialContext ctx = Constants.getInitialContext(); QueueConnectionFactory factory = (QueueConnectionFactory) ctx.lookup("java:/ConnectionFactory"); cnn = factory.createQueueConnection(); Jboss EJB3.0实例教程 版权所有:黎活明 sess = cnn.createQueueSession(false, QueueSession.AUTO_ACKNOWLEDGE); queue = (Queue) ctx.lookup("queue/foshanshop"); } catch (Exception e) { out.println(e.getMessage()); } TextMessage msg = sess.createTextMessage("佛山人您好,这是我的第一个消息驱动Bean"); sender = sess.createSender(queue); sender.send(msg); sess.close (); out.println("消息已经发送出去了,你可以到Tomcat控制台查看Bean的输出"); %> 本例子的源代码在EmbeddedEJB3文件夹(源代码下载:http://www.foshanshop.net/)。发布方式同上,通过 http://localhost:8080/EmbeddedEJB3/MessageDrivenBeanTest.jsp访问客户端. 8.1.3 如何使用依赖注入(dependency injection) 本节使用第四章的“依赖注入”例子,使用该例子时需要有一点修改,因为本章下载的嵌入版本EJB3.0比目前 Jboss中的EJB3.0版本要高,其中@EJB注释已经从javax.annotation包移到了javax.ejb 包。所以请把InjectionBean 类的import javax.annotation.EJB; 改为import javax.ejb.EJB;,javax.ejb.EJB在EJB3.0嵌入版本lib 文件夹下的 jboss-ejb3-all.jar 包中。 修改完后把MessageDrivenBean.jar 放入WEB应用的WEB-INF/lib 文件夹下,EJB 3.0容器将会自动发布该EJB。 EJB的JSP客户端代码如下: InjectionTest.jsp <%@ page contentType="text/html; charset=GBK"%> <%@ page import="com.foshanshop.ejb3.Injection, javax.naming.*,com.foshanshop.conf.Constants"%> <% try { InitialContext ctx = Constants.getInitialContext(); Injection injection = (Injection) ctx.lookup("InjectionBean/remote"); out.println(injection.SayHello()); } catch (NamingException e) { out.println(e.getMessage()); } %> 本例子的源代码在EmbeddedEJB3文件夹(源代码下载:http://www.foshanshop.net/)。发布方式同上,通过 http://localhost:8080/EmbeddedEJB3/InjectionTest.jsp访问客户端. 8.1.4 如何使用Entity Bean 在使用Entity Bean之前需要先配置数据源,数据源在embedded-jboss-beans.xml文件中配置,本例采用与前面章 Jboss EJB3.0实例教程 版权所有:黎活明 节一样的配置参数(数据库名:foshanshop用户名:root 密码:123456 数据源名称:DefaultMySqlDS),加入的 内容片断如下: org.gjt.mm.mysql.Driver jdbc:mysql://localhost:3306/foshanshop?useUnicode=true&characterEncoding=GBK root 123456 java:/DefaultMySqlDS 0 10 1000 100000 数据源配置好之后,我们还需把数据库驱动Jar文件放置在WEB应用的WEB-INF/lib 文件夹下(或放置在Tomcat 的shared/lib 文件夹下)。 本节使用第六章的“单表映射的实体Bean”例子,把EntityBean.jar 放入WEB应用的WEB-INF/lib 文件夹下,EJB 3.0 容器将会自动发布该EJB。 EJB的JSP客户端代码如下: EntityBeanTest.jsp <%@ page contentType="text/html; charset=GBK"%> <%@ page import="com.foshanshop.ejb3.PersonDAO, javax.naming.*, com.foshanshop.conf.Constants, java.util.Date, java.text.SimpleDateFormat"%> <% try { InitialContext ctx = Constants.getInitialContext(); PersonDAO persondao = (PersonDAO) ctx.lookup("PersonDAOBean/remote"); SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); persondao.insertPerson("黎活明", true, (short)26,formatter.parse("1980-9-30"));// 添加一个人 Jboss EJB3.0实例教程 版权所有:黎活明 out.println(persondao.getPersonNameByID(1)); //取personid为1的人 } catch (Exception e) { out.println(e.getMessage()); } %> 本例子的源代码在EmbeddedEJB3文件夹(源代码下载:http://www.foshanshop.net/)。发布方式同上,通过 http://localhost:8080/EmbeddedEJB3/EntityBeanTest.jsp访问客户端. 注意:一定别忘了把数据库驱动Jar文件放置在WEB应用的WEB-INF/lib文件夹下,或放置在Tomcat的shared/lib 文件夹下。 其中”事务管理”例子你可以通过http://localhost:8080/EmbeddedEJB3/TransactionTest.jsp访问 ”多对多”例子你可以通过http://localhost:8080/EmbeddedEJB3/ManyToManyTest.jsp访问