ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


iBatis DAO

by Sunil Patil
08/10/2005

The Data Access Object pattern in the Core J2EE Pattern Catalog suggests that while creating a robust J2EE application, you should abstract and encapsulate all access to a data source behind a common API. In terms of programming, what this means is to create an interface defining all of the business methods that your application needs. Inside your application, you use this interface when you want to interact with your data store, and then create a separate class that implements this interface and has the logic for interacting with that particular data store.

For example, consider the sample application developed in the article iBatis: SQL Maps. This is a Struts application that allows you to fire SELECT, INSERT, UPDATE and DELETE SQL queries on a CONTACT table. In this application, we are using SQL Maps as the persistence framework. Now say we want to change this application so that the CONTACT table is stored in an XML file instead of a RDBMS, or want to use Hibernate for SELECT queries and SQL Map for the other three queries, perhaps because Hibernate provides better support for caching. This will be very hard to implement, and if we are able to change it at all, it won't be a very clean solution.

A better approach for this type of problem would be to create a ContactDAO interface and define business methods for SELECT, INSERT, UPDATE, and DELETE queries in this interface. Then you'd create separate classes containing persistence logic for each method. So there would be a class that knows how to interact with the CONTACT table using SQL Maps, another class that knows how to interact with the CONTACT table if it is stored in a XML file instead of a RDBMS, and so on. Inside your application, you'd choose from different implementations of ContactDAO, depending on your requirements. This relationship is shown in Figure 1.

ContactDAO interface and implementations
Figure 1. ContactDAO interface and implementations

Related Reading

J2EE Design Patterns
By William Crawford, Jonathan Kaplan

iBatis Data Access Object (DAO) is an open source framework now hosted by Apache and targeted at solving these type of problems. It provides you with an infrastructure for creating applications based on the DAO pattern. What this means is that you can create a XML file and declare that XMLContactDAO.java is the implementation class for ContactDAO, which knows how to read and write contacts in an XML file instead of a RDBMS. SQLMapContactDAO is an implementation class that knows how to interact with the CONTACT table using SQL Maps as its persistence framework. Inside your application, you ask the DAO framework for an implementation of ContactDAO for XML, and it will provide you with an XMLContactDAO object. Also, the DAO framework provides you with a uniform interface to handle transaction management, irrespective of what persistence mechanism you use. It will also take care of the low-level details of connection management and initializing persistence frameworks.

This article is a step-by-step guide on how to use the iBatis DAO framework in your application. We will start by looking at how to change our sample application developed in the SQL Maps article to use the DAO framework. Then we will talk about the architecture of the DAO framework. Next we will look at which transaction managers are supported in DAO framework, and will finish with a section on how to create your own transaction manager.

Sample Application

