Designing and Building the Data Access Tier

In the last 3 years, we don't think we can honestly say that we have built an application for a customer that did not access some kind of data store—in most cases a relational database. The fact remains that data is an important part of building applications, and being able to store, retrieve, and manipulate that data quickly and reliably is, perhaps, even more important.

In this section, we take a look at the ubiquitous Data Access Tier, paying particular attention to the design pattern known as the Data Access Object (DAO). As with the DOM, the DAO pattern has had a huge amount of text written about it—usually related to the implementation of individual DAOs themselves. Because we covered the implementation of the SpringBlog DAOs in Chapters 10, we won't spend too much time looking at that here. Instead, we look at some of the practical considerations that go into designing a good set of DAOs.

Why Have a Data Access Tier?

At our company, we take on quite a few developers on a trainee scheme. When we get these developers in and show them our applications, the first thing that many of them ask is "Why do you bother with a Data Access Tier?" For most Java developers, the answer to this question is plainly, and often painfully, obvious, borne out of many late nights spent debugging spaghetti JSPs with embedded data access code. Aside from it being good design practice to encapsulate all data access logic behind a well-defined set of interfaces, building a Data Access Tier makes applications, and hence developers' lives, simpler. A good DAO provides an isolated area, away from the business logic, where the developer can concentrate on mapping the quite often different DOM to the database schema. Putting all your data access logic in one place reduces the need to spread, and often duplicate, the code across your application, an approach that inevitably leads to debugging and maintenance nightmares—not to mention programmers going prematurely gray.

Typically, it doesn't take that much effort to design a good Data Access Tier, and in many cases, the implementation time is much shorter than when you are spreading code throughout your application, because you are avoiding unnecessary duplication.

Practical Design Considerations

The task of designing and building a Data Access Tier is fairly simple; indeed, the most complex part is actually creating the data access code itself. However, you should bear in mind a few practical considerations when creating your Data Access Tier that lead you to build DAOs that are simpler to use and easier to extend.

Domain Objects or Data Transfer Objects?

As we mentioned earlier, one school of thought says that when you invoke your Data Access Tier, generally from the service objects in your Business Tier, then you should create Data Transfer Objects from your Domain Objects. Honestly, we cannot think of a reason you would want to do this; in our eyes, it seems that this practice has sprung up out of confusion on how certain J2EE patterns should be used. We have never used this technique in any of our applications, because we have never had a problem passing Domain Objects straight to our DAOs. If any of you know of a scenario where this approach is useful, then let us know, and we'll include it in the second edition.

More Interfaces

At the risk of sounding like a broken record, use interfaces. Define your DAOs in terms of inter- faces, not classes. When writing code in your service objects to work with your DAOs, code to the interfaces, not the implementation classes. Remember that when you are using Spring, it is a trivial job to pass an instance of the appropriate DAO implementation to your service tier, so using interfaces for your DAOs places very little additional coding burden on you.

Use the Base Interface Implementation Pattern

As we did with the BlogPosting interface earlier, it is often useful to define an abstract base class to the basis of the interface implementation. In this way different implementations of the same DAO can share common functionality, reducing the need for duplication. The difference between defining base classes for business interfaces and for DAO interfaces is that with DAO interfaces you often do not need to define them, because Spring has already taken care of this for you. For instance, consider the UML model in Figure 11-2, which shows a portion of the DAO structure in SpringBlog.

image from book
Figure 11-2: Using Spring base class for DAO implementations

Here you can see that we have defined the EntryDao interface, and for this, we have built an implementation using Hibernate—HibernateEntryDao. Rather than implement this from scratch, we have chosen to take advantage of the Hibernate support offered in Spring by extending the HibernateDaoSupport class. This class has predefined properties for Hibernate-specific dependencies such as the SessionFactory object, which allows for DI-based configuration of the DAO. All our HibernateEntryDao class needs to worry about is implementing the EntryDao interface. You can see more details on the full Hibernate DAO implementation for SpringBlog in Chapter 9.

