EJB
CONTENTS
Intro
EJB classes
A complete session example
Server side
Client side
Session Beans
Entity Beans
A complete, entity example
Server side
Client side
EJB-QL

Intro

Types of beans:
Basic call: client -> stub -> skeleton -> server
to top

EJB classes

Classes (and other files) required to implement an EJB:
The bean class:
A request is delegated to the bean object. Before delegation, the middleware handles:
The request is handled by an 'EJB Object', which delegates to the bean.
The remote interface implements javax.ejb.EJBObject
import javax.ejb.*; import java.rmi.Remote; import java.rmi.RemoteException; public interface EJBObject extends Remote { EJBHome getEJBHome() throws RemoteException; Object getPrimaryKey() throws RemoteException; void remove() throws RemoteException, RemoveException; Handle getHandle() throws RemoteException; boolean isIdentical(EJBObject) throws RemoteException; }
The remote interface implements these, plus business methods.
The home class must implement the EJBHome interface.
import javax.ejb.*; import java.rmi.Remote; import java.rmi.RemoteException; public interface EJBHome extends Remote { EJBMetaData getEJBMetaData() throws RemoteException; HomeHandle getHomeHandle() throws RemoteException; void remove(Handle handle) throws RemoteException, RemoveException; void remove(Object primaryKey) throws RemoteException, RemoveException; }
The local interface implements javax.ejb.EJBLocalObject
import javax.ejb.*; public interface EJBLocalObject { EJBLocalHome getEJBLocalHome() throws EJBException; Object getPrimaryKey() throws EJBException; void remove() throws EJBException, RemoveException; boolean isIdentical(EJBLocalObject) throws EJBException; }
The local home class must implement the EJBLocalHome interface.
import javax.ejb.*; public interface EJBLocalHome extends Remote { void remove(Object object) throws EJBException, RemoveException; }
Outline for a session bean:
import javax.ejb.*; public class XxxxBean implements SessionBean { private SessionContext context; // ejb methods public void ejbCreate() { ... } public void ejbActivate() { ... } public void ejbPassivate() { ... } public void ejbRemove() { ... } public void setSessionContext(SessionContext ctx) { context = ctx; } // business methods public ... businessMethod1(...) throws ... { ... } public ... businessMethod2(...) throws ... { ... } }
The context object provides access to container services:
public interface EJBContext { EJBHome getEJBHome(); EJBLocalHome getEJBLocalHome(); boolean getRollbackOnly(); void setRollbackOnly(); javax.transaction.UserTransaction getUserTransaction(); boolean isCallerInRole(String role); java.security.Principal getCallerPrincipal(); } public interface SessionContext extends EJBContext { EJBObject getEJBObject() throws IllegalStateException; EJBLocalObject getEJBLocalObject() throws IllegalStateException; MessageContext getMessageContext() throws IllegalStateException; } public interface EntityContext extends EJBContext { Object getPrimaryKey() throws IllegalStateException; EJBObject getEJBObject() throws IllegalStateException; EJBLocalObject getEJBLocalObject() throws IllegalStateException; } public interface MessageDrivenContext extends EJBContext { }
to top

A complete session example

This example uses JBoss.

Server side

The directory structure:
project testejb src net alethis testejb Calc.java CalcBean.java CalcHome.java CalcLocal.java CalcLocalHome.java META-INF ejb-jar.xml bin // classes go here dist // test.jar file goes here
The remote interface:
package net.alethis.testejb; import java.rmi.RemoteException; import javax.ejb.*; public interface Calc extends EJBObject { double averageWordLength(String input) throws RemoteException; }
The local interface:
package net.alethis.testejb; import javax.ejb.*; public interface CalcLocal extends EJBLocalObject { double averageWordLength(String input); }
The home interface:
package net.alethis.testejb; import java.rmi.RemoteException; import javax.ejb.*; public interface CalcHome extends EJBHome { Calc create() throws RemoteException, CreateException; }
The local home interface:
package net.alethis.testejb; import javax.ejb.*; public interface CalcLocalHome extends EJBLocalHome { CalcLocal create() throws CreateException; }
The bean class:
package net.alethis.testejb; import javax.ejb.*; import java.util.*; public class CalcBean implements SessionBean { static final long serialVersionUID = -3235974807011685965L; private SessionContext context; // ejb methods public void ejbCreate() { System.out.println("ejbCreate"); } public void ejbActivate() { System.out.println("ejbActivate"); } public void ejbPassivate() { System.out.println("ejbPassivate"); } public void ejbRemove() { System.out.println("ejbRemove"); } public void setSessionContext(SessionContext sc) { System.out.println("setSessionContext"); context = sc; } // business methods public double averageWordLength(String in) { double wordCount = 0; double charCount = 0; StringTokenizer st = new StringTokenizer(in, " \t\n\r"); while (st.hasMoreTokens()) { String t = st.nextToken(); wordCount++; charCount += t.length(); } double ratio = (wordCount == 0) ? 0 : (charCount/wordCount); return ratio; } }
The ejb-jar.xml file:
<?xml version='1.0'?> <!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN" "http://java.sun.com/dtd/ejb-jar_2_0.dtd"> <ejb-jar> <enterprise-beans> <session> <ejb-name>Calc</ejb-name> <home>net.alethis.testejb.CalcHome</home> <remote>net.alethis.testejb.Calc</remote> <local-home>net.alethis.testejb.CalcLocalHome</local-home> <local>net.alethis.testejb.CalcLocal</local> <ejb-class>net.alethis.testejb.CalcBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> </session> </enterprise-beans> </ejb-jar>
The ant build.xml file:
<?xml version='1.0'?> <project name="testejb" default="deploy" basedir="."> <property name="src.dir" value="src"/> <property name="bin.dir" value="bin"/> <property name="dist.dir" value="dist"/> <property name="jboss.deploy.dir" value="/software/jboss-4.0.1sp1/server/default/deploy"/> <path id="jars"> <pathelement location="/software/j2ee1.4/lib/j2ee.jar"/> </path> <target name="prepare"> <delete dir="${bin.dir}"/> <delete dir="${build.dir}"/> <mkdir dir="${bin.dir}"/> <mkdir dir="${bin.dir}/META-INF"/> <mkdir dir="${dist.dir}"/> </target> <target name="compile" depends="prepare"> <javac srcdir="${src.dir}" destdir="${bin.dir}"> <classpath refid="jars"/> </javac> </target> <target name="build" depends="compile"> <copy file="${src.dir}/META-INF/ejb-jar.xml" todir="${bin.dir}/META-INF"/> <jar basedir="${bin.dir}" destfile="${dist.dir}/test.jar"/> </target> <target name="deploy" depends="build"> <copy file="${dist.dir}/test.jar" todir="${jboss.deploy.dir}"/> </target> </project>

Client side

Directory structure:
project testejbclient src net alethis testejbclient CalcClient.java bin // class files go here build.xml
The client class:
package net.alethis.testejbclient; import javax.naming.*; import java.util.*; import net.alethis.testejb.*; public class CalcClient { public static void main(String[] args) { try { Hashtable env = new Hashtable(); env.put("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory"); env.put("java.naming.factory.url.pkgs", "org.jboss.naming:org.jnp.interfaces"); env.put("java.naming.provider.url", "localhost"); Context c = new InitialContext(env); Object o = c.lookup("Calc"); CalcHome home = (CalcHome) javax.rmi.PortableRemoteObject.narrow(o, CalcHome.class); Calc calc = home.create(); double ratio = calc.averageWordLength("Them Penguins is done like dinner"); System.out.println(ratio); calc.remove(); } catch (Exception e) { System.out.println(e.getMessage()); } } }
The ant build.xml file:
<?xml version='1.0'?> <project name="testejb" default="run" basedir="."> <property name="src.dir" value="src"/> <property name="bin.dir" value="bin"/> <path id="jars"> <pathelement path="${bin.dir}"/> <pathelement location="/project/testejb/dist/test.jar"/> <pathelement location="/software/j2ee1.4/lib/j2ee.jar"/> <pathelement location="/software/jboss-4.0.1sp1/client/jbossall-client.jar"/> </path> <target name="compile"> <javac srcdir="${src.dir}" destdir="${bin.dir}"> <classpath refid="jars"/> </javac> </target> <target name="run" depends="compile"> <java classname="net.alethis.testejbclient.CalcClient" fork="true"> <classpath refid="jars"/> </java> </target> </project>
to top

Session Beans

Two types:
Stateful session beans are subject to passivation. A bean involved in a transaction cannot be passivated until the transaction ends. There's no point passivating/activating a stateless session bean, as there's no state; the container is better just to create or destroy instances.
Any member of a stateful session bean class is part of the state if it's not transient. Any class having an instance in a stateful session bean state must be serializable. The state can include container objects such as EJB object references, EJB home references, EJB context references and JNDI naming contexts.
A stateful session bean may provided several ejbCreate(), with varying sets of parameters; it must provide at least one such method. A stateless session bean must provide a single, parameterless ejbCreate() method.
ejbPassivate() and ejbActivate() are useless for stateless session beans, and would only be defined for stateful session beans in the case where the bean holds persistent resources, such as sockets or database connections.
to top

Entity Beans

For each create() method in the home interface, there's an ejbCreate method, with the same parameters in the bean class. The bean class ejbCreate() returns a primary key, while the remote method returns the EJB object.
Outline for a BMP entity bean:
import javax.ejb.*; public class XxxxBean implements EntityBean { private EntityContext context; // ejb methods public PrimaryKey ejbCreate(...) throws CreateException { ... } public void ejbPostCreate(...) { ... } public void ejbActivate() { ... } public void ejbPassivate() { ... } public void ejbRemove() { ... } public void ejbLoad() { ... } public void ejbStore() { ... } public void setEntityContext(EntityContext ctx) { context = ctx; } public void unsetEntityContext() { context = null; } // finders public PrimaryKey findByPrimaryKey(PrimaryKey key) throws FinderException { // required for BMP // not needed for CMP ... } public Collection findBy...(...) throws FinderException { // other finders are optional // not needed for CMP ... } // home methods public ??? ejbHome...(...) { // correspond to methods on the home object ... } // business methods public ... businessMethod1(...) throws ... { ... } public ... businessMethod2(...) throws ... { ... } }
to top

A complete, entity example

This example uses JBoss.

Server side

The directory structure:
project testejb src net alethis testejb Galaxy.java GalaxyBean.java GalaxyHome.java GalaxyLocal.java GalaxyLocalHome.java META-INF ejb-jar.xml bin // classes go here dist // test.jar file goes here
The remote interface:
package net.alethis.testejb; import java.rmi.RemoteException; import javax.ejb.*; public interface Calc extends EJBObject { double averageWordLength(String input) throws RemoteException; }
The local interface:
package net.alethis.testejb; import javax.ejb.*; public interface CalcLocal extends EJBLocalObject { double averageWordLength(String input); }
The home interface:
package net.alethis.testejb; import java.rmi.RemoteException; import javax.ejb.*; public interface CalcHome extends EJBHome { Calc create() throws RemoteException, CreateException; }
The local home interface:
package net.alethis.testejb; import javax.ejb.*; public interface CalcLocalHome extends EJBLocalHome { CalcLocal create() throws CreateException; }
The bean class:
package net.alethis.testejb; import javax.ejb.*; import java.util.*; public class CalcBean implements SessionBean { static final long serialVersionUID = -3235974807011685965L; private SessionContext context; // ejb methods public void ejbCreate() { System.out.println("ejbCreate"); } public void ejbActivate() { System.out.println("ejbActivate"); } public void ejbPassivate() { System.out.println("ejbPassivate"); } public void ejbRemove() { System.out.println("ejbRemove"); } public void setSessionContext(SessionContext sc) { System.out.println("setSessionContext"); context = sc; } // business methods public double averageWordLength(String in) { double wordCount = 0; double charCount = 0; StringTokenizer st = new StringTokenizer(in, " \t\n\r"); while (st.hasMoreTokens()) { String t = st.nextToken(); wordCount++; charCount += t.length(); } double ratio = (wordCount == 0) ? 0 : (charCount/wordCount); return ratio; } }
The ejb-jar.xml file:
<?xml version='1.0'?> <!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN" "http://java.sun.com/dtd/ejb-jar_2_0.dtd"> <ejb-jar> <enterprise-beans> <session> <ejb-name>Calc</ejb-name> <home>net.alethis.testejb.CalcHome</home> <remote>net.alethis.testejb.Calc</remote> <local-home>net.alethis.testejb.CalcLocalHome</local-home> <local>net.alethis.testejb.CalcLocal</local> <ejb-class>net.alethis.testejb.CalcBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> </session> </enterprise-beans> </ejb-jar>
The ant build.xml file:
<?xml version='1.0'?> <project name="testejb" default="deploy" basedir="."> <property name="src.dir" value="src"/> <property name="bin.dir" value="bin"/> <property name="dist.dir" value="dist"/> <property name="jboss.deploy.dir" value="/software/jboss-4.0.1sp1/server/default/deploy"/> <path id="jars"> <pathelement location="/software/j2ee1.4/lib/j2ee.jar"/> </path> <target name="prepare"> <delete dir="${bin.dir}"/> <delete dir="${build.dir}"/> <mkdir dir="${bin.dir}"/> <mkdir dir="${bin.dir}/META-INF"/> <mkdir dir="${dist.dir}"/> </target> <target name="compile" depends="prepare"> <javac srcdir="${src.dir}" destdir="${bin.dir}"> <classpath refid="jars"/> </javac> </target> <target name="build" depends="compile"> <copy file="${src.dir}/META-INF/ejb-jar.xml" todir="${bin.dir}/META-INF"/> <jar basedir="${bin.dir}" destfile="${dist.dir}/test.jar"/> </target> <target name="deploy" depends="build"> <copy file="${dist.dir}/test.jar" todir="${jboss.deploy.dir}"/> </target> </project>

Client side

Directory structure:
project testejbclient src net alethis testejbclient CalcClient.java bin // class files go here build.xml
The client class:
package net.alethis.testejbclient; import javax.naming.*; import java.util.*; import net.alethis.testejb.*; public class CalcClient { public static void main(String[] args) { try { Hashtable env = new Hashtable(); env.put("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory"); env.put("java.naming.factory.url.pkgs", "org.jboss.naming:org.jnp.interfaces"); env.put("java.naming.provider.url", "localhost"); Context c = new InitialContext(env); Object o = c.lookup("Calc"); CalcHome home = (CalcHome) javax.rmi.PortableRemoteObject.narrow(o, CalcHome.class); Calc calc = home.create(); double ratio = calc.averageWordLength("Them Penguins is done like dinner"); System.out.println(ratio); calc.remove(); } catch (Exception e) { System.out.println(e.getMessage()); } } }
The ant build.xml file:
<?xml version='1.0'?> <project name="testejb" default="run" basedir="."> <property name="src.dir" value="src"/> <property name="bin.dir" value="bin"/> <path id="jars"> <pathelement path="${bin.dir}"/> <pathelement location="/project/testejb/dist/test.jar"/> <pathelement location="/software/j2ee1.4/lib/j2ee.jar"/> <pathelement location="/software/jboss-4.0.1sp1/client/jbossall-client.jar"/> </path> <target name="compile"> <javac srcdir="${src.dir}" destdir="${bin.dir}"> <classpath refid="jars"/> </javac> </target> <target name="run" depends="compile"> <java classname="net.alethis.testejbclient.CalcClient" fork="true"> <classpath refid="jars"/> </java> </target> </project>
to top

EJB-QL

Format in descriptor file:
<query> <query-method> <method-name>findAllTrains</method-name> <method-params> <method-param>java.lang.String</method-param> </method-params> </query-method> <ejb-ql>SELECT OBJECT(t) FROM Train t WHERE t.serial = ?1</ejb-ql> <result-mapping>Local</result-mapping> </query>
The result-mapping is not needed for finders, as they are defined in the local or remote interface, and the result will be set up accordingly. For finders, the query must always produce data corresponding to an EJBObject or EJBLocalObject.
For ejbSelect...(), the result is ambiguous, as the method is declared in the bean implementation class. In this case, if the result will be an EJBObject or EJBLocalObject, the result-mapping must be supplied. On the other hand, queries for ejbSelect() methods can return primitive values.
Example queries:
<!-- general select --> SELECT OBJECT(t) FROM Train t <!-- 'AS' is optional --> SELECT OBJECT(t) FROM Train AS t <!-- select with condition --> SELECT OBJECT(t) FROM Train t WHERE t.builtYear < 1980 <!-- select with parameterized condition --> SELECT OBJECT(t) FROM Train t WHERE t.builtYear < ?1 <!-- traversing the object graph --> SELECT t.factory FROM Train t WHERE t.serial = ?1 <!-- collection variables --> SELECT OBJECT(t) FROM Company c, IN(c.trains) t <!-- collection variables --> SELECT OBJECT(c) FROM Company c WHERE c.trains IS NOT EMPTY <!-- collection variables --> SELECT OBJECT(t) FROM Company c, IN(c.trains) t WHERE c.id = ?1 AND t.location = 'Chicago' <!-- distinct --> SELECT OBJECT(s) FROM Class c, IN(c.students) s <!-- aggregation --> SELECT MIN(t.builtYear) FROM Train t <!-- order --> SELECT OBJECT(t) FROM Train t ORDER BY t.buildYear DESC <!-- select returning a related object --> SELECT t.company FROM Train t WHERE t.builtYear < 1980 </query>
Built-in functions
Operations
Aggregation functions
String literals are in single quotes. Use doubled single quote to encode a contained single quote. Boolean literals are TRUE and FALSE (not case-sensitive).
to top