We will start by changing our sample application developed in the SQL Maps article to use the DAO framework.

  1. Copy the ibatis-dao-2.jar file to your WEB-INF/lib folder.

  2. Create DAOMap.xml at the root of your JavaSource folder, like this:

    
    <daoConfig>
     <context id="sqlmap">
      <transactionManager type="SQLMAP">
      <property name="SqlMapConfigResource" value=
       "com/sample/contact/dao/sqlmap/SqlMapConfig.xml"/>
      </transactionManager>
      <dao interface="com.sample.contact.dao.ContactDAO"
       implementation=
       "com.sample.contact.dao.sqlmap.SQLMapContactDAO"/>
     </context>
    </daoConfig>
    
    

    DAOMap.xml file is a deployment descriptor for your iBatis DAO framework. <daoConfig> is the root element. Each <context> element represents one persistence mechanism. In our example, we will use only SQL Maps for persistence, so we so have only one <context> element. Every persistence mechanism should have one <transactionManager> element, which represents the manager used for getting a connection to the underlying data store and marking the transaction boundary. We will talk more about transactionManager later.

    The <context> element also contains a list of DAOs for the given particular persistence mechanism. In our example, we want to create a ContactDAO, which will use SQL Maps for persistence, so we will add one <dao> element defining SQLMapContactDAO.

  1. Create ContactDAO.java, like this:

    
    public interface ContactDAO extends DAO {
        public int insertContact(Contact contact);
        public int updateContact(Contact contact);
        public Contact selectContact(int contactId);
        public int deleteContact(int contactId);
    }
        
    

    ContactDAO.java defines all the business methods required by a client for interacting with the CONTACT table. Please note that all the methods in ContactDAO.java take a Contact object as a parameter, which is a data transfer object for carrying data.

  2. Create a SQLMapContactDAO.java file, like this:

    
    public class SQLMapContactDAO extends
     SqlMapDaoTemplate implements ContactDAO {
      public SQLMapContactDAO(DaoManager arg0) {
          super(arg0);
      }
      public int deleteContact(int contactId) {
        return super.delete("deleteContact",
        new Integer(contactId));
      }
      public int insertContact(Contact contact) {
        Integer contactId =(Integer)super.insert
          ("insertContact",contact);
        return contact.getContactId();
      }
      public Contact selectContact(int contactId) {
        return (Contact)super.queryForObject("getContact",
          new Integer(contactId));
      }
      public int updateContact(Contact contact) {
        return super.update("updateContact",contact);
      }
    }
        
    
    SQLMapContactDAO is a concrete implementation of the ContactDAO interface, using SQL Maps as its persistence mechanism. Note that we are not writing any code for initializing SQL Maps, for getting a connection, or for marking a transaction boundary in our class. Instead, we extend our class from SqlMapDaoTemplate.java, which will take care of all of the underlying repetitive operations for us. Business logic is only thing that we need to worry about in our SQLMapContactDAO class.
  3. Change the execute() method of ContactSelectAction.java, like this:

    
    Contact contactForm = (Contact) form;
    Reader reader=
      Resources.getResourceAsReader("DAOMap.xml");
    DaoManager daoManager =
      DaoManagerBuilder.buildDaoManager(reader);
    ContactDAO contactDAO =
      (ContactDAO) daoManager.getDao(
    ContactDAO.class,"sqlmap");
    
    request.setAttribute("contactDetail",
      contactDAO.selectContact(
        contactForm.getContactId()));
    
    

    The last step is changing the execute() method in our ContactSelectAction class to use the DAO framework. In order to initialize the DAO framework, we need a Reader object for DAOMap.xml. The iBatis framework provides you with the Resources.getResourceAsReader() utility method that will allow you to read a resource as a Reader. Once you have a Reader object representing the DAOMap.xml file, you can pass it to DAOManagerBuilder.buildDaoManager(). This will return an instance of DaoManager, which should be used for interacting with the DAO framework in the future. Ideally, you should initialize your DAO framework at application startup. In our application, we can do that by putting this code in a Struts plugin, but we are initializing it in the execute method just to keep our example simple.

    Once you have an instance of DaoManager, you can call the getDao() method with name of the interface and persistence implementation (the value of the id attribute in the <context> element) that you want to use. In our example, we want an instance of SQLMapContactDAO, so we will pass ContactDAO as the name of the interface and "sqlmap" as the persistence mechanism. Once you have an instance of SQLMapContactDAO, you can start calling business methods on it.

You can try this example by downloading the sample code from the Resources section on the last page of this article.

The Architecture of the DAO Framework

Since we now have a working application, thanks to the DAO framework, let's take a peek at what is going on under the hood. First take a look Figure 2, a sequence diagram of how DAO works.

Thumbnail, click for full-size image.
Figure 2. DAO sequence diagram (click for full-size version)

To initialize the DAO framework, you start by calling DaoManagerBuilder.buildDaoManager() and passing the DAOMap.xml file to it. In this method, the DAO framework will read DAOMap.xml and create a corresponding DAOManager object from it. This object will contain data representing the supported persistence mechanisms. Which interfaces are implemented, and what's the implementation class for a particular combination of interface and persistence mechanism? Basically, this is the Java object equivalent of the DAOMap.xml file.

Once you have a DAOManager object, you can query it to get the SQL Map implementation of the ContactDAO interface. The DAO framework will return a DaoProxy object that wraps the implementation class. In our example, it will return a DaoProxy object for the SQLMapContactDAO class. The DaoProxy object allows the DAO framework to intercept calls to business methods. In our example, when you call contactDAO.selectContact(), the DAO framework will intercept the call and check whether the transaction is already started. If not, it will start a new transaction call by calling the startTransaction() method on the transaction manager. Once the transaction is started, it will call the selectContact() method of SQLMapContactDAO within that transaction. The DaoProxy object will intercept the call to selectContact() method on its way back and use it for committing the transaction.

By the way, if you don't want your transaction to be scoped on the method level, or if you want to call multiple methods in one transaction, you can call daoManager.startTransaction() before calling a business method on ContactDAO, and commit that transaction when done by calling daoManager.commitTransaction().