Spring provides similar convenience classes for the other ORM frameworks it supports, including iBATIS (covered in

When using the JDBC support class, JdbcDaoSupport, you may find it necessary to introduce a further layer of abstract classes to factor out database-specific functionality. For instance, when we were building the JDBC DAO for SpringBlog, we found that we could implement nearly all the functionality using standard SQL constructs, but retrieval of newly added primary keys required us to use database-specific functionality. To get around this, we created an abstract implementation of each DAO with the core functionality implemented, and then we created an implementation of each DAO specific to the target database. Figure 11-3 shows a skeleton UML structure for this hierarchy.

image from book
Figure 11-3: Accounting for DBMS differences in JDBC DAOs

Here you can see that we have defined a class, JdbcAbstractEntryDao, that implements the EntryDao and extends the JdbcDaoSupport class. This class is abstract and, in addition to providing the base implementation of the EntryDao interface, it defines a single abstract method, getIdentitySql(). Listing 11-12 shows the parts of JdbcAbstractEntryDao in question, with all the implementation code removed for clarity.

Listing 11-12: Defining an Abstract Hook Method for DB-Specific Functionality

image from book
package;      import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; import java.util.List;      import javax.sql.DataSource;      import org.springframework.beans.factory.InitializingBean; import org.springframework.jdbc.core.SqlParameter; import; import org.springframework.jdbc.object.MappingSqlQuery; import org.springframework.jdbc.object.SqlUpdate;      import; import com.apress.prospring.domain.Entry;      public abstract class JdbcAbstractEntryDao extends JdbcDaoSupport implements         EntryDao, InitializingBean {              protected abstract String getIdentitySql(); }
image from book

Remember, JdbcAbstractEntryDao contains the vast majority of the implementation code. Whenever this implementation code needs to find out the last primary key that was auto- generated by the database, it calls the getIdentitySql() method and executes the returned command. In this way, we can provide implementations of this method that are specific to a particular database. This is exactly what is done by the JdbcMysqlEntryDao class, as shown in Listing 11-13.

Listing 11-13: Implementing DB-Specific Functionality

image from book
package;      public class JdbcMysqlEntryDao extends JdbcAbstractEntryDao {          protected String getIdentitySql () {         return "select LAST_INSERT_ID()";     } }
image from book

Note that we have not removed any code here; that is all there is to this class. Now if we want to move the EntryDao to another database system, all we need to do is create a new class, derived from JdbcAbstractEntryDao, and implement getIdentitySql() as appropriate.

DAO Granularity

When deciding how to structure your DAO interfaces, definitely avoid creating one DAO per Domain Object and one DAO per table. Sometimes, these structures appear naturally after thoughtful design, but don't assume that either one of these structures is necessarily the best.

A big problem we often see with projects is the "one DAO per table" problem. When you define a structure like this, you end up with DAOs representing join tables that serve no purpose other than to join two other tables in a many-to-many relationship. Plus, you often find that you have to pass a single Domain Object to lots of different DAOs to have the data persisted.

This is a classic example of letting the database drive the design of your DAO layer. This is something that, in practice, we have found to be a bad idea. The purpose of a DAO is to map Domain Objects to the database and vice versa. Because the bulk of your application is interacting with the Domain Objects, not the database, it makes sense to let the DOM drive database design. Let your DAOs hide the complexity of mapping the data in your Domain Objects to the database; that is their job. You are trying to avoid the situation where persisting a Domain Object requires you to interact with many different DAOs. Situations like this arise naturally, such as when a Domain Object has a reference to another Domain Object of a different type, and both have been modified, and thus need to be persisted. In this case, you can encapsulate that logic in your service layer, but you do not need to create this problem artificially.

So then you might wonder if you should let the DOM drive the design of the DAOs. Well, yes and no. Yes, in that the purpose of the DOM is to get DOM data into and out of the persistent data store so it makes sense to let the DOM act as the driver. No, in that blindly creating one DAO per Domain Object leads to a situation where the persistence of one logical unit of data leads to calls to many different DAOs. Consider our earlier example of the Order and OrderLine objects that are created from the Cart and CartItem objects. Because it is unlikely that you are going to want to save or retrieve OrderLine objects without doing the same to an Order object, it makes sense to encapsulate persistence logic for both Domain Objects in a single OrderDao, rather than create OrderDao and OrderLineDao.

Navigating Domain Object Relationships

A problem that we often find when talking to other developers is that they are unsure of when to navigate Domain Object relationships in their DAOs, especially when loading data from the database. Consider the situation in the SpringBlog application of the Entry and Attachment objects. As shown in Figure 11-1, a single Entry can contain many Attachment objects. So the question here is should you load the related Attachment objects when you are loading the Entry objects? Again, the answer is yes and no. We once worked on a project where the developer of the DAO objects had blindly loaded all related data, regardless of whether that data was to be used or not. This led to hundreds of needless database calls being made and hundreds of needless objects being created. There is nothing wrong with loading related data, but only do so when you need to.

In the case of the SpringBlog application, we have the EntryDao.getAll() method that returns a list of all blog entries. We are going to use this method to provide a simple list of entries, with no consideration for the attachments at all. In this case, loading the attachment data from the database and creating corresponding Attachment objects for every Entry is an unnecessary overhead. On the flipside, we also have the EntryDao.getById() method, which loads a single Entry object from the database. SpringBlog uses this method when displaying the full entry along with all comments and attachments. In this case, we do want to get the list of attachments for the entry, so we load the list from the database. Listing 11-14 shows a snippet (we have left a substantial amount of the code out here!) of code from the JDBC-based implementation of EntryDao, JdbcAbstractEntryDao.

Listing 11-14: Loading Related Data in a DAO

image from book
package;      import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; import java.util.List;      import javax.sql.DataSource;      import org.springframework.beans.factory.InitializingBean; import org.springframework.jdbc.core.SqlParameter; import; import org.springframework.jdbc.object.MappingSqlQuery; import org.springframework.jdbc.object.SqlUpdate;      import; import; import; import com.apress.prospring.domain.Entry;          private CommentDao commentDao;          private AttachmentDao attachmentDao;          private SelectById selectById;          private SelectAll selectAll;          public void setCommentDao(CommentDao commentDao) {         this.commentDao = commentDao;     }          public void setAttachmentDao(AttachmentDao attachmentDao) {         this.attachmentDao = attachmentDao;     }          protected void initDao() throws Exception {         super.initDao();              selectById = new SelectById(getDataSource());     }          public Entry getById(int entryId) {         Entry e = (Entry) selectById.findObject(entryId);         e.setComments(commentDao.getByEntry(e.getEntryId()));         e.setAttachments(attachmentDao.getByEntry(e.getEntryId()));         return e;     }         public List getAll() {         return selectAll.execute();     } } 
image from book

