Persistence Layer


The persistence layer is a very important part of your application design. This is where you define how your data is going to be stored, potentially for years to come. This usually involves a relational database as well as some technology to ease the mapping between the relational model and your Java classes.

Data Model

We will begin the exploration by looking at the data model. You have already seen parts of this model in Chapter 5 when we explored the JDBC Framework. Now we will look at the entire data model in full detail. You can refer to the Entity/Relationship model shown in Figure 15-2.

image from book
Figure 15-2

It's important to note that this is an existing data model that we have inherited from the previous book Expert One-on-One J2EE Design and Development by Rod Johnson. This means that a lot of design decisions already have been made for us, which is not an uncommon situation.

There are a couple of different categories of tables in this data model. First we have the reference data that contains information about some fairly static entities. There are items added here, but they don't change frequently. Tables in this category are Shows, Genre, Performance, Price_Structure, Seating_Plan, Seat_Class, Price_Band, and Seat. The Seat_Class defines seating classes that a seat belongs to — an example is "AA," "Premium Reserve." The next table is the Seating_Plan table that lets us define different plans for different types of performances — an example is "Standard Opera Seating." The Price_Structure table adds similar flexibility for the pricing information — an example is "Standard Opera Pricing." The last table in this category is the Price_Band, which links the Price_Structure and Seat_Class with a specific price.

Next, we have some tables that contain information relating to the relationships between these reference tables. Tables in this category are Seat_Status and Seat_Plan_Seat. The Seat_Status table will allow us to query for available seats — seats that don't belong to any booking. The price_band reference in this table is not necessary but it provides a convenient shortcut to the pricing information. Then there is a Registered_User table to keep track of users registering on the site. Finally we have some tables that contain reservation data such as Booking and Purchase.

Domain Object Model

Now, let's look at the object model shown in Figure 15-3. This is the model that we will map the data model to. Just like the data model, this model is also mostly inherited from the previous implementation of the sample application.

For the most part, this object model is very close to the data model and the mapping is straightforward between the database tables and the domain classes. The exception here is the relationship between a Seat, a SeatingPlan, and a SeatClass. This is modeled in the database as an Association Table Mapping (see page 248 in Patterns of Enterprise Application Architecture by Martin Fowler or the accompanying website www.martinfowler.com/eaaCatalog/associationTableMapping.html for more on this pattern). The consequence of this is that the table holding the associated keys does not contain any additional information beyond the relationships and we will not create a corresponding class in our domain model. We will simply refer to the table Seat_Class_Seat in our queries, but the domain objects themselves do not have explicit references to each other. If this table contained some additional data elements, then we would add a class to map this data to. With our solution, we need to use an SQL query rather than an HQL query, to query this association. However, Hibernate provides good support for using SQL queries, and hence there is no reason at this point to add an additional mapped object, which would add more complexity overall.

There is one additional association table mapping between the Seat and Performance tables. This table contains additional information regarding the seat's availability for a certain performance and we have included a SeatStatus class that we map this table to. There are, of course, two additional classes that we have not mentioned yet. They are RegisteredUser and Payment that are referenced by the Booking class. We don't show the attributes in the UML diagram, but they are all the columns of the corresponding table mapped to their corresponding Java data type.

image from book
Figure 15-3

Object/Relational Mapping

We use Hibernate for our Object/Relational mapping. Choosing an O/R mapping solution can be a difficult task especially considering the recent introduction of the early draft of the new EJB 3.0 specification. Spring supports all the popular O/R Mapping solutions and it will also support the new EJB 3.0 persistence specification when it is finalized. Hibernate was the first O/R Mapper to be supported by Spring and it still remains the most popular one, so it was the most logical choice for our sample application. In addition to this, it is an excellent product and we knew it could handle our requirements.

In this section, we will take a look at some of the more interesting parts of the mapping definitions. The class/table mapping file is named ticket-mapping.hbm.xml and it is located in the /src/orm folder.

The mapping definitions start out by defining the mapping and relationships between the Genre, Show, Performance, and Seat domain objects. A Genre has a number of Shows associated. Each Show has a number of Performances and each Performance has a reference back to the Show and it also has a reference to the PriceStructure that it is associated with. The Seat class has a reference to the Seat to the left and right of it.

Hibernate's XML mapping metadata is quite intuitive, so you should find the following listing easy enough to understand even if you are not familiar with Hibernate. If necessary, please refer to the Hibernate reference manual or a book on Hibernate for an explanation of the meaning of the required elements.