Now the only remaining issue is who takes care of initializing the persistence mechanism and passing control to it. In our example, that means deciding who passes the path of SqlMapConfig.xml to the SQL Map framework and initializes it. This also means deciding who takes care of the actual interaction with the SQL Maps framework. The DAO framework provides us with Template classes for every persistence mechanism. In your application, you should extend your implementation class from this Template class, and write only business logic in your method; after that, pass control to this template class, which will take care of interacting with persistence mechanism. Our example calls super.queryForObject("getContact",new Integer(contactId));, which means SqlMapDaoTemplate will take care of initializing and interacting with the SQL Maps framework.

Your persistence mechanism may need some information for initialization. In our example, it requires the path to SqlMapConfig.xml, which contains information like the name of the driver class, the JDBC URL, the login info, etc. Such information required by a particular transaction manager is passed as a property element to it in the DaoMap.xml file. In the next section, we will talk about which transaction managers are supported by the DAO framework and the initialization information required by each.

Supported Persistence Mechanisms

The DAO framework provides built-in support for a few persistence mechanisms. To use one of the built-in transactionManagers, you have to do two things:

  1. Declare support for it in DAOMap.xml file by adding a <transactionManager> element and passing required information to it as properties.
  2. Extend the appropriate Template class for that transactionManager while creating your DAO implementation class.

Now we will go through the built-in transactionManagers and find out how to use each of them in your application.

JDBC

The JDBC transaction manager is good if you don't want to use any framework for persistence and want to write your own JDBC code. If you're using JDBC as your persistence mechanism, you can use one of the three connection management options:

  1. SIMPLE: Use SIMPLE as the value of the DataSource element if you want to use iBatis' own implementation of connection pooling. Pass the usual JDBC properties (DriverManager class, JDBC URL, etc.) to it as Properties. Look at the online iBatis documentation to learn about advanced connection properties.
    
    <transactionManager type="JDBC">
     <property name="DataSource" value="SIMPLE"/>
     <property name="JDBC.Driver"
      value="com.ibm.db2j.jdbc.DB2jDriver"/>
     <property name="JDBC.ConnectionURL"
      value="jdbc:db2j:D:\cloudscape\wpsdb"/>
     <property name="JDBC.Username"
      value="db2admin"/>
     <property name="JDBC.Password"
      value="db2admin"/>
     <property name="JDBC.DefaultAutoCommit"
      value="true" />
    </transactionManager>
    
    

  1. DBCP: Use DBCP when you want to use Apache DBCP for connection management. Please see the DAO online guide for information on how to configure the DBCP connection pool.
  2. JNDI: When you want to use the application server's implementation of the connection pool, all you have to do is provide the JNDI name of your connection pool and the DAO framework will use it for getting a connection.
    
    <transactionManager type="JDBC">
     <property name="DataSource" value="JNDI"/>
     <property name="DBJndiContext"
      value="java:comp/env/jdbc/MyDataSource"/>
    </transactionManager>
    
    

Then you will have to create a class that extends JdbcDaoTemplate.java to implement your business interface. In our sample, we created JDBCContactDAO.java. In the business method, you can ask your superclass for the connection by calling getConnection(). Since we are not using any persistence framework, we'll have to create our own SQL queries and execute them.


