一、一个企业级Bean是由几个文件共同组成:
1、Bean类
SessionBean实现javax.ejb.SessionBean接口;
EntityBean实现javax.ejb.EntityBean接口。
2、EJB接口(远程接口或者本地接口)和EJB对象
远程接口继承javax.ejb.EJBObject接口;
本地接口继承javax.ejb.EJBLocalObject接口。
3、Home接口(远程接口或者本地接口)和Home对象
远程接口继承javax.ejb.EJBHome接口;
本地接口继承javax.ejb.EJBLocalHome接口。
4、部署描述文件
5、供应商特有文件
二、企业级Bean的调用:
Bean的客户端不直接调用Bean类实例本身,而是通过EJB对象来调用。所以EJB对象必须知道Bean类公开的每个商务方法。
EJB对象就是实现了EJB接口的对象,由容器或容器供应商提供的工具自动生成。
三、客户端如何获得EJB对象的引用:
EJB对象的引用通过Home对象来得到。Home对象就是实现了Home接口的对象,也是由EJB容器自动生成。
四、部署描述文件
在部署描述文件中声明对中间件服务的要求,EJB容器检查部署描述符,并完成要求:
1、Bean的管理和生命周期
2、持久性要求(仅限实体Bean)
3、事务处理要求
4、安全性要求
部署描述文件是一个xml文件。
五、供应商特有文件
每个EJB服务器供应商都不尽相同,他们各自都有一些独特的增值特性。
举例:
<jboss>
<enterprise-beans>
<session>
<ejb-name>Hello</ejb-name>
<jndi-name>Hello</jndi-name>
<local-jndi-name>HelloLocal</local-jndi-name>
</session>
</enterprise-beans>
</jboss>
六、客户端调用EJB的步骤:
1、通过JNDI查找EJB对象的位置;
2、调用Home对象的create()方法创建EJB对象;
3、调用EJB对象的商务方法;
4、调用EJB对象的remove()方法。
说明:客户端调用EJB一般使用远程调用,因为WEB服务器和EJB服务器可能位于两台机器;
EJB调用EJB一般使用本地调用,因为它们通常都位于同一个EJB容器内。
七、客户端调用EJB代码举例:
1、远程调用例子:
public class HelloTestClient1 extends Object {
public static void main(String[] args) {
HelloHome helloHome = null;
Hello hello = null;
java.util.Hashtable JNDIParm = new java.util.Hashtable();
JNDIParm.put(Context.PROVIDER_URL, "localhost");
JNDIParm.put(Context.INITIAL_CONTEXT_FACTORY,"org.jnp.interfaces.NamingContextFactory");
try {
javax.naming.Context ctx = new InitialContext(JNDIParm);
Object ref = ctx.lookup("Hello");
//创建Home对象
helloHome = (HelloHome) PortableRemoteObject.narrow(ref, HelloHome.class);
//创建EJB对象
hello = helloHome.create();
//调用EJB对象的商务方法
System.out.println(hello.hello());
//EJB对象使用完毕后,要从内存中将它清除
hello.remove();
}
catch (Exception ex) {
System.out.println(ex);
}
}
}
2、本地调用例子:
//通过调用无参数的构造函数,获得默认的初始化上下文
javax.naming.Context ctx = new InitialContext();
Object ref = ctx.lookup("java:comp/env/ejb/LocalUser");
//创建Home对象
localUserHome = (LocalUserHome) PortableRemoteObject.narrow(ref, LocalUserHome.class);
//创建EJB对象
localUser = localUserHome.create();
System.out.println(localUser.hello());
//EJB使用完毕后,要从内存中将它清除
localUser.remove();
.
.
.
说明:本地调用必须将下面的内容添加到ejb-jar.xml的部署描述符的 session 元素中,如下所示:
<session>
...
<ejb-local-ref>
<ejb-ref-name>ejb/LocalUser</ejb-ref-name>
<ejb-ref-type>Entity</ejb-ref-type>
<local-home>com.rickhightower.auth.LocalUserHome</local-home>
<local>com.rickhightower.auth.LocalUser</local>
<ejb-link>userEntity.jar#UserBean</ejb-link>
</ejb-local-ref>
...
请注意 ejb-local-ref 定义了对 UserBean 的引用。
八、有状态的session bean
1、一个有状态的session bean始终和一个客户相联系,在众多的方法调用过程中,这个实例代表的客户都保持一定的状态。
有状态的session bean类的实例总是同一个对象联系在一起,并同该对象绑定在一起用来指明一个确切的客户。
虽然web应用定义了http会话的概念,可以将一个业务的处理流程直接嵌入web应用的实现中,但是将业务处理封装在一个会话bean中将更加合适。
2、有状态的session bean的“激活”和“钝化”
容器如果按照有状态会话Bean的设想来实现的话。那么有限的资源如:内存、数据库连接等就会被耗费很多。
容器使用“激活”和“钝化”的方法来解决这一矛盾。
九、实体EJB
1、实体Bean是能够存放在永久性存储空间中的持久对象。这样我们就可以使用实体Bean来对商务中的数据进行建模。
2、一个实体Bean类可以映射一个关系型表的定义。这个类的一个实体将会映射那个表中的一行。实体Bean类提供一些访问数据和操作数据的简单方法。
3、实体Bean的主键类
EJB通过让实体Bean包含一个主键类,提供了定义您的唯一标识的灵活性。主键对象的属性没有必要和持久化主键数据完全一致。
主键类必须是可以序列化的(实现java.io.Serializable接口)。
4、并发访问问题
EJB规定只有一个线程可以不断的运行在一个Bean实例当中。会话Bean、实体Bean都是单线程的。
对于多客户端访问同一数据,容器实例化同一个实体Bean类的多个实例,每个实例都代表同一个底层实体数据。
容器根据事务来在适当的时候调用ejbLoad()和ejbStore()方法。同步数据。
从客户端的角度,它认为它在处理单个的实体Bean的实例。
十、从连接池获得JDBC连接
public Connection getConnection() throws Exception {
try {
Context ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup("java:/MySQLDB");
return ds.getConnection();
}
catch (Exception e) {
System.out.println("没有取得数据源");
e.printStackTrace();
throw e;
}
}
十一、容器管理的持久实体Bean(CMP)
1、用容器管理的持久化,不用再在实体Bean自身中实现任何持久逻辑(如,JDBC/SQL),而是由容器为您执行存储空间的操作。
2、数据库无关的Bean
如果我们要开发一个可重用的组件,我们很可能不知道Bean的使用者将要使用什么样的数据库存储。在CMP中,实体Bean和它的持久化表现完全分开。
3、容器的作用
不能在CMP实体Bean中编写任何JDBC和其他持久化逻辑。容器通过继承实体Bean类来生成JDBC代码。
4、所有的CMP实体Bean类被分解为两个类:一个类是超类,这个类需要自己编写,它包含实体Bean的数据逻辑。
另外一个是子类,这个类由容器生成,它包含持久化逻辑。所以实际的CMP实体Bean类是一个超类和一个子类的组合。
十二、EJB-QL
1、EJB-QL是一种语法类似于SQL、面向对象、用于查询实体Bean的查询语言。
举例:
SELECT OBJECT(a) FROM Product AS a WHERE a.name = ?
十三、开发一个CMP的步骤
1、构建CMP实体Bean类
2、设计抽象持久化模型(关于持久化的部署描述,称为“抽象持久化模式”)
3、编写EJB-QL
十四、实体Bean 的增、删、改、查
1、新增
如下所示:
LocalUser user = userHome.create(email, password);
提示:我们并没有在 home 接口中定义新增方法,该方法是从 javax.ejb.EJBLocalHome 接口继承的,由 EJB 容器实现。
2、删除
如下所示:
userHome.remove(email);
由于电子邮件地址是主键,因此它可以用来唯一地标识实体,用 home 接口的删除方法来删除它。
提示:我们并没有在 home 接口中定义删除方法,该方法是从 javax.ejb.EJBLocalHome 接口继承的,由 EJB 容器实现。
3、修改
如下所示:
LocalUser user = userHome.findByPrimaryKey(email);
user.setName("SWANGOOSE");
使用 LocalUserHome 的 findByPrimaryKey() 方法来查找与 email 主键相关联的 UserBean 实例,然后修改。
4、查询
有了 EJB 2.0 CMP,您只需使用 EJB-QL 在部署描述符中定义查找程序方法的定义。
您仍然还必须在 home 接口中声明这个查找程序方法。
请注意查找程序方法 public Collection findAll() throws FinderException 是在 home 中声明的。
举例如下:
public interface LocalUserHome extends EJBLocalHome
{
public Collection findAll() throws FinderException;
}
<entity>
<display-name>UserBean</display-name>
<ejb-name>UserBean</ejb-name>
<query>
<description></description>
<query-method>
<method-name>findAll</method-name>
<method-params />
</query-method>
<ejb-ql>
<![CDATA[select Object(theUser) from UserBean as theUser]]>
</ejb-ql>
</query>
</entity>
十五、使用简单的EJB-QL命令
1、简单查询许多实体
例如:为了返回系统中所有的 Group,您要使用下列 EJB-QL:
SELECT OBJECT(g) FROM UserGroup g
您将模式名用于 abstract-schema-name 元素定义的实体 bean,而不是用 FROM 子句中的表或视图。
为了定义 findAll() 方法,您要做以下的工作:
1、1 在 home 接口中定义一个 findAll() 方法;
1、2 为查找程序定义包含 EJB-QL 的查询元素。
返回多个实体的 findAll() 查找程序方法看上去就像这样:
public Collection findAll() throws javax.ejb.FinderException;
请注意,返回多个实体的查找程序方法必须返回 java.util.Collection 或 java.util.Set。
(确保返回 java.util.Set 的查找程序方法在 Set 中仅返回一次实体。)还要注意,必须声明所有的查找程序方法都抛出 javax.ejb.FinderException。
query 元素由 query-method、result-type-mapping 和 ejb-ql 元素组成,如下所示:
<query>
<query-method>
<method-name>findAll</method-name>
<method-params>
</method-params>
</query-method>
<result-type-mapping>Local</result-type-mapping>
<ejb-ql><![CDATA[
SELECT OBJECT(g) FROM UserGroup g
]]></ejb-ql>
</query>
result-type-mapping 元素指出查找程序方法返回的是实体 bean 的本地接口还是远程接口。在这个例子中,您将返回本地接口。
ejb-ql 元素是定义用于查询的 EJB-QL 的地方。
请注意,FROM 子句使用 Group 实体 bean 的模式名 UserGroup。还要注意 OBJECT 关键字的使用,它必须用于返回实体 bean。上面的查询返回系统中所有的 Group。
2、简单查询单独的实体
我们来假设您想创建一个查询,来查找组名等于某一个您感兴趣的名称的组。您要使用下列 EJB-QL:
SELECT OBJECT(g) FROM UserGroup g WHERE g.name = ?1
?1 指的是方法的第一个参数。如果有两个参数,那么 ?2 指的是传递给查找程序方法的第二个参数,依此类推。
上面示例的 home 接口的查找程序方法如下:
public GroupLocal findByGroupName(String name)
throws javax.ejb.FinderException;
请注意,这个查找程序方法还说明了一个查找程序,该查找程序期望用一个单独的实体作为结果,因为它返回 GroupLocal 而不是 Collection。事实上,如果查询返回不止一个结果,将抛出 FinderException。
因为这个方法只用了一个参数,所以必须在部署描述符中用 method-param 元素声明这个参数,如下所示:
<query>
<query-method>
<method-name>findByGroupName</method-name>
<method-params>
<method-param>java.lang.String</method-param>
</method-params>
</query-method>
...
</query>
请注意,WHERE 子句(和 g.name 中一样)中使用的 name 是一个 CMP 字段。
SELECT OBJECT(g) FROM UserGroup g WHERE g.name = ?1
因此,CMP 字段可以用在 EJB-QL 查询中。
3、高级 EJB-QL,包括:
比较操作符,包括 LIKE
选择方法
逻辑操作符
BETWEEN 子句
IS NULL
比如:
SELECT DISTINCT OBJECT(user)
FROM User user
WHERE user.group.name IN ('engineering','IT')
(其他,详见资料或书籍)
十六、容器管理的关系(CMR)
1、关系类型,如下所示:
一对一
一对多
多对多
这些关系在 XML 部署描述符中定义。
2、举例说明
有四个截然不同的实体:User、Group、Role 以及 UserInfo。这些实体中的每一个都有下面这三种关系:
多个 User 与多个 Role 相关联(多对多)
一个 User 有一个 UserInfo(一对一)
一个 Group 包含多个 User(一对多)
请注意下面的代码:
public abstract LocalUser getUser();
public abstract void setUser(LocalUser user);
在 UserInfoBean 的本地接口和实现中都有。这个 setter 和 getter 方法定义这个双向关系的 CMR 字段。
因为这个关系是双向的,所以 UserInfo 和 UserInfoBean 都必须有引用对方的 CMR 字段。
3、UserInfoBean 的部署描述符
请注意,一个关系中所涉及的实体必须在同一个部署描述符中定义;这样,必须在同一个 .jar 文件中打包这些实体。
UserBean 和 UserInfoBean 一起在同一个 EJB .jar 文件中打包,并且一起在同一个部署描述符中定义。
4、在部署描述符中定义一对一关系:
关系在 <enterprise-beans> 元素之外定义。当您指定关系时,您必须指定关系中所涉及的两个实体 bean。
关系在 <ejb-relation> 元素中定义。关系中的每个角色在 <ejb-relationship-role> 元素中定义。
比如:
<relationships>
<ejb-relation>
<ejb-relation-name>UserHasUserInfo</ejb-relation-name>
<ejb-relationship-role>
<ejb-relationship-role-name>
UserHasUserInfo
</ejb-relationship-role-name>
<multiplicity>One</multiplicity>
<relationship-role-source>
<ejb-name>UserBean</ejb-name>
</relationship-role-source>
<cmr-field>
<cmr-field-name>userInfo</cmr-field-name>
</cmr-field>
</ejb-relationship-role>
<ejb-relationship-role>
<ejb-relationship-role-name>
UserInfoPartOfUser
</ejb-relationship-role-name>
<multiplicity>One</multiplicity>
<cascade-delete />
<relationship-role-source>
<ejb-name>UserInfoBean</ejb-name>
</relationship-role-source>
<cmr-field>
<cmr-field-name>user</cmr-field-name>
</cmr-field>
</ejb-relationship-role>
</ejb-relation>
</relationships>
<ejb-relationship-role> 有名称、多样性、源以及可选的 CMR 字段,分别由下面这些元素定义:
分别是 <ejb-relationship-role-name>、<multiplicity>、带有 <ejb-name> 子元素的 <relationship-role-source>
以及带有 <cmr-field-name> 子元素的 <cmr-field>。
现在说明一下:
<ejb-relationship-role-name> 元素体可以是您所希望的任何名称。请尽量使它对于您所描述的关系具有描述性。
另外,请尽量使它在部署描述符的上下文中是唯一的。
<relationship-role-source>/<ejb-name> 体必须设置为由 <enterprise-beans> 元素中的实体 /<ejb-name> 定义的 ejb-bean 名。
<cmr-field>/<cmr-field-name> 体必须设置为这一屏中定义的 CMR 字段名。
如果 <cmr-field> 由本地接口中的getUserInfo()和setUserInfo()定义,那么它将由 <cmr-field-name> 中的 userInfo 定义,如下所示:
<cmr-field>
<cmr-field-name>userInfo</cmr-field-name>
</cmr-field>
</ejb-relationship-role>
5、多对多关系
多对多关系听起来极其难以实现。实际上,它和一对一关系一样容易实现。EJB 容器替您做了所有最难做的工作。
对于这个示例,您将添加用户可能处于的不同角色。单个用户可能处于多个角色。并且一个角色可以与多个用户相关联。
在部署描述符中定义多对多关系:
添加多对多关系的 XML 元素和技术与添加一对一关系的 XML 元素和技术基本相同。唯一的关键的不同之处是多样性。
RoleBean 的 <ejb-relationship-role> 没有 CMR 字段元素,因为从 UserBean 到 RoleBean 的关系是单向的。
RoleBean 不知道 UserBean。下面列出了 <ejb-relation> 元素:
<ejb-relation>
<ejb-relation-name>UserAssociateWithRoles</ejb-relation-name>
<ejb-relationship-role>
<ejb-relationship-role-name>UserBeanToRoleBean</ejb-relationship-role-name>
<multiplicity>Many</multiplicity>
<relationship-role-source>
<ejb-name>UserBean</ejb-name>
</relationship-role-source>
<cmr-field>
<cmr-field-name>roles</cmr-field-name>
<cmr-field-type>java.util.Collection</cmr-field-type>
</cmr-field>
</ejb-relationship-role>
<ejb-relationship-role>
<ejb-relationship-role-name>RoleBeanToUserBean</ejb-relationship-role-name>
<multiplicity>Many</multiplicity>
<relationship-role-source>
<ejb-name>RoleBean</ejb-name>
</relationship-role-source>
</ejb-relationship-role>
</ejb-relation>
请注意,UserBeanToRoleBean 的 <multiplicity> 为 Many,而且 UserBeanToRoleBean 的 <cmr-field> 有一个子元素 <cmr-field-type>。<cmr-field-type> 元素用值为 Many 的 <multiplicity> 的关系定义了 Java 代码类型。
请注意,两种可能是 java.util.Collection 和 java.util.Set。因为您选择了 java.util.Collection 以及 CMR 字段名是 roles,那么您可以料到会在返回和设置 java.util.Collection 的 UserBean 的本地接口中看到一个叫做 roles 的 CMR 字段(setter 和 getter 方法对)。
6、一对多关系
和第一个关系示例中的 UserInfoBean 一样,GroupBean 有一个引用 UserBean 的 <cmr-field>。和 UserInfoBean 不同,GroupBean 中的 <cmr-field> 的用户是 UserBean 的集合。
在部署描述符中定义一对多关系:
用来添加一对多关系的 xml 元素和技术与添加一对一和多对多关系的 xml 元素和技术几乎相同。唯一的关键的不同之处是多样性。
GroupBean 的 <ejb-relationship-role> 有一个 CMR 字段元素,因为从 UserBean 到 GroupBean 的关系是双向的。
GroupBean 有一个名为 users 的 CMR 字段。UserBean 有一个名为 group 的 CMR 字段。下面列出了 <ejb-relation> 元素:
<ejb-relation>
<ejb-relation-name>GroupsHaveUsers</ejb-relation-name>
<ejb-relationship-role>
<ejb-relationship-role-name>GroupHasUsers</ejb-relationship-role-name>
<multiplicity>One</multiplicity>
<relationship-role-source>
<ejb-name>GroupBean</ejb-name>
</relationship-role-source>
<cmr-field>
<cmr-field-name>users</cmr-field-name>
<cmr-field-type>java.util.Collection</cmr-field-type>
</cmr-field>
</ejb-relationship-role>
<ejb-relationship-role>
<ejb-relationship-role-name>UsersInGroup</ejb-relationship-role-name>
<multiplicity>Many</multiplicity>
<relationship-role-source>
<ejb-name>UserBean</ejb-name>
</relationship-role-source>
<cmr-field>
<cmr-field-name>group</cmr-field-name>
</cmr-field>
</ejb-relationship-role>
</ejb-relation>
请注意,GroupHasUsers 的 multiplicity 为 One,因为在这个关系中有一个组,并且和上一个示例 GroupHasUsers 的 <cmr-field> 一样,有一个子元素 <cmr-field-type>,如下所示:
<cmr-field>
<cmr-field-name>users</cmr-field-name>
<cmr-field-type>java.util.Collection</cmr-field-type>
</cmr-field>
字段关系的类型是 java.util.Collection,CMR 字段名是 users。
关系的另一方 UsersInGroup 的 <multiplicity> 为 Many,因为这个组里有很多用户。另请注意将 <cmr-field> 设置为 group。