In the article "Creating Web Applications with the Eclipse WTP" (http://jdj.sys-con.com/read/152270.htm ), we created a Web application using Eclipse Web Tools Project, the Tomcat application server, and the MySQL database server. That application (DBTest) was good, however, it had some limitations:
Fortunately, two interesting solutions can address these problems. The first problem can be addressed using the Open Source Struts framework, which separates an application's Model, View, and Controller by mappings the actions of the model to view components (such as JSPs) in a simple configuration file.
The second problem can be addressed by using one of the frameworks providing persistence between the Java world and the relational database world. Hibernate framework provides a powerful high-performance mapping engine between the objects and database tables. The following technology is used in this article:
Two corresponding database tables CUSTOMERS and ORDERS were created to represent the data that were held in these objects. We also created four database command classes that were responsible for performing the aforementioned use cases, and four servlets acting as controllers to gather input from the user, invoke these commands, and forward the response to the appropriate JSP. A special class CommandExecutor was responsible for handling the database connections using Tomcat connection pools.
Adding Struts Support
Import the DBTest.WAR file (http://java.sys-con.com/read/152270.htm) into your Eclipse workspace using the File-Import option and selecting the WAR file item to import. That will work perfectly if the DBTest project isn't already in your workspace. If DBTest project is already in your workspace, just copy it to preserve the existing project by right-clicking on the project in the Navigator view and selecting Copy and then Paste when you'll be prompted for the new project name (we'll select DBTestStruts as the new name), so that the existing project isn't clobbered. Now to add Struts support, we have to copy the following files into the WEB-INF\lib folder: struts.jar, commons-lang.jar, commons-collections.jar, commons-beanutils.jar, commons-validator.jar, commons-logging.jar, commons-digester.jar, commons-fileupload.jar.
All these files can be downloaded from the Struts Web site and contain the Struts framework along with corresponding Apache Commons packages necessary for handling features such as internationalization, collection operations, utilities, validation, logging, digester, and file upload operations. They are all supporting parts for Struts. We won't use all of these features here, but Struts relies on many of them (e.g., the digester part is heavily used when parsing a Struts configuration file), and they may become handy later when services such as logging and file uploading are required.
So add the following files to the WEB-INF folder: struts-config.xml, struts-bean.tld, struts-html.tld, struts-logic.tld, struts-nested.tld, struts-template.tld, struts-tiles.tld.
The most important of these files is struts-config.xml, which is a main configuration file for the Struts framework. It contains definitions of all the action mappings, data sources, plug-ins, etc. See the sample one in Listing 1.
TLD files are Struts tag library definition files that can be used inside of JSPs to do various useful operations such as HTML rendering, logic handling, or Tiles support. They can be obtained from the Struts 1.1 distribution.
The next thing we need to do is modify our Web Deployment Descriptor (web.xml) to specify the location of the Struts configuration servlet and corresponding parameters. The code snippet from Listing 2 should be added to web.xml.
Tags from Listing 2 define the location of the Action Servlet, which is the Struts primary controller responsible for handling the lifecycle of the actions and mapping them to forwards, which are objects that are returned by an action and have two fields: a name and a path (typically the URL of a JSP file). The location of the struts-config.xml file is specified here, as well as the parameters for debugging and validation. The servlet is loaded at startup and the order of its load is 1 (first servlet to be loaded). The servlet gets invoked whenever a *.do pattern is detected in the invoking URL.
Now we have to convert our existing servlet classes into action classes and define appropriate mappings for them in struts-config.xml. To simplify this process, we'll provide an abstract superclass for all of our actions (see Listing 3) (Listings 3-15 and additional source code can be downloaded from the online version of this article.)
In this class, we provide implementation of the "execute" method that's invoked by default on the action by the Struts 1.1 framework. It handles the logic in its performAction() method and then forwards to either success or failure processors depending on whether an exception has been thrown or not. Correspondingly, mappings for both success and failure will have to be defined in the Struts configuration file (struts-config.xml) for every action.
Creating concrete actions is easy. We can use the Eclipse wizard to create action classes. Make sure that AbstractAction is selected as a superclass and the "Inherited abstract methods" box is checked (see Figure 2).
The class CreateCustomerAction with performAction() method will be automatically generated. Copy the contents of the CreateCustomerServlet doGet() method (see previous article at http://java.sys-con.com/read/152270.htm -) and paste it into performAction() with the modifications shown in Listing 4.
As one can see, the only difference between non-Struts code and Struts code is that instead of the following code:
RequestDispatcher rd = getServletContext().getRequestDispatcher("/customer_created.jsp");
rd.forward(request, response);
the following (simpler one) is used:
return mapping.findForward("customer_created");
We don't have to hard-code the name of the JSP inside our code any more. Instead, we use the "customer_created" reference, which will be resolved in the struts configuration file. Inside the <action-mappings> tags, we'll add the fragment in Listing 5.
In this example, /CreateCustomer would be the URI to invoke the action. Two forwards are defined - "customer_created" - which, incidentally, points to "customer_created.jsp" and "failure," which points to "failure.jsp" where errors can be displayed. It's useful to have a common error page for the application, and we'll create one right now (see Listing 6).
In this file, we use the Struts HTML tag library to display the errors captured.
In a similar manner, we'll convert other servlets into Struts actions. Don't forget to change the URLs in the index.html file and other JSP files and add a suffix ".do" to the action invocations. Changing references from DBTest to DBTestStruts is also necessary to deploy the new application in co-existence with the old one in the Tomcat server. Also change the display name in web.xml from DBTest to DBTestStruts.
Remove the old servlet definitions from the DBTest application source code and Web Deployment Descriptor - all we need there is just the definition of our Actions and the Action servlet. To delete the "servlet" package, simply right-click on the "servlet" package and select "Delete," then answer, "Yes," when prompted for confirmation.
To deploy the new application to Tomcat open its console http://localhost:8080/manager/html and deploy a new WAR file. Make sure that DBTest.xml is copied into DBTestStruts.xml and all the references to DBTest are changed to DBTestStruts in it.
Another issue, however, is that in the original solution's SQL was hard-coded directly inside the command classes. This will be addressed in the next section by the popular Hibernate framework, which supports persistence between Java and relational databases.
Adding Hibernate Support
The Hibernate framework helps you in the following areas:
Now we have to configure Hibernate on the application level. Create the hibernate.cfg.xml configuration file under "Java Source" folder in Eclipse: this way when the application is deployed it will automatically go into the application class-path under WEB-INF\classes.
The file shown in Listing 7 contains references to the following:
The file in Listing 8 contains two class tags, where each of our domain classes Customer and Order is mapped to the corresponding database table, with each instance variable mapped to a database column. One attribute worth mentioning is "lazy" - we have explicitly set to it to false, the reason being that when lazy is true (default), reading from the database is only done whenever a particular method is accessed. For example, it'll happen only when a getFirstName() is called, rather than pre-reading the whole set of customers right away - when the SQL query is issued. This may sometimes be beneficial when you read a large set of data and want to defer expensive database operations. In our example, we're only reading a small set of customers and don't want additional performance or database access issues to occur later, such as if the database session is closed at that later time, we'll get an exception from Hibernate if we still try to invoke the "lazy" method.
The Hibernate configuration is complete, and we have to modify our CommandExecutor class a bit to use the framework and remove hard-coded SQL code. This class has been used as a singleton for storing data sources and getting database connections. First of all, we'll be adding the instance variable to store the Hibernate session factory. The Hibernate session factory is similar to a data source, except instead of getting database connections from it, we'll be getting Hibernate database sessions. The instance variable will look simply as follows:
private SessionFactory sessionFactory = null;
Next, we want to create an access method for this instance variable (See Listing 9). This will maintain the object state encapsulated, and will also allow using techniques of the lazy initialization (access the data only when needed):
In this method, we initialize a session factory for the first time. Hibernate's Configuration object is used to read the configuration file from the classpath and initialize the framework accordingly.
We've used the executeDatabaseCommand() method before we started using Hibernate to perform our database operations: using the DatabaseCommand interface that required the executeDatabaseOperation() method. Since we now want to use Hibernate, we're going to introduce another method to the DatabaseCommand interface and another one to the CommandExecutor singleton object - this method will execute all of our database operations using Hibernate framework (See Listing 10).
This looks very like the executeDatabaseCommand() method, except that in this case, we're using a Hibernate Session object rather than a normal JDBC connection. The next step would be simply to add the following stub method to the DatabaseCommand interface:
public Object executeHibernateOperation(Session session) throws SQLException;
Now that we've added this new method to the interface, all the classes implementing this interface will be marked with red bullets in the Eclipse workbench, because the classes implementing the interface have to implement all the methods that it requires. We have four classes implementing the database command interface:
public class CreateCustomer implements DatabaseCommand
public class CreateOrder implements DatabaseCommand
public class ListCustomers implements DatabaseCommand
public class ListCustomerOrders implements DatabaseCommand
Hence we have to add an executeHibernateOperation() implementation method to each of them. Let's take a look at the CreateCustomer class first. Its executeDatabaseOperation() method is shown in Listing 11.
This method is fairly long and coding one requires a developer to know JDBC: how to create and execute prepared statements. Also, if one were to change the database from MySQL to some other one, it might require that the developer re-write the SQL because it may differ from one database to another. With Hibernate, you just change the SQL dialect in the hibernate.cfg.xml configuration file. Our corresponding executeHibernateOperation() method is in Listing 12.
In Listing 12 we tell the session object to save our class in the database. No SQL, no JDBC knowledge, no hard-coding of column and table names. If we have to change the table or column name, we don't have to go through possibly multiple lines of code in the application. Hibernate knows how to save the object, whether this object already exists in the database or not (to do an INSERT or UPDATE operation - it performs a check in either the optimistic way (tries to do an UPDATE, and if that fails, INSERT) or pessimistic way (does a SELECT to see if the row exists, if yes, then does UPDATE, otherwise INSERT). We flush the session after the execution of the command to make sure that all the database commands are executed right away without anything left over in the framework's buffer. In a similar fashion we perform the operation for the CreateOrder class.
First, the two operations that we have dealt with (in the JDBC version), CreateCustomer and CreateOrder, are database insert operations. However, one also has to handle database queries to make the application work. For that, we have ListCustomers and ListCustomerOrders commands. Let's look at how we get the customer list.
Listing 13 contains a bunch of JDBC calls. First, create an SQL statement, execute it using hard-coded column and table names, and then go through the ResultSet of the database query and construct a Customer domain object explicitly from each row read from the table, where we also have to remember the column order or names. All those operations are error prone and may become hard to maintain whenever changes to the database tables are required. This is where Hibernate comes to the rescue. It introduces a whole new language called the Hibernate Query Language (HQL), where one doesn't need to query database tables, but rather objects. Our executeHibernateOperation() method will look as in Listing 14.
Once again this method looks pretty similar to the old one, however, there are some significant differences. Here we are using a bunch of Hibernate objects. The first one is a Query class. It lets one create and execute database queries using either HQL (through the createQuery method) or regular SQL (through the createSQLQuery method). Let's look at the HQL we are using here:
from customer in class domain.Customer
Basically we're selecting all the customers identified by the variable customer from the domain.Customer class. Obtaining the iterator of the query lets us put "customers" in any collection. In our case it's ArrayList<Customer>.
A very similar method can be written for the ListCustomerOrders class, but HQL is a tiny bit more complex as you can see in Listing 15.
In this case we use the where-clause in our query. Note that in this where-clause we can use the instance variable of the Order class (custId) to query by. The syntax is similar to Java's dot notation.
Finally we have to update our Struts action classes to invoke the executeHibernateOperation() method instead of the executeDatabaseOperationMethod(). This can be done easily using Eclipse editors.
Exporting an Eclipse Project into WAR
Using WTP
tools we can easily export our project into the WAR file to be deployed
under Tomcat. Just select the DBTestStruts Web project, then select
Export from the File menu. When prompted, select the WAR file, specify
the file name, and you'll have the WAR file ready to be deployed in
Tomcat.
Conclusion
The goal of this article was to how you
how to integrate Struts and Hibernate support into a simple Web
application developed using Eclipse and WTP tooling. These frameworks
help to improve application maintainability and code reusability, as
well as code clarity.