As you can see, the getAll() method simply loads the List of Entry objects and returns it to the caller. However, in the getById() method, the Entry object is loaded and then populated with the List of Attachment objects using an implementation of AttachmentDao. By being selective about the data we load, and by not loading related data needlessly, we can increase the performance of our application without affecting the functionality.

The code in Listing 11-14 does raise an interesting question though: how should you go about loading related data? Well, essentially, there are two options. The first is to embed the code inside the DAO of the parent Domain Object; the second is to have the DAO of the parent Domain Object use the DAO of the child Domain Object. Both of these approaches are valid, but they are aimed at different scenarios. We tend to use the first approach when the child Domain Object is entirely dependent on the parent Domain Object. The Order and OrderLine structure is an example of this kind of relationship. In most systems we have worked on, it doesn't really make sense to work with OrderLines outside the context of a containing Order. Listing 11-15 shows a snippet of code from the jPetStore sample application supplied with Spring.

Listing 11-15: Loading Child Data in Parent DAO

image from book
package org.springframework.samples.jpetstore.dao.ibatis;      import java.util.List;      import org.springframework.dao.DataAccessException;aff import; import org.springframework.samples.jpetstore.dao.OrderDao; import org.springframework.samples.jpetstore.domain.LineItem; import org.springframework.samples.jpetstore.domain.Order;      public class SqlMapOrderDao extends SqlMapDaoSupport implements OrderDao {          public List getOrdersByUsername(String username) throws DataAccessException {     return getSqlMapTemplate().executeQueryForList(                                        "getOrdersByUsername", username);   }        public Order getOrder(int orderId) throws DataAccessException {     Object parameterObject = new Integer(orderId);     Order order = (Order) getSqlMapTemplate().executeQueryForObject(                          "getOrder", parameterObject);         if (order != null) {         order.setLineItems(getSqlMapTemplate().executeQueryForList(                         "getLineItemsByOrderId", new Integer(order.getOrderId())));         }     return order;   }       /* omitted for brevity */      } 
image from book

