Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Mastering Enterprise JavaBeans™ and the Java 2 Platform, Enterprise Edition - Roman E

..pdf
Скачиваний:
41
Добавлен:
24.05.2014
Размер:
6.28 Mб
Скачать

J2EE in the Real World: Combining Servlets with Enterprise JavaBeans 491

/*

* First, turn the quote into an order */

order = quote.purchase();

/*

* Next, charge the user */

Customer customer = order.getCustomer();

String custAcctNum = teller.lookupAccountNumber(customer.getName()); String storeAcctNum = teller.lookupAccountNumber("Jasmine"); teller.transfer(custAcctNum, storeAcctNum, quote.getTotalPrice());

/*

* Finally, commit the transaction */

userTran.commit();

}

catch (Exception e) { try {

log(e);

userTran.rollback();

throw new ServletException(e.toString());

}

catch (Exception ex) { log(ex);

throw new ServletException(e.toString() +

" ** Rollback failed ** : " + ex.toString());

}

}

/*

* Payment received -- clear the quote */

quote.clear();

/*

* Then write the response */

out.println(

"<h3>Thank you for shopping with us. " +

"<p>Your order number is " + ((OrderPK)order.getPrimaryKey()).orderID + "." + "<p>Please shop with us again soon!</h3>" +

"<p><i><a href=\"" + response.encodeUrl("/servlet/wsf") + "\">Click here to return to the main page.</a></i>" +

"</body></html>");

Source 15.7 PurchaseServlet.java (continues).

Go back to the first page for a quick link to buy this book online!

492

 

M A S T E R I N G E N T E R P R I S E J A V A B E A N S

 

 

 

 

 

out.close();

 

}

 

 

private void log(Exception e) {

 

 

getServletConfig().getServletContext().log(e, "");

 

}

 

 

public String getServletInfo() {

 

 

return "This servlet performs an actual purchase, prints out " +

 

 

"a receipt, and returns the user to the Web storefront.";

 

}

 

}

 

 

 

 

 

Source 15.7 PurchaseServlet.java (continued).

The most interesting aspect of the code in Source 15.7 is that it shows how to perform a transaction programmatically from a servlet. When the user purchases his or her goods, we’d like to both submit an order to manufacture the goods and bill the customer for the cost of the goods. We’d like these operations to either both succeed or both fail, but never have one succeed without the other. Therefore, we perform the operations as a single transactional atomic operation. To do this, we need to begin and end a transaction from a servlet. You can do this by accessing the Java Transaction API (JTA)’s UserTransaction interface. The UserTransaction interface allows you to demarcate transactional boundaries in your Java code, such as this example shows. To get access to the UserTransaction interface, we look it up via JNDI. See Chapter 10 for a complete description of the UserTransaction interface.

The other interesting thing to notice about our code is that we keep the user’s cleared Quote around even after the Order has been generated, so the user can continue to shop in the store.

This completes the servlets for our e-commerce deployment. The receipt screen is shown in Figure 15.7.

The Servlet Properties

The final ingredient in our presentation tier is our servlet’s properties file. This is read in by the servlet engine, and it associates servlets with common names that can be referenced in a thin-client browser. For example, we call the Login servlet “login” and the Web Storefront servlet “wsf”; those will be part of the URL the end user needs to type into his browser (such as “http://localhost:8080/ servlet/wsf”).

Go back to the first page for a quick link to buy this book online!

J2EE in the Real World: Combining Servlets with Enterprise JavaBeans 493

Figure 15.7 The final purchasing screen.

Our properties file also lists a number of JNDI initialization parameters. Our servlets will use these parameters to connect to the home objects for the enterprise beans they use. In general, you should externalize properties such as these to properties files so that you don’t need to recompile your servlets if you change EJB container/server or if you change where your beans are located when they’re deployed. This is exactly analogous to how we externalized JNDI parameters to our EJB properties files when our enterprise beans looked up other enterprise beans using JNDI.

The code for servlet.properties is in Source 15.8.

#Used by Servletrunner.

#This file contains the properties for the Ecommerce GUI servlets.

#

# The servlet that manages list of products available

#

Source 15.8 servlet.properties (continues).

Go back to the first page for a quick link to buy this book online!

494 M A S T E R I N G E N T E R P R I S E J A V A B E A N S

servlet.login.code=com.wiley.compBooks.ecommerce.LoginServlet

servlet.login.initArgs=\

java.naming.factory.initial=weblogic.jndi.TengahInitialContextFactory,\

java.naming.provider.url=t3://localhost:7001

#

# Web Storefront to our entire online-store

#

servlet.wsf.code=com.wiley.compBooks.ecommerce.WebStorefrontServlet

#

# The servlet that manages list of products available

#

servlet.productbase.code=com.wiley.compBooks.ecommerce.ProductBaseServlet

servlet.productbase.initArgs=\

java.naming.factory.initial=weblogic.jndi.TengahInitialContextFactory,\

java.naming.provider.url=t3://localhost:7001

#

# View all the products in the store

#

servlet.catalog.code=com.wiley.compBooks.ecommerce.CatalogServlet

servlet.catalog.initArgs=\

java.naming.factory.initial=weblogic.jndi.TengahInitialContextFactory,\

java.naming.provider.url=t3://localhost:7001

#

# Show information about a specific product

#

servlet.productdetails.code=com.wiley.compBooks.ecommerce.ProductDetailServlet

#

# See the current quote

#

servlet.showquote.code=com.wiley.compBooks.ecommerce.ShowQuoteServlet

servlet.showquote.initArgs=\

java.naming.factory.initial=weblogic.jndi.TengahInitialContextFactory,\

java.naming.provider.url=t3://localhost:7001

#

# Purchase the quote

#

servlet.purchase.code=com.wiley.compBooks.ecommerce.PurchaseServlet

servlet.purchase.initArgs=\

java.naming.factory.initial=weblogic.jndi.TengahInitialContextFactory,\

java.naming.provider.url=t3://localhost:7001

Source 15.8 servlet.properties (continued).

Go back to the first page for a quick link to buy this book online!

J2EE in the Real World: Combining Servlets with Enterprise JavaBeans 495

Running the Complete E-Commerce System

We’re finally ready to go! Running our e-commerce system involves several steps. Let’s walk through the procedure together.

Starting the Business Logic Tier

First, we’ll start the business logic tier, which contains our enterprise beans defined in the previous chapters. To do this, we need to start one or more EJB servers. It doesn’t matter whether we do this before or after the presentation tier because the presentation tier won’t reference the business logic tier until we try to connect with a client. To start up the BEA WebLogic server we tested against, we ran the t3server program. See your EJB server documentation for details on starting your server.

Starting the Presentation Tier

Next, we’ll start up our presentation tier—one or more Web servers. For simplicity, we’ll use Sun’s basic free servlet engine, servletrunner, to manage our servlets. In a real deployment, you’d probably want to use the Java Web Server or some other advanced Web server. Note that you may need to import your servlet.properties file into other Web servers in some other manner because each Web server handles properties differently.

The arguments you pass to servletrunner are very important. You must pass an absolute path name to servletrunner when you tell it where your servlet classes are located. For example, on my machine, I run servletrunner as follows:

servletrunner -d f:\book\9\ecommerce\gui-classes -s

f:\book\9\ecommerce\servlet.properties

The -d parameter references where the servlet classes reside, and the -s parameter directs servletrunner to the correct servlet.properties file. Depending on where you install the servlets, your parameters will vary.

Starting the Thin Client

Finally, we start up our thin-client browser, such as Microsoft Internet Explorer or Netscape Navigator. Because servletrunner listens at port 8080 by default, we direct our Web browser to:

http://localhost:8080/servlet/login

Go back to the first page for a quick link to buy this book online!

496 M A S T E R I N G E N T E R P R I S E J A V A B E A N S

This will cause servletrunner to automatically load the Login servlet, initialize it, and have it service our HTTP request.

Have fun trying out the e-commerce deployment—you’ve earned it. There are many ways you can extend this deployment as well. For example:

■■Play around with different settings in the Pricer rules, and see how that affects your discounts in the view quote screen.

■■Try adding a Web-based maintenance utility for importing new Products and Customers.

■■Generalize the Pricer to use database-based pricing rules, rather than properties files. Make some new advanced pricing rules that take into account bundling of different kinds of products.

■■Add shipping charges to orders.

■■Write a fulfillment utility for setting Order status. You’d have to add a new “order status” field to the Order entity bean.

■■Add functionality to view existing orders. This could enable users to view how their orders are progressing (whether the order is still being manufactured, being shipped, been delivered, etc.).

■■Add a mechanism to associate more details with each product, such as a picture of the product.

■■Write a credit card billing system to allow credit-card purchases. Add a credit card number field to the Customer Bean.

■■Write a maintenance utility to add new Customers or Products.

There are a plethora of ways you can go with this—and we encourage you to do so. Only by programming with EJB will you truly understand its limitations and virtues.

Optimizations and Design Strategies

Now that we’ve seen our e-commerce example in action, let’s go over a few ways we can tune and optimize it to make it more robust. We’ll also look at a few general EJB design strategies that you can apply in your EJB projects.

Unique Primary Key Generation

In our Quote bean designed in Chapter 14, we used a primary key generator via System.currentTimeMillis(). This generates a long integer based on the number of milliseconds that have passed since 1970—a quick and dirty way to get unique values.

Go back to the first page for a quick link to buy this book online!

J2EE in the Real World: Combining Servlets with Enterprise JavaBeans 497

However, this has a serious limitation. If two concurrent users create primary keys in the same millisecond, we may have a uniqueness collision. And remember, primary keys must be unique values within the database. How can we solve this problem?

One mechanism is to have a singleton object responsible for creating all primary keys. The singleton would ensure that all primary keys are unique because it governs primary key generation. All clients who wish to create a new primary key would have to go through this singleton, even if it involves a network round trip. In fact, this singleton could simply call System.currentTimeMillis(). The only restriction is that we’d need to ensure that only one client was ever calling this singleton at any given time. We could easily do this by making a method such as:

public abstract class uniqueGen {

private static long uniqueID = System.currentTimeMillis();

public static synchronized long getUnique() { return uniqueID++;

}

}

Because the method is synchronized, only one client can ever be executing within the method at once. Since we increment the static variable uniqueID on each method call, each client will get a monotonically increasing value, guaranteeing unique primary keys. If the program crashes, the uniqueID field will be reinitialized to the current time, which is always greater than any previously generated primary key values, guaranteeing that no collisions will occur. The downside to this algorithm is that it does not guarantee uniqueness across multiple servers because each server has its own uniqueGen singleton. Two instances of uniqueGen on two different servers could generate the same primary key.

And, unfortunately, we can’t make a singleton enterprise bean either. The EJB 1.0 specification disallows static variables, which eliminates any possibility of a singleton bean. But singletons are a very important and useful design pattern. How do we use singletons in EJB?

The answer is to use JNDI. In Appendix B, we learn how to bind a networked Java object to a directory. Using that mechanism, we can have a singleton object be bound and available at a well-known JNDI tree location. All clients who need to generate Primary Keys would reference this singleton across machine boundaries. The trade-off here is that any Primary Key generation involves a JNDI lookup and a possible network call, which is expensive.

Another alternative is to use the database itself to create Primary Keys. Many databases have their own (proprietary) way of having a unique counter. You can

Go back to the first page for a quick link to buy this book online!

498 M A S T E R I N G E N T E R P R I S E J A V A B E A N S

use this to your advantage, by hard-coding a hook to your database’s proprietary unique counting mechanism and using that for Primary Key generation. The problem here is your code is nonportable, unless you use properties files to customize the particular database’s counter mechanism. But there’s another, even larger problem as well—to ensure that two concurrent clients each get a unique counter, you need to serialize all calls to the database counter by making each client run in a transaction, using the TRANSACTION_SERIALIZABLE isolation level. This makes your system grind to a halt because serializable transactions are very, very expensive.

Perhaps the best way to create unique primary keys is to use a globally unique identifier algorithm, such as GUIDs. There are many native algorithms available that take into account things like the current time and your net card’s NIC address to ensure that the chance of two generated IDs colliding is almost zero.

Reducing Network Round Trips: Lazy-Loading Beans

One way in which our code is not optimized is that a bean’s ejbLoad() call forces the bean to load of all its referenced subbeans, all at the same time. This could be a huge waste of resources because the client may never call a method that uses those subbeans.

A better way to approach this is to lazy-load beans. Lazy-loading is just-in-time loading of beans, and it reduces the large one-time hit of initially loading an entire graph of subbeans. A future version of EJB may standardize how this is done— perhaps via a deployment descriptor setting on container-managed fields.

For now, you can get lazy-loading functionality by adding a proxy layer of indirection between your beans and your subbeans. This proxy layer is responsible for loading beans dynamically as needed. Thus, instead of loading your subbeans in ejbLoad(), you load proxies, and the proxies load the real beans as needed when they’re referenced.

A fellow by the name of Rickard Oberg has developed such a proxy package. He calls it SmartProxies—intelligent proxies that perform lazy-loading of the real objects, plus a host of other nifty features. The idea is to load the proxies, rather than the real enterprise beans, in your ejbLoad() operations. When a client wants to use the real object, the smart proxy will load it dynamically.

Rickard Oberg has some other useful tools available as well. His home page is referenced from the book’s Web site (www.wiley.com/compbooks/roman).

Identifying Entity Bean Synchronization Needs

There is one final optimization we can apply to our e-commerce example. In Part II, when we first introduced entity beans, we described how a single entity bean

Go back to the first page for a quick link to buy this book online!

J2EE in the Real World: Combining Servlets with Enterprise JavaBeans 499

instance can be reused to represent many different database instances of entity bean data. For example, an instance of a bank account entity bean can be reused by the container to represent different bank accounts. When a container wants to load new data into an entity bean instance, it must passivate the entity bean instance and call ejbStore() to write the instance’s state to the database.

Unfortunately, ejbStore() is a heavyweight operation because it involves database work. Is it really necessary to call this method? After all, the entity bean instance’s state may not need to be written to disk if it hasn’t changed. Having to call ejbStore() for unmodified entity beans seems like a waste of effort.

Some EJB product vendors have worked around this and made vendor-specific ways of identifying whether an entity bean needs to be persisted. The idea is to associate your entity bean with a dirty flag and persist to the database only if your entity bean is “dirty”—that is, if your bean has been modified. BEA’s WebLogic server does this—its EJB container first calls a bean method called isDirty(). If your Bean indicates that it’s dirty, the container will save the bean state to the database and call ejbStore(). Otherwise, it skips this operation, saving precious database bandwidth.

Unfortunately, EJB 1.0 does not standardize a way to perform isDirty(). A future version of EJB may change this. For now, you’ll have to go with a vendorspecific solution if you want this optimization.

Entity Bean versus Session Bean Design

Our e-commerce example has several session beans and entity beans in action together. As you’ve learned, session beans encapsulate a business task, such as computing the price of a quote. Changing a pricing algorithm should not affect client code because all pricing logic is encapsulated in a session bean, which resides on the business logic tier. Session beans also handle your transactional needs, reducing the need for clients to be aware of transaction behavior.

Entity beans, on the other hand, are useful for modeling persistent data and the business logic associated with that data. You must strive to keep the business logic in entity beans very simple because the more complex your business logic gets, and the more your entity beans are coupled to other parts of your system, the less reusable your entity beans become.

Try to have the scope of your business logic encapsulate everything the entity bean itself needs, but no more. For example, it would probably be wrong for you to perform any heavy interactions with other beans in your entity beans. Of course, because entity beans can reference other, smaller grained entity beans, you will need to perform some bean-bean interactions. But keep this logic

Go back to the first page for a quick link to buy this book online!

500 M A S T E R I N G E N T E R P R I S E J A V A B E A N S

straightforward, and have it revolve around the data itself. Keep the task-oriented application logic in session beans, where it can evolve over time. This will yield a highly extensible architecture.

As we’ve seen with the Teller bean example, ideally you should encapsulate all access to entity beans with a session bean facade. The session beans can perform bulk operations on the entity beans, reducing network latency.

Fine-Grained versus Coarse-Grained Entity Beans

One bottleneck with our deployment is that we make many cross-tier boundary calls. For example, when we display the Quote to the user, our servlets need to retrieve each Quote Line Item’s data, and they do so by calling our Quote Line Item enterprise beans. Each call is a cross-tier boundary call, which could result in network lockup—especially if several Web servers are concurrently performing similar tasks.

We’ve already reduced network round trips somewhat by having our session beans be caches for entity beans. Sometimes, though, that is not enough. One way you could boost the performance of this example is to provide aggregate entity bean get and set methods, which get/set many fields at once. Performing these coarse-grained operations will reduce the network load between your tiers tremendously.

In addition, you may want to cache some enterprise bean data in presentation tier objects. For example, you could supplement our Quote bean with a presen- tation-tier shopping cart that holds all the shopping cart data. A perfect way to do this is to cache your data in regular JavaBeans. This reduces the frequency of calling the business logic tier. Of course, the more you do this, the more your presentation tier starts looking like a business logic tier, which increases coupling between the tiers and lessens the extensibility of your deployment—so be careful with how far you take this notion.

In general, it’s acceptable to have fine-grained entity beans such as line-items, so long as you limit over-the-wire interactions with these fine-grained beans. My recommended approach is to interact with fine-grained entity beans through a façade of session beans or larger-grained entity beans.

Finding a Large Number of Entity Beans

When our Product Base servlet searched for every Product sold on the Web site, the returned entity beans could potentially represent millions of records in an underlying database. The database, as well as the network, could easily become bogged down because of this. If our client does not need all the entity beans, this could be wasted effort. What alternatives are there to reduce the clog?

Go back to the first page for a quick link to buy this book online!