Introduction
This quick EJB lesson doesn't explain why you should or shouldn't use EJBs, nor does it
talk about when to use them and when not to, nor does it even
explain any of the concepts behind the different EJB components. This guide is simply to help someone
see how to put together a working EJB 2.1 application that makes remote EJB calls to an EJB deployed on JBoss Application server.
To be consistent with many of the other examples, the front end is built using the Employee Struts application that is used elsewhere on this site. (The Struts app is 1.x not 2.x). The front end of choice doesn't matter at all so if you haven't even seen a Struts application it won't matter. Where you see the "EmployeeAction" class in the source code that calls our EJB layer, just think of a Servlet (which is all a Struts Action actually is)).
This example is using Remote EJB calls. It's only slightly more complex than the Local EJB example application. The main difference between the two applications is that this example has you run the war on one server and the ear on another server. The ejb lookups in the Action class are also slightly different - they make use of the Remote lookup in our ServiceLocator class. Also, you'll obvoiusly need two different servers to test this application. You'll deploy the war to one appserver and the ear to another server (you do this on just one machine if you play around with changing the ports, but I don't explain that in this lesson. It's easiest if you just have two machines to use.)
Without further ado, lets get started...
To be consistent with many of the other examples, the front end is built using the Employee Struts application that is used elsewhere on this site. (The Struts app is 1.x not 2.x). The front end of choice doesn't matter at all so if you haven't even seen a Struts application it won't matter. Where you see the "EmployeeAction" class in the source code that calls our EJB layer, just think of a Servlet (which is all a Struts Action actually is)).
This example is using Remote EJB calls. It's only slightly more complex than the Local EJB example application. The main difference between the two applications is that this example has you run the war on one server and the ear on another server. The ejb lookups in the Action class are also slightly different - they make use of the Remote lookup in our ServiceLocator class. Also, you'll obvoiusly need two different servers to test this application. You'll deploy the war to one appserver and the ear to another server (you do this on just one machine if you play around with changing the ports, but I don't explain that in this lesson. It's easiest if you just have two machines to use.)
Without further ado, lets get started...
The Setup
Download the source bundles The source is broken into two different downloads - one that builds the webapp war and
the other that builds the ear.
Java5 The Java5 JDK needs to be set as your environment. JAVA_HOME env variable needs to be defined pointing to yth the JDK home directory.
JBoss 4.x - Download JBoss 4.x here http://labs.jboss.com/portal/jbossas/download. Since you'll be probably be coding with the EJB 3.0 spec soon enough, set up JBoss4.x using the installer as described on the download page. After JBoss is installed declare a JBOSS_HOME environment variable pointing to the JBoss root directory.
Using the build.xml files To run the builds you will need ant installed. Running "ant all" from the root of the project directory will build the entire project. Use can just run "ant deploy" which will do all plus deploy the ear to jboss (or the war file if working with the webapp project. Remember if you use 'ant deploy' make sure you are doing it with your project on the correct server. This demo is showig a remote call so the war is deployed on one server and the ear on another server. Might be easiest to just use 'ant all' and the manually move the ear and war file to where you want to deploy them (each is found in the {project}/build/distribution directory)). **IMPORTANT: make sure to change he jndi/jndi.properties file in the rr-employee-webapp project to match the IP of the machine where you are deploying you ear!
Running the example You'll need the ear file and the war file. Run the build files to build the ear and war as described in the step above. Deploy the ear to your JBoss /server/default/deploy directory and deploy the war to another application server (another JBoss instance or even just standalone Tomcat instance is fine.) Start jboss from the jboss/bin directory with 'run.bat' or 'run.sh' depending on your OS. Then start up the server where you deployed the war. Once the server where the webapp was deployed has started, go the following url: http://localhost:8080/rr-employee-webapp.
TODO: document the code snippets that follow...
Java5 The Java5 JDK needs to be set as your environment. JAVA_HOME env variable needs to be defined pointing to yth the JDK home directory.
JBoss 4.x - Download JBoss 4.x here http://labs.jboss.com/portal/jbossas/download. Since you'll be probably be coding with the EJB 3.0 spec soon enough, set up JBoss4.x using the installer as described on the download page. After JBoss is installed declare a JBOSS_HOME environment variable pointing to the JBoss root directory.
Using the build.xml files To run the builds you will need ant installed. Running "ant all" from the root of the project directory will build the entire project. Use can just run "ant deploy" which will do all plus deploy the ear to jboss (or the war file if working with the webapp project. Remember if you use 'ant deploy' make sure you are doing it with your project on the correct server. This demo is showig a remote call so the war is deployed on one server and the ear on another server. Might be easiest to just use 'ant all' and the manually move the ear and war file to where you want to deploy them (each is found in the {project}/build/distribution directory)). **IMPORTANT: make sure to change he jndi/jndi.properties file in the rr-employee-webapp project to match the IP of the machine where you are deploying you ear!
Running the example You'll need the ear file and the war file. Run the build files to build the ear and war as described in the step above. Deploy the ear to your JBoss /server/default/deploy directory and deploy the war to another application server (another JBoss instance or even just standalone Tomcat instance is fine.) Start jboss from the jboss/bin directory with 'run.bat' or 'run.sh' depending on your OS. Then start up the server where you deployed the war. Once the server where the webapp was deployed has started, go the following url: http://localhost:8080/rr-employee-webapp.
A Note About XDoclet
XDoclet reduces the amount of annoying code you have to write when
dealing with EJB 2.1 by probably 90%. I didn't use XDoclet here since
I wanted to show all the steps you'd have go through to work with EJBs
when not using XDoclet. EJB3 is also greatly simplified with the use
of annotations.
The Persistence Layer
This example "Employee" application has the persistence classes in the
'common' section of our project. The src code there will be built into
a common.jar that will be part of our ear file. Our EJBs will simply
call a service class that in turn calls our dao to handle our CRUD
(create, retrieve, update, delete) operations. I could have just left
the service class out and had the EJBs call the daos directly, but my
personal preference is to have one extra layer of abstraction there
just in case I need to do any non-dao related stuff that still belongs
in a common area. The CRUD Employee dao in this example does nothing
more than work with an in-memory list of items.
Our EmployeeService class looks like:
Our EmployeeService class looks like:
public class EmployeeService {
private static EmployeeDao dao = new EmployeeSimpleDao();
public static List<Employee> getAllEmployees() {
return dao.getAllEmployees();
}
public static void updateEmployee(Employee emp) {
dao.update(emp);
}
public static void deleteEmployee(Integer id) {
dao.delete(id);
}
public static Employee getEmployee(Integer id) {
return dao.getEmployee(id);
}
public static void insertEmployee(Employee emp) {
dao.insert(emp);
}
}
EmployeeFacade
This is our EJB that has the actual business method implementations
(our calls to our service/persistence layer). I followed the Sun convention and the one used in the
"JBoss At Work" book and called all our object a Facade since our EJB
acts as a Stateless Session Facade object
http://java.sun.com/blueprints/corej2eepatterns/Patterns/SessionFacade.html -
simply the object that exposes our business methods for our client to use.
public class EmployeeFacadeBean implements SessionBean {
private SessionContext sessionCtx;
public void setSessionContext(SessionContext sessionCtx) throws EJBException {
this.sessionCtx = sessionCtx;
}
public void ejbCreate() throws CreateException {}
public void ejbRemove() throws EJBException {}
public void ejbActivate() throws EJBException {}
public void ejbPassivate() throws EJBException {}
public List<Employee> getAllEmployees() throws EJBException {
return EmployeeService.getAllEmployees();
}
public void updateEmployee(Employee emp)throws EJBException {
EmployeeService.updateEmployee(emp);
}
public void deleteEmployee(Integer id) throws EJBException {
EmployeeService.deleteEmployee(id);
}
public Employee getEmployee(Integer id) throws EJBException {
return EmployeeService.getEmployee(id);
}
public void insertEmployee(Employee emp) throws EJBException {
EmployeeService.insertEmployee(emp);
}
}
TODO: document the code snippets that follow...
EmployeeFacadeRemote
import java.rmi.RemoteException; import java.util.List; public interface EmployeeFacadeRemote extends javax.ejb.EJBObject { public List<Employee> getAllEmployees() throws RemoteException; public void updateEmployee(Employee emp) throws RemoteException; public void deleteEmployee(Integer id) throws RemoteException; public Employee getEmployee(Integer id) throws RemoteException; public void insertEmployee(Employee emp) throws RemoteException; }
EmployeeFacadeRemoteHome
import javax.ejb.CreateException;
import javax.ejb.EJBHome;
public interface EmployeeFacadeRemoteHome extends EJBHome {
public static final String COMP_NAME = "java:comp/env/ejb/EmployeeFacade";
public static final String JNDI_NAME = "ejb/EmployeeFacadeRemote";
public EmployeeFacadeRemote create() throws CreateException;
}
ejb/META-INF/ejb-jar.xml
<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd"
version="2.1">
<description>ejb-jar.xml for rr-employee-ejb</description>
<display-name>ejb-jar.xml</display-name>
<enterprise-beans>
<!-- Session Beans -->
<session>
<description>EmployeeFacade Session Bean</description>
<display-name>EmployeeFacade Session Bean</display-name>
<ejb-name>EmployeeFacade</ejb-name>
<home>net.reumann.ejb.EmployeeFacadeRemoteHome</home>
<remote>net.reumann.ejb.EmployeeFacadeRemote</remote>
<local-home>
net.reumann.ejb.EmployeeFacadeLocalHome
</local-home>
<local>net.reumann.ejb.EmployeeFacadeLocal</local>
<ejb-class>net.reumann.ejb.EmployeeFacadeBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
</session>
</enterprise-beans>
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>EmployeeFacade</ejb-name>
<method-intf>Local</method-intf>
<method-name>getAllEmployees</method-name>
<method-params></method-params>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>EmployeeFacade</ejb-name>
<method-intf>Local</method-intf>
<method-name>updateEmployee</method-name>
<method-params>
<method-param>net.reumann.common.vo.Employee</method-param>
</method-params>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>EmployeeFacade</ejb-name>
<method-intf>Local</method-intf>
<method-name>deleteEmployee</method-name>
<method-params>
<method-param>java.lang.Integer</method-param>
</method-params>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
....
</assembly-descriptor>
</ejb-jar>
ejb/META-INF/jboss.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss PUBLIC "-//JBoss//DTD JBOSS 4.0//EN" "http://www.jboss.org/j2ee/dtd/jboss_4_0.dtd">
<jboss>
<enterprise-beans>
<session>
<ejb-name>EmployeeFacade</ejb-name>
<jndi-name>ejb/EmployeeFacadeRemote</jndi-name>
<local-jndi-name>EmployeeFacadeLocal</local-jndi-name>
<method-attributes></method-attributes>
</session>
</enterprise-beans>
<assembly-descriptor></assembly-descriptor>
<resource-managers></resource-managers>
</jboss>
common package ServiceLocator
public class ServiceLocator {
...
public static EJBHome getEjbRemoteHome(String remoteHomeJndiName, Class ejbRemoteHomeClass) throws ServiceLocatorException {
EJBHome remoteHome = null;
try {
/*
InitialContext will look for a jndi.properties in classpath.
we defined out jndi.properties in the jndi dir of this project
and the build puts in the classes dir in the common package
use jndi.properties, but if you wanted to do it with code, you'd do:
Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
env.put(Context.PROVIDER_URL, "someIP:1099");
env.put("java.naming.factory.url.pkgs", "org.jboss.naming:org.jnp.interfaces");
Context ctx = new InitialContext(env);
*/
Context ctx = new InitialContext();
Object narrowFromObj = ctx.lookup(remoteHomeJndiName);
remoteHome = (EJBHome) PortableRemoteObject.narrow(narrowFromObj, ejbRemoteHomeClass);
} catch (ClassCastException cce) {
throw new ServiceLocatorException(cce);
} catch (NamingException ne) {
throw new ServiceLocatorException(ne);
}
return remoteHome;
} ...
}
webapp pacakge - example call to an EJB in
a Servlet (EmployeeAction)
*Note: For a real-life application is probably best to put these ejb calls in some kind of other layer outside your serlvet. This will make it easier to change around whether you need to make local EJB calls or remote ones.
EmployeeFacadeRemoteHome employeeHome =
(EmployeeFacadeRemoteHome) ServiceLocator.getEjbRemoteHome(EmployeeFacadeRemoteHome.JNDI_NAME,
EmployeeFacadeRemoteHome.class);
EmployeeFacadeRemote employeeEJB = employeeHome.create();
List<Employee> employees = employeeEJB.getAllEmployees();
ear application.xml file
<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/application_1_4.xsd"
version="1.4">
<display-name>rr-employee-ejb EAR</display-name>
<module>
conf.jar
</module>
<module>
<java>common.jar</java>
</module>
<module>
<java>persistence.jar</java>
</module>
<module>
<ejb>employee-ejb.jar</ejb>
</module>
</application>
</code></pre></div>