<class name="Genre" table="Genre">   <cache usage="read-only"/>     <id name="id" column="id" type="long">       <generator />     </id>     <property name="name" column="name" type="string"/>   <set name="shows" lazy="true" inverse="true" order-by="name">     <key column="Genre_id"/>     <one-to-many />   </set> </class>     <class name="Show" table="Shows">   <cache usage="read-only"/>     <id name="id" column="id" type="long">       <generator />     </id>     <property name="name" column="name" type="string"/>   <set name="performances" inverse="true" order-by="date_and_time">     <key column="Show_id"/>     <one-to-many />   </set> </class>     <class name="Performance" table="Performance">   <cache usage="read-only"/>     <id name="id" column="id" type="long">       <generator />     </id>     <property name="dateAndTime" column="date_and_time" type="timestamp"/>   <many-to-one name="show"            column="Show_id"/>   <many-to-one name="priceStructure"            column="Price_Structure_id"/> </class>     <class name="Seat" table="Seat">   <cache usage="read-only"/>     <id name="id" column="id" type="long">       <generator />     </id>     <property name="name" column="name" type="string"/>     <property name="rowOrBox" column="row_or_box" type="int"/>     <property name="rowPosition" column="row_pos" type="int"/>   <many-to-one name="leftSide"         cascade="none"         outer-join="false"         foreign-key="fk_left_Seat">     <column name="left_Seat_id"         not-null="false"/>   </many-to-one>   <many-to-one name="rightSide"         cascade="none"         outer-join="false"         foreign-key="fk_right_Seat">     <column name="right_Seat_id"         not-null="false"/>   </many-to-one> </class>

These definitions are sufficient for retrieving the populated Genre objects including references to Show, which in turn references all the Performance objects associated with a specific Show. The HQL query we use for retrieving all genres that do have some performances is as follows:

select g from Genre g  join fetch g.shows s  group by g  having size(g.shows) > 0  order by g.name

Again, HQL should be fairly intuitive, especially if you already know SQL (a necessity for enterprise Java developers).

We are using join fetch to ensure that the lazy-loaded collection of shows in the Genre class is populated. A Hibernate "fetch join" allows lazy relationships (including collections) to be retrieved along with their parent objects (Genre objects in this case) in a single query. It is particularly useful in cases like this, where we want to eagerly load a relationship that is marked as lazy in the static metadata.

This HQL query and a couple of additional ones are stored in the mapping file ticket-queries.hbm.xml as named queries. The use of "named" queries is a good practice in most ORM tools, including TopLink and Hibernate, when queries are complex. For example, it allows for query tuning in a well-defined location, without changing Java code. Named queries can also be reused in multiple DAO implementations, which guarantees consistency.

<query name="currentGenres">   <![CDATA[     select g from Genre g      join fetch g.shows s      group by g      having size(g.shows) > 0      order by g.name   ]]> </query>     <query name="availabilityForPerformance">   <![CDATA[     select new       org.springframework.samples.ticket.dao.hibernate.AvailabilityDetail(         pb, p, count(ss.seat), count(ss.booking))     from Performance p, PriceBand pb, SeatStatus ss      where ss.performance = p     and ss.priceBand = pb       and p.show = :show     group by p.id, pb.id     ]]> </query>     <query name="costOfSeats">   <![CDATA[     select pb.price from PriceBand pb, SeatStatus ss      where pb.id = ss.priceBand      and ss.performance = :performanceId       and ss.seat in ( :seatIds )    ]]> </query>     <query name="getSeatStatusForBooking">   <![CDATA[     from SeatStatus      where Performance_id = :perfId      and Seat_id in ( :ids )    ]]> </query> 

The next item in the mapping file is a query that will return all available seats for a specific performance. This query relies on some information in the Seat_Plan_Seat table. Because this table is not mapped to an object, we have to use a named SQL query here. The mapping to the Seat object is straightforward; it's just the selection of the available seats that is a little tricky. We include only those seats that don't have a reference to a booking instance. We also include a "for update" clause, which will lock the selected rows in the database. This will prevent other users from booking these seats until our booking transaction has completed.

<sql-query name="availableSeatsToBook">   <![CDATA[     select {seat.*} from Seat seat     where exists       (select * from Seat_Status ss         where ss.Seat_id = seat.id         and ss.Performance_id = :perfId         and ss.Booking_id is null)     and exists       (select * from Seat_Plan_Seat sps     where sps.Seat_id = seat.id     and sps.Seat_Class_id = :seatClassId)     for update   ]]>   <return alias="seat" /> </sql-query>

Note the use of the seat.* syntax to automatically select all mapped columns from the seat table. This is a Hibernate convenience that greatly simplifies the SQL query we need to write and ensures that our query will not break if we change the mappings for the Seat class. The only downside to using an SQL query here is that it depends on specific table and column names, which are otherwise concealed from application code using Hibernate metadata. However, this is usually an acceptable small price to pay. In cases where queries are highly complex, there may be an important advantage in using SQL queries, as it enables DBAs to tune them.

As we can see, Hibernate — like other leading ORM tools — can actually help us in some cases if we need to work at an SQL level.

DAO Implementation

