3.2 EJB实例分析
该实例是运行在JBOSS5.1与SQLServer2005下。
(1) 会话BEAN
对于会话Bean的开发,相对简单如图:
图3.4 会话Bean接口声明
声明接口即与使用此EJB的用户签定了契约。用户可以使用接口提供的功能。当然要为接口实现此功能如图:
图3.5 会话Bean服务实现类
对于接口服务的实现,EJB3中使用了更为方便的标签方式,声明这个Bean并为其指定接口。完成之后,即可以向外提供服务,需要组织成构件,如图:
图3.6 jar文件
当这个jar文件部署到服务器中时,服务器解析此为一个EJB的实体Bean,并向外提供服务。
图3.7 调用EJB服务代码
图3.8 调用结果显示
(2) 实体BEAN
由于实体BEAN的开发就要涉及与数据库的关联,所以要配置数据源
对于JBOSS数据源的配置做简单的介绍。
对于JBOSS5.1数据源的配置,在目录docs/example/jca中已经包含了相关的数据源配置的xml例子文件,如图:
图3.9 数据源配置xml文档
首先复制一份文件,并修改
<?xml version="1.0"encoding="UTF-8"?>
<datasources>
<local-tx-datasource>
<jndi-name>MSSQLDS</jndi-name>//这是设置jndi名称以供在程序能够找到这个数据源
<connection-url>jdbc:sqlserver://localhost:1433;DatabaseName=MBOOK</connection-url>
<driver-class>com.microsoft.sqlserver.jdbc.SQLServerDriver</driver-class>
<user-name>sa</user-name>
<password>123456</password>
<metadata>
<type-mapping>MS SQLSERVER2005</type-mapping>
</metadata>
</local-tx-datasource>
</datasources>
修改好这个文件,同时需要把SQLServer2005所用的jdbc驱动包拷贝到jboss-5.1.0.GA\server\default\lib目录下,如图:
图3.10 数据驱动jar包
然后将文件移动到目录jboss-5.1.0.GA\server\default\deploy发布。如果此时你的jboss已经是启动状态,则会看到控制台打印:
图3.11 数据源加载成功
表示数据源加载成功。
接下来可以写你的EJB应用:
需要注意的是在实体Bean中,(注意实体bean中,get方法才有ejb的标签。另要确保实体bean有一个默认的的构造函数)需要包含一个persistence.xml文件,此文件在导出jar包的时候也要包含。此文件可以为此EJB对象指定数据源。即上面已经配置好的并加载进JBOSS中的。
<persistencexmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<!--持久化单元 transaction-type事物的类型。一个是全局事物JTA,一个是本地事物 -->
<persistence-unitname="sql"transaction-type="JTA">
<jta-data-source>java:MSSQLDS</jta-data-source>
<!--当数据源发布到jboss的时候,他的jndi名称默认是在java:的命名空间里 , 需要加上,jndi默认是在全局上找的 -->
<properties>
//注意如如果下面方言不配置,有时是会报错的。
<propertyname="hibernate.dialect"value="org.hibernate.dialect.SQLServerDialect"/>
<propertyname="hibernate.show_sql"value="true"/>
<propertyname="hibernate.format_sql"value="true"/>
<propertyname="hibernate.jdbc.fetch_size"value="50"/>
</properties>
</persistence-unit>
</persistence>
而jndi.properties文件在测试时和应用EJB时是必不可少的。
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.provider.url=localhost:1099
发布到jboss-5.1.0.GA-jdk6\jboss-5.1.0.GA\server\default\deploy目录下:
图3.12 发布EJB
如上图,已经成功发布了EntityBeanTest的EJB,该EJB是以jar文件形式存在的。并且还指出了beanName为PersonServiceBean,以及接口名IPersonService.
测试如下:
publicclass PersonServiceTest {
privatestatic IPersonServicepersonService;
@BeforeClass
publicstaticvoid setUpBeforeClass()throws Exception {
try {
InitialContextic =new InitialContext( );
personService = (IPersonService)ic.lookup("PersonServiceBean/remote");
} catch (Exception e){
e.printStackTrace();
}
}
@Test
publicvoid testSave() {
personService.save(newPerson("sunquan"));
}
}
执行成功,由于在persistence.xml中showSql为true,控制台显示了相应的sql语句。
图3.13 SQL语句
查看数据库:
图3.14 数据添加成功
收到对应结果。
1. 有几点需要注意,在数据库的设计过程中,id必须设为主键,且自增长。
2. 导入jboss/client的所有jar包。由于持久化操作,所以需要hibernate.jar包。
这样对于一个实体的bean,对应数据库中的一张表,并且包括对于数据的相应操作,就都包含在这个EJB对象当中。
(3) 消息驱动BEAN
消息驱动BEAN的特性有如异步调用,无状态特性,以及无本地远程接口等。
对于一个消息驱动BEAN,即当客户端发送一条消息时,该BEAN能够自动响应这条消息。能过客户端的消息来驱动该BEAN的调用、执行。
那么首先要存在这样一个所谓消息BEAN,并且他能够监听特定的消息。
packagecn.sunquan.MessageBean;
importjavax.ejb.ActivationConfigProperty;
importjavax.ejb.EJBException;
importjavax.ejb.MessageDriven;
importjavax.ejb.MessageDrivenBean;
importjavax.ejb.MessageDrivenContext;
importjavax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
importjavax.jms.TextMessage;
@MessageDriven(mappedName="MyMessageDrivenBean",activationConfig = {
@ActivationConfigProperty(propertyName ="destinationType", propertyValue ="javax.jms.Queue"),
@ActivationConfigProperty(propertyName ="destination", propertyValue ="queue/itmQueue")})
//关于上述标签的解释:destination指明要去监听容器通道的名字,destinationType指//明要去监听容器通道的类型。mappedName非必须的。
publicclassMyMessageDrivenBeanimplements MessageDrivenBean, MessageListener {
privatestaticfinallongserialVersionUID = 1L;
publicMyMessageDrivenBean() {
//TODO Auto-generated constructor stub
}
@Override
publicvoid onMessage(Messagemessage) {
TextMessagemsg = (TextMessage) message;
try {
System.out.println(msg.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
@Override
publicvoid ejbRemove()throws EJBException {
}
@Override
publicvoidsetMessageDrivenContext(MessageDrivenContext arg0)
throws EJBException {
}
}
下面这个xml文件就是声明上述那个通道,打在jar包和单独发布到deploy目录下效果一样。
<?xmlversion="1.0"encoding="UTF-8"?>
<server>
<mbeancode="org.jboss.mq.server.jmx.Queue"
name="jboss.mq.destination:server=Queue,name=itmQueue">
<attributename="JNDIName">queue/itmQueue</attribute>
<dependsoptional-attribute-name="DestinationManager">jboss.mq:service=DestinationManager</depends>
</mbean>
<!-- <mbeancode="org.jboss.mq.server.jmx.Topic"
name="jboss.mq.destination:service=Topic,name=itmTopic">
<attributename="JNDIName">topic/itmTopic</attribute>
<dependsoptional-attribute-name="DestinationManager">jboss.mq:service=DestinationManager</depends>
</mbean>
-->
</server>
测试发现在bean打成jar包后,如果不包含上述这个xml的文件(xxx-service.xml)时,这个消息驱动bean是bound不成功的。为什么呢?
图3.15 绑定不成功
既然JBOSS已经发现了一个叫做itemQueue类型的消息,服务器此时只能从bean中的标签中发现itemQueue,至此服务器已经知道有一个bean,
他是处理itemQueue类型的。当一个itemQueue消息发送过来的时候,服务器应该是可以把这条消息交给指定bean处理的,或者是这个bean一直在等待,类似监听这类消息,一旦有就处理。(仅是猜测)
那么EJB消息驱动Bean是哪种处理方法的呢?
很明显,由于异步的需要,是采用的第二种,BEAN一直在监听。那么JBOSS就要为这个BEAN开辟一条通道,让BEAN在这条通道上监听,发送对应类型的消息时,也发送到这个通道上。那么我们就需要为JBOSS指定这个通道,然后把BEAN绑定到这条通道上。
(同时联想到bean中的一个属性名是destination,目的地,bean是消息处理的终点,怎么还有目的地,哈哈,此处的意思是去容器的一个目的通道上监听。)
而上面我们只声明了BEAN,JBOSS中确不存在这条通道,而你的bean又要去这里监听,那么JBOSS服务器会显示指定的通道没有绑定。
所以有理由相信,所以要通过上面xml文件使用容器创建一个通道(该文件可以打包在jar文件中,也可以只直接以xml文件的方式发布在deploy目录下,其效果是一样的):
图3.16 绑定成功
接下来就是处理发送消息:
package cn.sunquan.send;
import java.util.Properties;
import javax.jms.Queue;
importjavax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSession;
import javax.jms.TextMessage;
import javax.naming.InitialContext;
publicclass QueueSender {
publicstaticvoid main(String[]args){
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);
//查找 QUeue类型的连接工厂。
QueueConnectionFactory factory = (QueueConnectionFactory)ctx
.lookup("ConnectionFactory");
QueueConnection conn=factory.createQueueConnection();
//创建一个到该地址的会话,第二个参数:消息的确认模式,这里用自动的确认模式。
QueueSession session = conn.createQueueSession(false,
QueueSession.AUTO_ACKNOWLEDGE);
Queue queue = (Queue) ctx
.lookup("queue/itmQueue");
TextMessage msg =session.createTextMessage("您好,这是我的第一个消息驱动Bean");
javax.jms.QueueSender sender=session.createSender(queue);
sender.send(msg);
session.close();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
可以看到lookup时是找的queue/itmQueue
图3.17 绑定成功
即在bean中指定的destination.
图3.18 显示结果
(注:上面的xxx-service.xml既然可以放在jar包中,那么实体bean中的dataSource.xml声明是否也可以放在实体bean的jar包中……)
下载: