Accessing Databases from Web Applications
Data that is shared between web components and is persistent between invocations of a web application is usually maintained in a database. Web applications use the Java Persistence API (see Chapter 24) to access relational databases.
The Java Persistence API provides a facility for managing the object/relational mapping (ORM) of Java objects to persistent data (stored in a database). A Java object that maps to a database table is called an Entity class. It is a regular JavaBeans component (also known as a POJO, or plain, old Java object) with properties that map to columns in the database table. The Duke's Bookstore application has one Entity class, called
Bookthat maps toWEB_BOOKSTORE_BOOKS.To manage the interaction of entities with the Java Persistence facility, an application uses the
EntityManagerinterface. This interface provides methods that perform common database functions, such as querying and updating the database. TheBookDBAOclass of the Duke's Bookstore application uses the entity manager to query the database for the book data and to update the inventory of books that are sold.The set of entities that can be managed by an entity manager are defined in a persistence unit. It oversees all persistence operations in the application. The persistence unit is configured by a descriptor file called
persistence.xml. This file also defines the data source, what type of transactions the application uses, along with other information. For the Duke's Bookstore application, the persistence.xml file and theBookclass are packaged into a separate JAR file and added to the application's WAR file.As in JDBC technology, a
DataSourceobject has a set of properties that identify and describe the real world data source that it represents. These properties include information such as the location of the database server, the name of the database, the network protocol to use to communicate with the server, and so on.An application that uses the Java Persistence API does not need to explicitly create a connection to the data source, as it would when using JDBC technology exclusively. Still, the DataSource object must be created in the Application Server.
To maintain the catalog of books, the Duke's Bookstore examples described in Chapters 3 through 14 use the Derby evaluation database included with the Application Server.
This section describes the following:
Populating the Example Database
To populate the database for the Duke's Bookstore examples, follow these steps:
- In a terminal window, go to
<INSTALL>/javaeetutorial5/examples/web/bookstore/.- Start the Derby database server. For instructions, see Starting and Stopping the Java DB Database Server (page 30).
- Run
asantcreate-tables. This task runs a Derby commander tool command to read the filetutorial.sqland execute the SQL commands contained in the file.- At the end of the processing, you should see the following output:
When you are running
create-tables, don't worry if you see a message that an SQL statement failed. This usually happens the first time you run the command because it always tries to delete an existing database table first before it creates a new one. The first time through, there is no table yet, of course.Creating a Data Source in the Application Server
Data sources in the Application Server implement connection pooling. To define the Duke's Bookstore data source, you use the installed Derby connection pool named
DerbyPool.You create the data source using the Application Server Admin Console, following this procedure:
Defining the Persistence Unit
As described in Accessing Databases from Web Applications, a persistence unit is defined by a
persistence.xmlfile, which is packaged with the application WAR file. This file includes the following:
- A
persistenceelement that identifies the schema that the descriptor validates against and includes a persistence-unit element.- A
persistence-unitelement that identifies the name of a persistence unit and the transaction type.- An optional
descriptionelement- A
jta-data-sourceelement that specifies the global JNDI name of the JTA data source.The
jta-data-sourceelement indicates that the transactions in which the entity manager takes part are JTA transactions, meaning that transactions are managed by the container. Alternatively, you can use resource-local transactions, which are transactions controlled by the application itself. In general, web application developers will use JTA transactions so that they don't need to manually manage the life cyle of theEntityManagerinstance.A resource-local entity manager can not participate in global transaction. Secondly web container won't roll back pending transaction left behind by poorly written applications.
Creating an Entity Class
As explained in Accessing Databases from Web Applications, an Entity class is a JavaBeans component that represents a table in the database. In the case of the Duke's Bookstore application, there is only one database table and therefore only one Entity class: the
Bookclass.The
Bookclass contains properties for accessing each piece of data for a particular book, such as the book's title and author. To make in anEntityclass that is accessible to an entity manager, you need to do the following:The following code shows part of the
Bookclass:import java.io.Serializable; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name="WEB_BOOKSTORE_BOOKS") public class Book implements Serializable { private String bookId; private String title; public Book() { } public Book(String bookId, String title, ...) { this.bookId = bookId; this.title = title; ... } @Id public String getBookId() { return this.bookId; } public String getTitle() { return this.title; } ... public void setBookId(String id) { this.bookId=id; } public void setTitle(String title) { this.title=title; } ... }Obtaining Access to an Entity Manager
The
BookDBAOobject of the Duke's Bookstore application includes methods for getting the book data from the database and updating the inventory in the database when books are sold. In order to perform database queries, theBookDBAOobject needs to obtain anEntityManagerinstance.The Java Persistence API allows developers to use annotations to identify a resource so that the container can transparently inject it into an object. You can give an object access to an
EntityManagerinstance by using the@PersistenceUnitannotation to inject anEntityManagerFactory, from which you can obtain anEntityManagerinstance.Unfortunately for the web application developer, resource injection using annotations can only be used with classes that are managed by a Java EE compliant container. Because the web container does not manage JavaBeans components, you cannot inject resources into them. One exception is a JavaServer Faces managed bean. These beans are managed by the container and therefore support resource injection. This is only helpful if your application is a JavaServer Faces application.
You can still use resource injection in a web application that is not a JavaServer Faces application if you can do it in an object that is managed by the container. These objects include servlets and
ServletContextListenerobjects. These objects can then give the application's beans access to the resources.In the case of Duke's Bookstore, the
ContextListenerobject creates theBookDBAOobject and puts it into application scope. In the process, it passes to theBookDBAOobject theEntityManagerFactoryobject that was injected intoContextListener:public final class ContextListener implements SerlvetContextListener { ... @PersistenceUnit private EntityManagerFactory emf; public void contextInitialized(ServletContexEvent event) { context = event.getServletContext(); ... try { BookDBAO bookDB = new BookDBAO(emf); context.setAttribute("bookDB", bookDB); } catch (Exception ex) { System.out.println( "Couldn't create bookstore database bean: " + ex.getMessage()); } } }The
BookDBAOobject can then obtain anEntityManagerfrom theEntityManagerFactorythat theContextListenerobject passes to it:private EntityManager em; public BookDBAO (EntityManagerFactory emf) throws Exception { em = emf.getEntityManager(); ... }The JavaServer Faces version of Duke's Bookstore gets access to the
EntityManagerinstance a little differently. Because managed beans allow resource injection, you can inject theEntityManagerFactoryinstance intoBookDBAO.In fact, you can bypass injecting
EntityManagerFactoryand instead inject theEntityManagerdirectly intoBookDBAO. This is because thread safety is not an issue with request-scoped beans. Conversely, developers need to be concerned with thread safety when working with servlets and listeners. Therefore, a servlet or listener needs to inject anEntityManagerFactoryinstance, which is thread-safe, whereas a persistence context is not thread-safe. The following code shows part of theBookDBAOobject included in the JavaServer Faces version of Duke's Bookstore:import javax.ejb.*; import javax.persistence.*; import javax.transaction.NotSupportedException; public class BookDBAO { @PersistenceContext(unitName="books") private static EntityManager em; ...As shown in the preceding code, an
EntityManagerinstance is injected into an object using the@PersistenceContextannotation. AnEntityManagerinstance is associated with a persistence context, which is a set of entity instances that the entity manager is tasked with managing.The annotation must specify the name of the persistence unit with which it is associated. This name must match a persistence unit defined in the application's
persistence.xmlfile.The next section explains how the
BookDBAOobject uses the entity manager instance to query the database.Accessing Data From the Database
After the
BookDBAOobject obtains anEntityManagerinstance, it can access data from the database. ThegetBooksmethod ofBookDBAOcalls thecreateQuerymethod of theEntityManagerinstance to retrieve a list of all books bybookId:public List getBooks() throws BooksNotFoundException { try { return em.createQuery( "SELECT bd FROM Book bd ORDER BY bd.bookId"). getResultList(); } catch(Exception ex){ throw new BooksNotFoundException("Could not get books: " + ex.getMessage()); } }The
getBookmethod ofBookDBAOuses the find method of theEntityManagerinstance to search the database for a particular book and return the associatedBookinstance:public Book getBook(String bookId) throws BookNotFoundException { Book requestedBook = em.find(Book.class, bookId); if (requestedBook == null) { throw new BookNotFoundException("Couldn't find book: " + bookId); } return requestedBook; }The next section describes how Duke's Bookstore performs updates to the data.
Updating Data in the Database
In the Duke's Bookstore application, updates to the database involve deprecating the inventory count of a book when the user buys copies of the book. The
BookDBAOperforms this update in thebuyBooksandbuyBookmethods:public void buyBooks(ShoppingCart cart) throws OrderException{ Collection items = cart.getItems(); Iterator i = items.iterator(); try { while (i.hasNext()) { ShoppingCartItem sci = (ShoppingCartItem)i.next(); Book bd = (Book)sci.getItem(); String id = bd.getBookId(); int quantity = sci.getQuantity(); buyBook(id, quantity); } } catch (Exception ex) { throw new OrderException("Commit failed: " + ex.getMessage()); } } public void buyBook(String bookId, int quantity) throws OrderException { try { Book requestedBook = em.find(Book.class, bookId); if (requestedBook != null) { int inventory = requestedBook.getInventory(); if ((inventory - quantity) >= 0) { int newInventory = inventory - quantity; requestedBook.setInventory(newInventory); } else{ throw new OrderException("Not enough of " + bookId + " in stock to complete order."); } } } catch (Exception ex) { throw new OrderException("Couldn't purchase book: " + bookId + ex.getMessage()); } }In the
buyBookmethod, the find method of theEntityManagerinstance retrieves one of the books that is in the shopping cart. ThebuyBookmethod then updates the inventory on theBookobject.To ensure that the update is processed in its entirety, the call to
buyBooksis wrapped in a single transaction. In the JSP versions of Duke's Bookstore, theDispatcherservlet callsbuyBooksand therefore sets the transaction demarcations.In the following code, the
UserTransactionresource is injected into the Dispatcher servlet.UserTransactionis an interface to the underlying JTA transaction manager used to begin a new transaction and end a transaction. After getting theUserTransactionresource, the servlet calls to thebeginandcommitmethods ofUserTransactionto mark the boundaries of the transaction. The call to therollbackmethod ofUserTransactionundoes the effects of all statements in the transaction so as to protect the integrity of the data.@Resource UserTransaction utx; ... try { utx.begin(); bookDBAO.buyBooks(cart); utx.commit(); } catch (Exception ex) { try { utx.rollback(); } catch (Exception exe) { System.out.println("Rollback failed: "+exe.getMessage()); } ...