In this code, you can see that the getOrdersByUsername() method loads just the Order objects, whereas the getOrder() method loads the Order object and its associated OrderLines. The logic for loading OrderLine objects is contained in this DAO, not in a DAO specific to the OrderLine object; indeed, no such DAO exists. This implementation approach works well when the child Domain Object is completely dependent on the parent, but when this is not the case, the second approach—having the DAO of the parent Domain Object use the DAO of the child Domain Object—is better.

In the SpringBlog application, we chose to create a specific DAO for the Attachment object and then have EntryDao and CommentDao implementations use this DAO. There are two reasons for this decision. First, an Attachment is a child of either an Entry or a Comment. Using the first approach means that we have to duplicate code for inserting, updating, and deleting Attachments across EntryDao and CommentDao implementations. Refactoring this naturally leads to creating an AttachmentDao anyway. Second, we want to be able to manipulate Attachments separately from all other objects; a separate DAO is logical anyway.

Nothing is stopping you from being selective about when you load related data and when you do not. For Domain Objects that form a clear parent/child hierarchy with the child completely dependent on the parent, you should choose to encapsulate all DAO logic within the DAO of the parent object. For Domain Objects that do not form this clear relationship or have multiple parents, you should create a separate DAO and use this from within the DAO of the parent object.

Canonicalization and Data Access

Earlier in the chapter we talked about canonicalization and looked at how to implement this in your DOM. Obviously, the lack of a public constructor on a canonicalized Domain Object is going to have an impact on your DAO objects. In this section, we discuss the problems you face when building a DAO for canonicalized Domain Objects.

The first thing to consider when using canonicalization with Domain Objects is identity. If you recall the ShippingCompany example presented earlier in Listing 11-9, you will remember that client code can access an instance of ShippingCompany using the static fromInt() method. Each instance of ShippingCompany is given a unique ID that the fromInt() method uses to find the appropriate instance. When canonicalizing Domain Objects that are stored in the database, ensure that the appropriate ID is stored in the database to allow you to retrieve the correct ShippingCompany instance. In the case of the ShippingCompany object, our typical approach is to create a table in the database, ShippingCompanies, and then have the primary key field correspond to the ID of the correct ShippingCompany object. You don't actually need to create a table in the database when you are using canonicalized objects, but we would recommend it because you can use foreign key constraints to enforce referential integrity in the database. Consider an Order object that has a property, shippedBy, of type ShippingCompany. Without the ShippingCompanies table and a foreign key constraint between that table and the Orders table, it is possible to store any value in the shippedBy column of the Orders table.

