March 3, 2010, 6:29 p.m.
posted by hashspark
DesignIn this section, we go through the process of designing several EJBs. Although the design process of an EJB application is 95% identical to the design process of a non-EJB application (maybe even 99% identical), some steps in this process require special attention. To discuss design, we need to change our thinking a bit. Throughout this book, we have focused on the details of EJBs and entity beans, and on how their individual components work. In this section, we consider the Titan EJB application as a system meeting a business need and not simply as a collection of fine-grained components. We will look at the design of such a system from the ground up, taking the application as a whole instead of continuing to view only the EJB components themselves (though we'll obviously pay special attention to those components, since this is a book on EJBs). Let's start by looking at its requirements. At a high level, the application will be used by:
The application will be accessed via three mechanisms. The first two mechanisms are for "person" users (as opposed to "system" users, described shortly):
The third access mechanism is for systems that need direct access to the business layer. For our application, this includes access by:
All three communications mechanisms (web client, standalone application, and business-to-business) must allow only secure actions to be executed by users. Connectivity to the external travel agencies and to the ship provisioning vendors is not guaranteed, so the communications mechanism will need to handle disconnects. Finally, we want to generate reservation confirmations and other forms in PDF format. Figure is a system diagram of our requirements so far. Application system diagram![]() Business Entity IdentificationNow that we know our application's requirements, at least at a high level, we can identify the key business entities that the application needs to represent. This is generally a lengthy process, and we will go over only some of the results here. Although in our example, this is presented as a systematic, one-time process, it is really iterative. You will probably take a first stab at identifying business entities and then go through the process repeatedly before having a final list. Here are some of the business entities for the Titan application:
There's more structure to this list than is immediately apparent. It follows a number of guidelines that help reveal the important aspects of each entity:
Because we focus on the components that will end up being EJBs, our functional analysis is completeselecting business entities is the most important part of the EJB design process for us.[*]
The next step is to look at the technical architecture and its implications for our entities. We'll get to that in just a moment. First, let's take a minute to diagram our business entities using UML so that we have a clear understanding of their relationships. While the textual descriptions help define the business relationships, a UML diagram depicts them more exactly. Figure is a UML diagram of our business entities and their relationships. UML diagram of the application's business entities![]() The UML diagram introduces a Person entity from which we will derive both the TravelAgent and Customer entities. We've also introduced a mapping entity for mapping Reservation entities to Cabin entities. Otherwise, the UML diagram states exactly what we described earlier in the text. The next step is to consider which objects to implement as EJBs and entity beans, and what types of EJBs to use. But first, it will help to understand the technical architecture of the system because aspects of it will have direct implications on our entity implementation choices. Technical ArchitectureEarlier, we depicted our system in a high-level diagram (Figure). This diagram depicts relationships between various entities and our system, but not much more. What else do we know about the various interactions of these entities and our Titan application?
Using this information, our technical system diagram can be amended as shown in Figure. Amended system diagramUML diagram of the application's business entities![]() Although it may not look like it at first, we've gotten much closer to identifying our EJBs and entity benas. Between the new architecture diagram and our business entity UML diagram, we have all we need to move forward. EJB and Entity Bean IdentificationNot all of our business entities will turn out to be EJBs or entity beans, so the next step in our design process is to identify which of them should. Our understanding of the application's technical architecture helps. This is not a simple or well-defined process, like completing a jigsaw puzzle or building a bridge. For all but the simplest of applications, the process of identifying EJBs and entities in the application's technical architecture presents ambiguity and conflicting requirements. It's not easy to make the right choices. Fortunately, several rules of thumb will help guide the process. Let's quickly review the component types:
Identifying entity beansWith these characteristics in mind, let's start out by identifying entity beans in our application:
Our class diagram was created from the list of business entities in our functional analysis, which are essentially the things in our functional requirements. We know right away that all of the components in the diagram are candidates for implementation as entity beans. Although it is tempting to use straight JDBC for pieces of data that are read-only, it is strongly suggested that you don't. Since you would probably be populating a Java object anyway from the result set of any JDBC invocation, why not let Java Persistence do the work for you? More importantly, though, most persistence providers supply nice caching layers so that frequently accessed data remains in memory and you can avoid making expensive network calls to your database. Identifying session beansWhile entity beans are the things in our application, session beans implement taskflow. They are the processors and workhorses; they do stuff. We will identify them by considering the work that our application must do. A good starting place is reviewing the responsibilities depicted in the class diagram. Looking over the class diagram, we see that TRavelAgent has the following responsibilities:
In any application, functionality seems to collect around one or more entities. Such a grouping of responsibilities often indicates that a session bean is needed, which brings us to the next guideline:
As for the name for the session bean, a good tactic is to create a name that reflects a combination of the target of the action and the action itself. For example, the TravelAgent creates and updates or "manages" Reservations, so a good name for our session bean might be ReservationManager. The primary objective of the name is to communicate what the session bean does. As the session bean encapsulates the responsibilities, each responsibility corresponds to a method in the EJB. So, our ReservationManager session bean will initially have three methods: bookReservation( ), updateReservation( ), and cancelReservation( ). These methods are also named intuitively to suggest what they do. If we follow this line of reasoning, we may think we need to have a separate session bean called CruiseManager. However, the only interaction the TravelAgent has with a Cruise is to list it. Furthermore, it could be argued that in the overwhelming majority of cases, the TravelAgent will list Cruises only when making a Reservation. For these reasons, it might make more sense to combine the Cruise functionality and simply add a new listCruises( ) method to the ReservationManager. The listCruises( ) method stands apart from the other methods a bit, both in effect (it reads data, while the other methods write data) and in direct object (it returns a collection of cruises, while the other methods manipulate a single reservation). This suggests Guideline #3:
We have now accounted for all the responsibilities depicted in the class diagram, but we haven't accounted for all the functionality specified in the functional requirements. Creating the initial class diagram from the business entities initially misses functionality that has no "source" entity. For example, we've focused only on the reservations and the actions and entities around them. However, a reservation involves a Cruise that has certain characteristics. Some part of our application must be available to administer these Cruises. Cruises are made up of Cabins and Ships. Our administration functionality should focus on the management of all three entities: Cruise, Ship, and Cabin. Our application revolves around travel agency functionality, but without the configuration of the cruises themselves, that functionality (creation of reservations, and so on) would be meaningless. Let's add a general session bean around this and other (to be determined) configuration chores. Although we may need to break this into multiple session beans later, we can start with one called ConfigurationManager . Here too, we want to give it methods based on the functionality it encapsulates. Because no taskflows are detailed at this point, we will assume that all three items need to be created, updated, and deactivated. Thus, these actions (for the three entities) become nine initial methods:
We can now expand our entity diagram into the class diagram shown in Figure. Expanded into a class diagram![]() Identifying message-driven beansNow we need to look for the message-driven beans in the application. As our review of the EJB types reminds us, message-driven beans (MDBs) implement taskflows like session beans, but they can be invoked asynchronously. Roughly put, they are transactional message handlers.[*] This suggests Guideline #4:
Here's where our system architecture diagram helps us. Messaging takes place between our system and another (ostensibly external) system in two places:
As you can see from our functional requirements and the technical architecture diagram, our system receives messages only from external travel agencies, so we'll focus on the travel agent functionality. Since we've not been told anything to the contrary, we assume that external travel agent systems function like ours. Thus, ours should include all the functionality incorporated into ReservationManager . Additionally, the external travel agencies need some way to retrieve a list of ships and their cabins. This listing ability is included because the external travel agency systems can communicate only via messaging. This suggests that one or more MDBs could be used to implement this functionality. For the Titan application, we will have two MDBs:
Compare the responsibilities of ReservationListener with those of ReservationManager. The cruise-listing behavior and the reservation-specific behavior are implemented in separate MDBs. Why? Guideline #4 tells us that if we are only going to execute a given piece of functionality in the context of a given process, we should combine that function with the others. This guideline is appropriate for session beans. Adding another method to a session bean does not introduce any complexity to the bean; it's just another method. However, in JMS-based MDBs, you have only one onMessage( ) function. While you can certainly have many different types of messages coming into the queue on which the MDB is listening, each one must be processed separately. Each message type adds another significant condition to the MDB's processing logic. Furthermore, the functionality represented by the various messages for the ReservationListener will be largely the same, but messages representing queries for cruise information might be different.
While we're covering JMS-based MDBs, it makes sense to discuss the importance of message design. When a message listener is invoked, the only information it has is the message that it has been passed. In many cases, the message listener needs specific business information to do its work, and that information is packaged in the message. Exactly how it is packaged depends on which message type you choose: javax.jms.Message or its subinterfaces (BytesMessage, MapMessage, ObjectMessage, StreamMessage , and TextMessage ). A general rule of thumb is to use ObjectMessage for messaging between systems that are guaranteed to be Java-based and to use TextMessage for messaging between potentially non-Java systems. Because ObjectMessage carries a full Java object, its data is already structured for easy access by the MDB, whereas all but the simplest data in a TextMessage (and the other types, to varying extents) will generally have to be processed before it can be used (by a StringTokenizer , an XML parser, Integer.parseInt , or something similar). On the upside, TextMessage (and maybe BytesMessage) is the universal message typeevery messaging system knows how to send and receive simple text (and binary data). With that said, you should investigate message types and their tradeoffs before making final decisions. Because we need to accept messages from the greatest variety of external travel agency systems, we will use TextMessage messages carrying XML payloads. Although it requires a heavy XML parser when processing messages, TextMessage provides interoperability benefits that fit our needs. We've now identified all of the EJBs and entity beans in our sample application. Figure shows an updated class diagram. Updated class diagram![]() EJB DetailsNow that we have identified the EJBs in our application along with some of their methods, we have completed about two-thirds of our design. So far, much of the design has flowed almost naturally from our business and technical requirements. The remaining one-third of the design is more difficult and requires some hard decisions. Stateless versus stateful session beansAs their names indicate, the difference between the two subtypes of session beans is the maintenance of state. A common source of confusion is that we use similar words when we talk about web session state, servlets, and other aspects of web-based applications. Session bean state is taskflow related and should have little or no relation to the Web or to presentation tiers of your application. Session bean state is a way of sharing information among multiple methods of the same session bean. For example, the stateful version of ReservationManager contains the current Customer so that it is not passed into the bookReservation( ), updateReservation( ), and cancelReservation( ) methods (see Figure). Stateful version of ReservationManager![]() Contrast that with the stateless version of ReservationManager, in which the current Customer is a parameter for those methods (see Figure). Stateless version of ReservationManager![]() The stateful session bean is more elegant when we need to call bookReservation, updateReservation, or cancelReservation multiple times, especially if you are using some of the extended persistence context queuing and batching features we discussed in Chapter 16. However, there are certain performance considerations to think about. A stateful session bean must be created for each client that needs one. This stateful bean instance stays in memory until it is removed by its client or is passivated by the EJB container. This is probably not an issue in most applications, but if you have tens of thousands of stateful bean clients, it may become an issue. The side effects of using a stateful bean in this scenario can be minimized by making sure clients release their stateful bean references when they don't need them anymore, and by tuning the passivation timeout of your EJB container. There are also performance considerations when you are clustering your stateful session beans. Clustered stateful session beans must have their state replicated to other nodes in the cluster so that client requests can successfully failover if a machine goes down. This can create a lot of network traffic and CPU cycles. In general, distributed systems should be designed to be as stateless as possible. However, if a client needs to have a stateful conversation with your application, then you're going to have to pay the cost of state replication somewhere in your application, whether it is in the web tier or in your EJB tier. Local versus remote interfacesDon't use remote interfaces unless you really have towe can't emphasize this enough. Distributing your EJBs adds a whole layer of complication that is often unnecessary. There are the basic, only somewhat irritating issues, such as handling RemoteException s in your client code, and there are the complex, intractable issues, such as loss of performance and reliability when your components must operate across a network. One big complication is that remote interfaces (and the implementation they present) are often difficult to change because they will be used by other systems or applications that may be resistant to change. Our application clearly needs to be distributed; it must support the standalone Java client that our internal travel agents will use. In your application, take a long, hard look at any requirements that push you in the direction of distributed components. Approach such requirements with common sense:
If, after this process, you determine that you need distributed functionality, your next task is to identify which EJBs should be implemented with remote interfaces and which should stay as local interfaces. In our application, travel agents will use the standalone client to access the full range of application functionality. We already know that session beans are the workhorses of our application, which is why we have exposed the session beans via remote interfaces. How do we pass entity data, such as cruise information, across the remote interface? Good answers to this question are provided in the upcoming section "Returning entity data from EJBs." Because entity beans are plain Java objects, they can be serialized to remote clients. Entity beans can be a part of the interface of an EJB component. It is a good idea to implement both remote and local interfaces for our session beans. While this results in slightly more code to build and maintain, it is a good idea to use the local interfaces in the code that runs inside the application server, such as servlets or JSPs. The small duplication is worth avoiding the remote interface. Thus, our interface recommendations are:
Entity DetailsWe may have finished identifying our entity beans and the relationships between them, but we still need to make a few design decisions regarding the persistence layer. One thing that needs consideration is how you transfer entity data from your session beans to your client code. Another thing that requires careful planning is how you interact with your data model. Database access usually has the most impact on the performance and scalability of your system. We'll discuss both of these issues in this section. Returning entity data from EJBsIf you have remote clients that access your EJBs, you need to figure out how you will transfer entity information to and from the server. Because entity beans are plain Java objects, this job can be as simple as having your entity classes implement the java.io.Serializable or Externalizable interface and passing them as parameters in the remote interface of your EJBs:
@Entity
public Customer implements java.io.Serializable {
...
}
@Remote
public interface TravelAgentRemote {
Ticket makeReservation(Reservation reservation);
Reservation findReservation(Customer cust, Cruise cruise);
void updateReservation(Reservation res);
}
For example, let's say you have a Swing GUI application that interacts with remote EJBs. It could create Reservation objects locally and send them to the server to be processed. The session bean could take the Reservation object as is and only call EntityManager.persist( ). Later, the Swing GUI might want to update this reservation. It would call the findReservation( ) method mentioned earlier, and it would display the Reservation on the GUI locally. Edits could be made directly to the Reservation instance on the client, sent back to the server, and merged with the EntityManager.merge( ) operation. There is one pitfall to this kind of design, though. An entity bean may have many nested relationships that create a complex object graph. You don't want to serialize entire object graphs over the wire to your remote clients because they may be too large. Therefore, it is good practice to set the javax.persistence.FetchType of all your relationships to LAZY so that they are lazily loaded on demand on the server. In your server logic that obtains and returns references to these complex entities, you can choose which information you want to be loadedbased on the business process you are performingby using features such as JOIN FETCH queries. Minimizing database accessThe biggest bottleneck in EJB applications is usually the database. It is usually a highly contended resource that requires the most optimizations. Why is this so? First, the database is quite likely in another process on the same machine as the application server or, more likely, on another machine in the network. We mentioned earlier (in the local versus remote interface debate) that network invocations are expensive. This is also true of database calls. Second, the databases have to synchronize to disk any updates that you perform. A disk can go only so fast and is a resource that can be highly contended. Hard drives have a maximum throughput, and once you reach this limit, you can't go much further to increase the performance of your system. Therefore, you want to minimize database interaction as much as possible in your application design, which means limiting the number of round trips to the database and even combining multiple updates and/or queries into single operations. There are a couple of portable ways to tune your database access that we'll discuss in this section: caching and minimizing database invocations. CachingCaching will probably have the biggest impact on performance for your system. Caching is the act of leaving data in memory so that when a business process needs access to a certain entity bean, the persistence provider does not have to communicate to the database to obtain this information. Although caching is not discussed in the Java Persistence specification, all vendors will at least have some sort of single VM global cache that can hold data in memory until it is needed. Most vendors will additionally have distributed caching available so that you can retain this performance benefit when you run your application in a cluster of networked computers. Caching usually works with only the find( ) and getreference( ) EntityManager methods because the cache usually comprises only simple maps, with the primary key as the identifier. EJB QL queries are usually never cached, but some vendors do provide some ability in this area as well. It is a good idea to check your vendor's caching capabilities before making any purchasing decisions. One thing to remember about caching is that one size does not fit all of the entity beans in your system. Although you can have a very large heap size with 64-bit systems nowadays, memory is still a finite resource. Some entities in your system may be accessed very rarely, and they may be using space that is needed by frequently accessed entities. In our Titan Reservation system, Ships, Cruises, and Cabins are f entities, and Customers and Reservations are rarely accessed after the initial order is placed in the system. In this case, it might make sense to eliminate or reduce the cache size for Customers and Reservations so that other entities that need caching can take up more room in memory. Another type of entity that you may not want to cache is one that is updated frequently. In this case, the cache is continuously being updated. While this isn't so important on applications that run on a single server, it can have performance penalties in a cluster because the updating server must send network messages to other nodes in the system to ensure that these remote caches are up-to-date. So, some good common-sense rules for caching are:
Which entities you should cache and how large your cache should be are factors that depend on the size of your database and the amount of memory you have available on your servers. It is best to create real-world simulations of the traffic your system will receive to fine-tune your cache settings. Don't make caching decisions blindly. Combining queriesEven if you have optimal cache settings, you can still do a lot to minimize the number of times you hit the database. One thing you can do is to combine queries. In the earlier section titled "Returning entity data from EJBs," we discussed how it is good practice to set the FetchType of your related entities to be LAZY so that you don't pull entire object graphs. A lazy fetch mode may have the side effect of forcing the underlying persistence provider to make multiple extra queries to traverse a relationship, when this relationship could have been fetched along with the root entity. For example, let's look at the @OneToMany Cruise-Reservation relationship:
1 Cruise cruise = entityManager.find(Cruise.class, 1) ;
2 ... // do some work on base cruise
3 Collection<Reservation> reservations = cruise.getReservations( );
4 for (Reservation res : reservations ) "
5 {
6 res.setDate(newDate);
7 }
If our @OneToMany Cruise-Reservation were set to LAZY fetching, then the preceding business process would result in two separate database queries. Line 1 would load the base cruise object. Line 3 would execute a separate query to pull in the reservations associated with the cruise. It is more optimal to pull in the reservations by doing a join when the cruise is first loaded to eliminate the extra queries. Your first reaction might be to change the FetchType to EAGER for this relationship. This is not a good choice because different business processes might not be interested in the reservations relationship. A better solution is to use the JOIN FETCH feature of EJB QL:
1 Query query = entityManager.createQuery("FROM Cruise c LEFT FETCH JOIN
c.reservations WHERE c.id = 1");
2 Cruise cruise = (Cruise)query.getSingleResult( );
3 ... // do some work on base cruise
4 Collection<Reservation> reservations = cruise.getReservations( );
5 for (Reservation res : reservations )
6 {
7 res.setDate(newDate);
8 }
In this code, even though the Reservation side of the relationship is marked as LAZY, the LEFT FETCH JOIN causes the relationship to be loaded when the cruise is queried. Minimizing updatesCertain circumstances in your code can pop up, causing additional, unneeded database updates within a transaction. Because improving performance is mostly about reducing database access, you should be aware of these circumstances. Consider the following transaction:
1 Reservation res = entityManager.find(Reservation.class, 1);
2 res.setAmountPaid(amount);
3
4 Query query = entityManager.createQuery("FROM Reservation res WHERE
res.dateReserved > 'APR-06-2005'");
5 List list = query.getResultList( );
6 for (Object obj : list) {
7 Reservation reservation = (Reservation)obj;
8 Reservation.setDateReserved(today);
9 }
The default FlushModeType for an EntityManager and for a Query is AUTO . This means that Line 4 may cause a database update of the Reservation updated in Line 2. The persistence provider may not be able to figure out whether the update in Line 2 affects the executed query. Because Lines 6-9 update reservations again, the same reservation might be updated in the database twice. A solution to this problem is to set FlushModeType to COMMIT . This defers any database updates, inserts, and deletes until the transaction commits.
entityManager.setFlushMode(FlushModeType.COMMIT);
Reservation res = entityManager.find(Reservation.class, 1);
res.setAmountPaid(amount);
Query query = entityManager.createQuery("FROM Reservation res WHERE
res.dateReserved > 'APR-05-2006'");
List list = query.getResultList( );
for (Object obj : list) {
Reservation reservation = (Reservation)obj;
Reservation.setDateReserved(today);
}
In many cases, the persistence provider can do batch queries when COMMIT is used and send all these changes in one network call. Another solution might be to always perform your queries upfront before any updates are made to the queried entities. Fleshing Out the DesignNow that you've determined the major aspects of your EJBs, all that remains is to complete the design down to the class and method levels. This is the same task you would perform for any application, so we will not cover it here. However, this stage can undo or compromise good EJB design if it is executed poorly. This section discusses the two most critical lessons we have learned to keep an EJB design in good shape. Don't confuse EJB typesThis may seem like a no-brainer, but don't try to make one EJB type behave like another. If you've been paying attention throughout this book (you have, haven't you?), the differences between EJB types should be pretty clear in your head. Session and message-driven beans manage processes (synchronously and asynchronously, respectively), entity beans persist data, and everyone is happy. That's great! There are two possible wrinkles, however:
As a consequence, you may find some of the following in your application:
These are all bad things.[
Minimize transaction scopeAs you flesh out your EJBs, especially session beans, make sure that your transactions have the smallest scope possible. By scope, we mean the number of operations executed and the number of components used. Operations executed inside a transactional context require more container management than nontransactional operations, and this management generally results in limitations and performance costs. The limitations depend on the container, database, and other transactional components of your application. Exceeding these limitations can create problems that depend greatly on the execution environment and the exact processing being done. This variability often makes diagnosis and troubleshooting of transactional problems difficult, so the best approach is to minimize transactional scope during design or early in coding. Here is how to identify possible transaction resource problems:
Repeat this evaluation if you make significant changes to your EJBs, especially after revisions that affect your session beans. ExceptionsExceptions are fundamental to error notification and management in Java. Understanding exceptions and how to handle them is even more important in EJB because exceptions have a significant effect on transaction control. Be sure to review the section on exceptions and transactions in Chapter 16. Exception design for EJBs is essentially the same as general exception design. With EJBs, though, you need to be aware of what types of exceptions roll back a transaction. Any exception annotated with @ApplicationException (rollback=true) causes a transaction rollback. Any exception annotated with @ApplicationException(rollback=false) does not roll back the transaction. If the <application-exception> element is used within the EJB XML deployment descriptor, the same rules apply. If there is no @ApplicationException annotation, or no EJB XML deployment descriptor metadata that defines the transaction rollback behavior, any java.lang.RuntimeException or java.rmi.RemoteException class, or any exception class that extends them, automatically causes a rollback. Although the EJB specification allows you to manually roll back a transaction with the EJBContext.setRollbackOnly( ) method, it is usually a best practice to let thrown exceptions determine the transaction's viability. This will shrink the number of try/catch blocks in your code base and will generally simplify exception handling. You must be thorough with your application of @ApplicationException, <application-exception>, and runtime or remote exceptions, however, to make sure that the behavior of your system remains consistent. Here are the fundamental steps in exception design:
Identifying business exceptionsThe first step is to determine the business exceptions. Business exceptions encapsulate business errors that prevent the completion of a taskflow. The user should be notified, or the application should attempt to recover from the error, or both. The essential criterion is that the error needs to be propagated several layers (at least) up the application call stack. For example, the Titan application would throw a business exception if a reservation could not be completed because the desired cruise was sold out, and this exception would cause the user interface to display an error message. Avoid scenarios where business exceptions are used as costly if-then statements or other forms of flow control. Exceptions are exceptional.[*]
Business exceptions can often be identified almost straight from your business requirements, so if the requirements are fully defined, much of the work in this step is already done. The trick is to make sure your exceptions focus on error conditions. Some developers have used exceptions for user interface control, which is bad. For example, if a query for cabin information from the Titan application produced no results, it is better to return an empty Collection than to throw an exception. Exceptions should be reserved for errors, and other mechanisms should be employed for controlling user interaction. Designing the exception hierarchyExamine your object model and object interaction diagrams to pinpoint all business exceptions that need to be thrown. After you have determined what business exceptions you need, incorporate them into a class hierarchy. A hierarchy provides at least two benefits:
Here are some specific steps to assist in creating the hierarchy:
Deciding on rollback exceptionsNow that you have your exception hierarchy, walk through it to determine whether each exception will cause an automatic transaction rollback. Even if you are annotation averse, this is the time to swallow your pride and use @ApplicationException to identify this behavior. This is good practice because the application code may want to determine generically whether a rollback happened.
try {
ejb.doOperation( );
} catch (com.titan.exceptions.AbstractException ex) {
ApplicationException ae = ex.getAnnotation(ApplicationException.class);
if (ae.rollback( ) == true) System.out.println("unrecoverable error");
else // try and recover
}
If you are thorough and use @ApplicationException everywhere in your exception hierarchy, you can examine this annotation metadata at runtime, without knowing the concrete type of the exception, to determine whether the transaction was rolled back. Don't forget to annotate Runtime or RemoteException s that do not roll back the transaction automatically with @ApplicationException! Checked versus unchecked exceptionsIn most cases, you will want to use unchecked exceptions for your exception hierarchy. With checked exceptions, a lot of unnecessary try/catch blocks turn up all over the place. Usually, when an exception is thrown, nothing can be done and the thread of execution needs to be aborted anyway. Use checked exceptions only when you want to force the calling code to handle the exception. Even though you will primarily use unchecked exceptions, it is a good idea to put the unchecked exception in the throws clause of your methods. Most IDEs have automatic try/catch block generation around invoked methods. If the calling code wants to catch a particular unchecked exception, the IDE automatically creates the correct catch block if the exception is in the throws clause of the called method. There is an informal category of checked exceptions that deserves special treatment. We call them subsystem exceptions. As the name indicates, subsystem exceptions are checked exceptions thrown by a subsystem of the JVM or a resource, such as JDBC or JMS. For example, IOException is thrown by the I/O subsystem; JMSException is thrown by JMS; SQLException is thrown by JDBC, and so on. These exceptions usually mean that the transaction needs to be rolled back, so you must take appropriate measures to handle them. Wrap subsystem exceptionsWhether or not subsystem exceptions should appear in your EJB method signatures is circumstantial. If the client can recover or retry the business operation based on information available from the subsystem exception, then it might be a good idea to put it within the throws clause. For instance, if you are doing straight JDBC, you may run into deadlock exceptions when doing the JDBC operation. In this case, you will want the exception to be rolled back, but the client may want to retry the operation because the deadlock conditions may have been cleared up with the rollback. In this case, you can add SQLException to your method signature, but you must also declare it as an <application-exception> in your XML deployment descriptor:
<ejb-jar>
<assembly-descriptor>
<application-exception>
<exception-class>java.sql.SQLException</exception-class>
<rollback>true</rollback>
</application-exception>
</assembly-descriptor>
</ejb-jar>
If the subsystem exception is not recoverable or retryable, then don't put it in the throws clause of your methods. In this case, you should catch and wrap the exception in an EJBException or an appropriate business exception:
try {
...
} catch ( SQLException se ) {
throw new EJBException("SQLException caught during processing: " +
se.getMessage( ), se);
} catch ( RemoteException re ) {
throw new EJBException("RemoteException caught during processing: "
+ re.getMessage( ), re);
}
Utility ClassesAs you design your EJBs, you will begin to spot areas of common functionality. For example, since several classes and functions deal with reservations in the Titan application, several of the implementations may require the use of startDate and endDate parameters. They may even be of a similar type (i.e., java.util.Date ). As another example, suppose the DBA for your application's database decides that there will be a timestamp column named L*_MODIFIED in all database tables. Every single entity bean in your application will support this field. Furthermore, the implementation of this field will have to remain consistent across all implementations of all entity beans in order to be of use. You'll start to notice that a bunch of regular Java objects will begin to pop up in your design. These are called utility classes . Utility classes are hard to define precisely because they include generalized data-holding classes, such as a DateRange class that encapsulates a start date and an end date, and nondata classes that contain infrastructure-related, library-like, and convenience methods. Examples of nondata classes include a StringUtils class containing String manipulation functionality, an ObjectUtils class containing various equality and comparison convenience methods, and a DatabaseUtils class containing primary key generation and database connection functionality. Data-holding classes can be more ambiguous. Determining whether they are utility classes or domain-specific types will depend on your particular application and design. For example, a Money class that combines an amount and a currency could be considered a generalized, cross-package class or a finance-specific class. The primary benefit of utility classes is reducing code duplication, which makes it easier to fix or improve your application without risking shotgun surgery.[*] Utility classes can also increase code readability.
You will discover candidate utility classes as you implement your design. The biggest sign that you might need a utility class is code duplication. If your code performs the same or very similar logic multiple times, or if two or more classes always accompany each other in methods or method signatures, you have a possible utility class (more correctly, a possible utility method or a possible utility class). Here's a method that might belong in a utility class: public static boolean isEmpty (String str) { return ((str == null) || (str.trim( ).equals(""))); } The isEmpty method is very simple, but implementing it in a utility class is worthwhile if you check for null or empty strings often enoughfor instance, when validating method arguments. I would put this method in a StringUtils class. Here's an example of a data-holding utility class; suppose you have a series of classes with method signatures that require both a currency and an amount parameter every time: public Ticket bookPassage(CreditCard card, double price, Integer currency) If you created a Money class, the modified method from the TRavelAgent session bean would look like this: public Ticket bookPassage(CreditCard card, Money amount) A DateRange utility class is a common requirement in handling reservations. For example, imagine that we had added startDate and endDate virtual persistence fields to the Cruise EJB: public Date getStartDate( ); public void setStartDate(Date start); public Date getEndDate( ); public void setEndDate(Date end); Because travel agents will want to search for cruises by these fields, we have added a listMatchingCruises method to the TRavelAgent EJB: public Collection listMatchingCruises(Date start, Date end) throws RemoteException; After a DateRange class is created, this method changes to: public Collection listMatchingCruises(DateRange range) throws RemoteException; An additional benefit of reduced duplication is that it makes the interface more coherent; it's easier to understand a method signature with a date range than one with separate parameters for the start and end dates. Likewise, it's easier to understand a Money parameter than separate price and currency parameters. Everyone who touches the revised bookPassage and listMatchingCruises methodstheir developers, the developers of any client code, or some college intern tasked with maintaining the code a year or two down the linewill have a more intuitive grasp of what those methods expect. Unfortunately, knowing when to implement this type of refactoring comes with experience. Fortunately, there is an excellent book on refactoring: Martin Fowler's Refactoring: Improving the Design of Existing Code (Addison-Wesley). Take a look for other ways to identify candidates for utility classes (and for other ways to refactor your code). |
- Comment







]
]