public int updateContact(Contact contact) {
 try {
  Connection conn = getConnection();
  PreparedStatement updateStmt =
   conn.prepareStatement("UPDATE DB2ADMIN.CONTACT
    SET FIRSTNAME=?,LASTNAME=? WHERE CONTACTID=?");
  updateStmt.setString(1, contact.getFirstName());
  updateStmt.setString(2, contact.getLastName());
  updateStmt.setInt(3, contact.getContactId());
  return updateStmt.executeUpdate();
 } catch (SQLException ex) {
    throw new DaoException(ex);
 }
}

When using a JDBC transactionManager, the DAO framework will control the transaction by calling commit and rollback methods on the Connection object, so transactions will be handled at the Connection level instead of participating in global transaction.

JTA

If you are creating a J2EE application, then it is a much better idea to use the connection pool provided by your application server, because it will perform much better than a SIMPLE or DBCP connection pool. Also, with a J2EE application, RDBMS will be only one of the transactional sources; in addition to RDBMS, you will also have other things like JCA, MQ Server, etc. This means you cannot start and commit transactions at the connection level; instead, your code should participate in global transaction by calling the begin() and commit() methods on a UserTransaction. For this type of requirement, you can use JTA as transctionManager and provide the JNDI URL of both your DataSource pool and UserTransaction object to it.


<transactionManager type="JTA">
 <property name="DBJndiContext"
  value="java:comp/env/jdbc/MyDataSource"/>
 <property name="UserTransaction"
  value="java:comp/env/UserTransaction"/>
</transactionManager>

Hibernate

Since Hibernate is a very popular persistence framework, iBatis DAO offers support for it. To use Hibernate in your application, add a <transactionManager> element in your DAOMap.xml file, as follows.


<transactionManager type="HIBERNATE">
 <property name="hibernate.dialect"
  value="net.sf.hibernate.dialect.Cloudscape"/>
 <property name="hibernate.connection.driver_class"
  value="com.ibm.db2j.jdbc.DB2jDriver"/>
 <property name="hibernate.connection.url"
  value="jdbc:db2j:D:\cloudscape\wpsdb"/>
 <property name="hibernate.connection.username"
  value="db2admin/>
 <property name="hibernate.connection.password"
  value="db2admin"/>
 <property name="class.1"
  value="com.sample.contact.Contact"/>
</transactionManager>

You also need to create a DAO class extending HibernateDaoTemplate. Inside your DAO, you can access the Hibernate Session object by calling the getSession() method.

SQL Map

Please look at the sample application (in the Resources section) for details about how to use the SQL Map persistence framework in your application.

External

An external transaction manager allows transactions to be externally controlled by the DAO framework. This behavior is good for interacting with non-RDBMS data sources. In the next section, we will see how to use the DAO framework to use an XML file as a data source.

<transactionManager type="EXTERNAL">
</transactionManager>

Developing an XML Transaction Map

There may be situations when you want to read and write data from an XML file instead of an RDBMS. For example, imagine working on a banking project where you don't have direct access to bank's database, and instead the customer provides you with sample data in the form of XML files. Your requirement is that your application should use these XML files during development, and a RDBMS in production. We will take our contact application as example and change it so that it will read and write data from an XML file instead of an RDBMS. This requires two changes:

  1. Add support for an external transactionManager in your DAOMap.xml file.
  2. Create a XMLContactDAO.java file, as follows:
    
    public class XMLContactDAO implements ContactDAO {
     public static final String
      CONTACTXMLNAME = "c:\\Contact.xml";
     public XMLContactDAO(DaoManager manager) {
        super(manager);
     }
     public int insertContact(Contact contact) {
      HashMap contactMap = loadChanges();
      if (contactMap.get(new Integer
        (contact.getContactId())) == null)
       contactMap.put(new
        Integer(contact.getContactId()), contact);
      saveChanges(contactMap);
      return contact.getContactId();
     }
     public Contact selectContact(int contactId) {
      HashMap contactMap = loadChanges();
      return (Contact) contactMap.get(
       new Integer(contactId));
     }
     public HashMap loadChanges() {
      HashMap contactMap = null;
      try {
       XStream xstream = new XStream(new DomDriver());
       xstream.alias("contact", Contact.class);
       contactMap =
        (HashMap) xstream.fromXML(
         new FileReader(CONTACTXMLNAME),HashMap.class);
      } catch (FileNotFoundException e) {
        e.printStackTrace();
        return new HashMap();
      }
      return contactMap;
     }
     public void saveChanges(HashMap contactMap) {
      try {
       XStream xstream = new XStream();
       xstream.alias("contact", Contact.class);
       xstream.toXML(contactMap,
        new FileWriter(CONTACTXMLNAME));
       } catch (IOException e) {
       e.printStackTrace();
      }
     }
    }
    
    

In this example, XMLContactDAO implements the ContactDAO business interface. Since we are using an EXTERNAL transaction manager, we cannot use any of the existing Template classes. In our class, we have created two simple methods--loadChanges() and saveChanges--for reading and writing an XML file using the XStream framework. XStream is open source framework that helps you read an XML file as a Java object and save a Java object as an XML file.

Conclusion

Nowadays, a lot of new persistence frameworks are coming up. As a developer, this is both good and bad for you. It's good because now you have many more options to choose from. But it's bad because you have to make choices. And the bigger problem is that you have to commit to one persistence framework at the start of the project, at which point you may not be completely clear on project requirements or completely sure if a particular framework can fulfill all your requirements. DAO is a very easy-to-use and useful framework that guards against changes in persistence mechanisms. It may look like you're making some investment up front, but it will definitely help you in the long run.

Resources

Sunil Patil has worked on J2EE technologies for more than five years. His areas of interest include object relational mapping tools, UI frameworks, and portals.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.