The data access logic is not overly complex when it comes to the Java code. Most of the complexity is contained in the queries that are part of the Hibernate mapping file. We are using a Data Access Object (DAO) to separate the data access–specific code from the rest of the application. All the required methods are specified in the BoxOfficeDao interface and any implementation class must implement this interface. The implementation for Hibernate, which we are using here, is named HibernateBoxOfficeDao.

This DAO implementation extends the HibernateDaoSupport class and this allows us to use Spring's Hibernate support to execute all of our Hibernate queries. This amounts to just a few lines of code for each method. The only exceptions are the reserveSeats and getAvailabilityForPerformance methods that do involve more complex processing or multiple calls to the database to retrieve and update the relevant objects.

A typical call to execute a named query looks like this:

public Seat[] getAvailableSeatsToBook(Performance performance, SeatClass seatClass)                             throws DataAccessException {   List seats =      getHibernateTemplate().findByNamedQueryAndNamedParam(       "availableSeatsToBook",        new String[] {"perfId", "seatClassId"},        new Object[] {new Long(performance.getId()), new Long(seatClass.getId())});   return (Seat[])seats.toArray(new Seat[seats.size()]);     }

The parameters for this query — the Performance Id and the Seat Id — are passed in as named parameters in an array.

Note the code that we do not need to write. (This will be particularly obvious if you have used Hibernate without Spring in the past.) Spring's Hibernate integration is taking care of important issues such as obtaining the correct Hibernate Session to use, opening and closing it as necessary, and translating Hibernate exceptions into Spring's informative, persistence API-agnostic DataAccessException hierarchy. Code using this DAO via its interface is not tied to Hibernate. We could implement this DAO interface using another persistence API such as TopLink or JDO (or JSR-220 persistence, when it is finalized).

The code that we do need to write is not obscured by such mundane but important issues: It is focused on providing the necessary data to run the query and return strongly typed results.

The getAvailabilityForPerformance method is a bit more complicated and it starts out by getting the available performances together with availability and price information in the form of an AvailabilityDetail helper class. The next step is to populate an extension to the Performance class named PerformanceWithAvailability with these availability data. These two steps are highlighted in the program listing.

public PerformanceWithAvailability[] getAvailabilityForPerformance(Show show) {   List availability =      getHibernateTemplate().findByNamedQueryAndNamedParam(       "availabilityForPerformance",        "show",        show);   List performanceWithAvailability = new LinkedList();   long lastPerformanceId = -1;   PerformanceWithAvailability perf = null;    for (int i = 0; i < availability.size(); i++) {     AvailabilityDetail curr = (AvailabilityDetail)availability.get(i);     if (lastPerformanceId != curr.getPerformance().getId()) {       perf = new PerformanceWithAvailability(           curr.getPerformance().getId(),           curr.getPerformance().getDateAndTime(),           curr.getPerformance().getPriceStructure(),           curr.getPerformance().getShow());       performanceWithAvailability.add(perf);       lastPerformanceId = curr.getPerformance().getId();     }     PriceBandWithAvailability pb = new PriceBandWithAvailability(         curr.getPriceBand().getId(),         curr.getPriceBand().getPrice(),         curr.getPriceBand().getPriceStructure(),         curr.getPriceBand().getSeatClass(),         curr.getAvailableSeatCount());     perf.add(pb);   }       return (PerformanceWithAvailability[])performanceWithAvailability.toArray(       new PerformanceWithAvailability[performanceWithAvailability.size()]); }

This DAO class needs a reference to the Hibernate SessionFactory that we have defined in our configuration file. Naturally, this dependency is resolved in our sample application using Spring's Dependency Injection features. However, the DAO implementation is not tied to or aware of Spring IoC, and could be used programmatically without the Spring container. The content of the necessary configuration file is explored in the next section.

Data Access Context

The data access context is defined in a configuration file named dataAccessContext.xml. This is a regular Spring bean definition file with the beans needed for the DAO along with a transaction manager to provide transaction management for the data access technology that we are using. In this case it is, of course, a HibernateTransactionManager, but if we had used JDO instead, then we would just swap out the transaction manager for a JDOTransactionManager.

<bean    >   <property name="dataSource">     <ref local="dataSource"/>   </property>    <property name="sessionFactory">     <ref local="sessionFactory"/>   </property>  </bean>

The data source configuration details are provided in a separate Properties file to allow an administrator to easily modify these setting at deployment time. If they are in a separate file, the administrator does not have to worry about accidentally altering any other configurations. Figure 15-4 shows the basic structure of this configuration.

image from book
Figure 15-4

The graphs in Figures 15-4, 15-5, and 15-7 showing the structure of the configuration files were prepared using SpringViz, a utility for visualizing Spring's configuration files developed by Mike Thomas. It can be downloaded from www.samoht.com/wiki/wiki.pl?SpringViz.



Professional Java Development with the Spring Framework
Professional Java Development with the Spring Framework
ISBN: 0764574833
EAN: 2147483647
Year: 2003
Pages: 188

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net