7.8. Using an Enterprise JavaBeans ObjectSo far, we've seen how to write an enterprise bean and how to deploy it through an EJB container. Now let's look at how you use an enterprise bean as a client. 7.8.1. Finding Home Interfaces Through JNDIOnce an enterprise bean is deployed within an EJB container, the home interface for the bean has been exported under a particular name using JNDI. As a client, you need to know how to connect to the JNDI context of the remote EJB server, and you need to know the name for the bean home interface you're interested in. A typical way to connect to the JNDI naming context is to specify the initial context factory for JNDI and create a new naming context through it (for more details, see Chapter 6, "JNDI"): Hashtable props = new Hashtable(); // Specify the context factory for our EJB server props.put(Context.INITIAL_CONTEXT_FACTORY, "my.ejb.server.context.factory"); // Specify the URL for the context provider, if any props.put(Context.PROVIDER_URL, "my.server.jndi.url"); // Trying looking up the context javax.naming.Context ctx = null; try { ctx = new javax.naming.InitialContext(props); } catch (NamingException ne) { System.out.println("Failed to create JNDI context from EJB server"); } In this example client, we make a Hashtable that contains two standard JNDI properties, the INITIAL_CONTEXT_FACTORY and PROVIDER_URL properties. These property names are String constants defined on the javax.naming.Context interface, whose values are "java.naming.factory.initial" and "java.naming.provider.url", respectively. You can use the explicit string values if you prefer, but the constant values are provided by the Context interface as a convenience, since they're a bit easier to remember. The value of INITIAL_CONTEXT_FACTORY is the class name for a context factory, provided by your JNDI provider (the EJB server, in this case), and PROVIDER_URL is a URL used to connect remotely to the JNDI server running within the EJB server. The proper values for both of these items should be provided by your EJB application server provider. You can also specify the security principal under which you want to interact with the EJB/JNDI server, by including the Context.SECURITY_PRINCIPAL property in the call to create your naming context. This property value should be a java.security.Identity object that represents the identity of your client: Hashtable props = new Hashtable(); // Initialized other connection properties... java.security.Identity id = . . .; props.put(Context.SECURITY_PRINCIPAL, id); javax.naming.Context ctx = new javax.naming.InitialContext(props); ... Some EJB server providers use this identity in determining access to bean features, as specified in the bean deployment descriptors. The EJB 1.0 specification doesn't provide a standard for providing this identity, however, so check the documentation on your EJB server for specific details. For more details on accessing remote JNDI servers, see Chapter 6, "JNDI". Now that we have a JNDI naming context from the EJB server, we can look up the home interface for the bean we're interested in: ProfileHome pHome = null; try { pHome = (ProfileHome)ctx.lookup("jen.ejb.entity.beanManaged.ProfileHome"); } catch (NamingException ne) { System.out.println("Failed to lookup home for ProfileBean."); } In this case, we've assumed that the bean provider has deployed its home interface on the EJB server under the name "jen.ejb.entity.beanManaged.ProfileHome". With the home interface stub, our client can now create or find beans located on the server. 7.8.2. Creating/Finding BeansThe home interface for the bean contains methods that allow a client to create new beans or find existing beans (for entity beans). Continuing our example client, assuming we're using our entity ProfileBean from Example 7-9 and its corresponding home interface, we can create a new ProfileBean and get a stub reference to it as follows: Profile profile = null; try { profile = pHome.create("Kaitlyn"); } catch (DuplicatePersonException dpe) { System.out.println("Profile already exists for Kaitlyn."); } catch (RemoteException re) { System.out.println("Remote exception while creating profile."); } Here we're trying to create a new profile for someone named "Kaitlyn". We're using the create() method defined on our ProfileHome interface, which we've declared to throw RemoteException and our own DuplicatePersonException. In the client, we catch each of these exceptions and print a corresponding error message if it occurs. Now, if we thought a profile already existed for Kaitlyn, we could try finding it in persistent storage first, using one of the finder methods on our home interface: try { profile = pHome.findByPrimaryKey(new ProfilePK("Kaitlyn")); } catch (FinderException fe) { System.out.println("No profile found for Kaitlyn."); } catch (RemoteException re) { System.out.println("Remote exception while finding profile."); } If we weren't sure whether a profile for Kaitlyn had been created or not, we could try finding it first, using the code above, then create one if needed: if (profile == null) { // Create profile as before ... } 7.8.3. Using Client-Side TransactionsI mentioned before that you can specify at deployment time how your bean handles transactions, using one of the transaction attributes we already discussed. On the client-side, you can use the Java Transaction API (JTA) to create your own transaction boundaries. These transactions are taken into account, along with the bean's transaction-handling attributes, by the EJB container, to determine which transaction context to put the bean into for each remote method call your client makes. Again, a complete description of the JTA is beyond the scope of this chapter, but it's useful to take a quick look at how to create your own client transactions. Continuing the example client we've been working with in this section, we've already found the home interface for the ProfileBean and created/found a Profile for Kaitlyn. Now we want to make some changes to Kaitlyn's profile, but we want to make sure that all our changes are made before we commit them. To do this, we create our own "javax.transaction.UserTransaction"[5] and either commit it or roll it back, depending on whether we get an exception while setting the profile entries:
javax.transaction.UserTransaction xact = (UserTransaction)jndiCtx.lookup("javax.transaction.UserTransaction"); xact.begin(); try { profile.setEntry("username", "kschmitz"); profile.setEntry("password", "foobar"); profile.setEntry("interestGroups", "dogs:cartoons:napping"); xact.commit(); } catch (RemoteException re) { xact.rollback(); } We acquire the transaction from whatever transaction provider we're using on the client side. Here, we assume that we've retrieved a "JNDI context" from the EJB server and use it to look up an instance of "javax.transaction.UserTransaction" that the EJB server provides. When we use this transaction object, the EJB server can manage the transaction around calls to enterprise bean methods. We begin the transaction before starting our profile update. We make our updates to Kaitlyn's profile, and, if successful, we commit the transaction. If we get an exception from the EJB server along the way, we rollback our changes by calling rollback() on the transaction. If the ProfileBean was deployed with a transaction attribute of TX_SUPPORTS, TX_REQUIRED, or TX_MANDATORY, the EJB container should execute the bean methods within our client transaction context. The corresponding database updates are committed to the database when we commit() our transaction or rolled back if we rollback() our transaction. The bean does not need to know anything about the transaction boundaries, even if it is managing its own persistence. The EJB container manages the association between the client-specified transaction boundaries and the JDBC transactions initiated by the bean implementation. Copyright © 2001 O'Reilly & Associates. All rights reserved. |
|