If you choose to use JDBC to build your DAO objects, using canonicalization should not really have much of an impact. Because you are responsible for creating Domain Objects in the DAO, you can simply switch from using the Domain Object constructor to using the static fromInt() method to obtain the appropriate canonicalized object. When using ORM tools such as Hibernate or iBATIS, things become a little less clear-cut.

Hibernate does provide support for canonicalization through the use of the PersistentEnum interface, which has your Domain Object implement a structure very similar to that of the ShippingCompany object. However, you may be uncomfortable coupling your Domain Objects so closely to a particular persistence technology, especially after you have gone to the trouble of creating a separate DAO that is abstracted by a set of well-defined interfaces. If you are planning on committing to Hibernate long-term, then you may not see this as a big issue, but think carefully about committing to this solution. If you are using iBATIS, then you are completely out of luck; it does not support canonicalization at all. Thankfully, there is an easy solution when your ORM tool either requires too close a coupling for canonicalization or just doesn't support it all—use JDBC.

With Spring support, using JDBC is so simple that you should not discount it because of its complexity. However, if you have chosen to use a particular ORM tool, then you may be reluctant to start replacing entire DAO implementations with JDBC-based ones—so are we. The problem arises when you start to look at having both JDBC and, for instance, Hibernate code in a single class; you lose out on Spring support for one or the other because your DAO can only extend either HibernateDaoSupport or JdbcDaoSupport. Thankfully, there is quite an elegant solution to this problem that allows you to encapsulate both Hibernate and JDBC support in a single DAO without losing out on Spring support for both. The key to this is to factor the JDBC code into an inner class and then delegate calls from the main DAO to this inner class. An example of this is shown in Listing 11-16.

Listing 11-16: Mixing Hibernate and JDBC in a Single DAO

image from book
package com.apress.prospring.ch11.canonicalization;      import; import;      public class MyDao extends HibernateDaoSupport {          private MyJdbcDao innerDao;          public MyDao() {         innerDao = new MyJdbcDao();     }          public void update(MyDomainObject obj) {         // use Hibernate to persist the data     }          public MyDomainObject getById(int someId) {         return innerDao.getBy(someId);     }          private static class MyJdbcDao extends JdbcDaoSupport {                  public MyDomainObject getBy(int someId) {             // do some real processing             return null;         }     } } 
image from book

Although this is only a skeleton implementation, you should get the gist. All the JDBC-related code is moved into an inner class that extends JdbcDaoSupport whereas all the Hibernate-related functionality is left in the outer class that can still extend HibernateDaoSupport. With this class, we can still use Hibernate for persisting Domain Objects, but we can redirect querying calls to the embedded JDBC DAO class.

Utilizing canonicalization effectively in your application can lead to greatly improved memory usage by preventing the creation of vast amounts of needless objects. If you must be able to update data related to canonicalized objects and you are using an ORM framework for your DAO objects, then you can introduce a little JDBC code to add canonicalization support in an ORM framework-independent manner.

Data Access Tier Summary

Creating a Data Access Tier for your application provides the rest of your application components with a standard mechanism for storing and retrieving data. Without a Data Access Tier, you will find that data access code becomes spread out through your application, often resulting in code duplication that is hard to maintain. In the long term, this poorly managed code inevitably leads to bugs and developer headaches.

The DAO pattern provides a good base from which to implement your Data Access Tier. You should always define DAOs as interfaces and then implement these interfaces using your chosen data access technology. When you are using Spring, working with interfaces is trivial and you can easily provide concrete implementations of your DAO interfaces to other components in your application.

In this section, we looked at some of the main design-related issues that are present when you are building a Data Access Tier for your application. In reality, much of the complexity in a Data Access Tier comes from implementation, not design. You can find more details on data access in Chapters 10 where we discuss JDBC, Hibernate, and iBATIS, respectively.

Pro Spring
Pro Spring
ISBN: 1590594614
EAN: 2147483647
Year: 2006
Pages: 189

Similar book on Amazon © 2008-2017.
If you may any questions please contact us: