Chapter 11. Hibernate Support

In this chapter, we will take a look at the Hibernate support code in Spring. We will begin with a short description of the Hibernate versions and their support in Spring. Next, you will read about configuration of the Hibernate session factory and transaction manager. After that, we will take a look at how to write efficient data manipulation code, focusing especially on using Hibernate with other data access methods. Next, we will take a look at lazy loading and session management—these areas are not very difficult conceptually but can become very complex in their implementation. Finally, we will you show some integration testing approaches.

Hibernate Primer

Before we start the discussion of Spring Hibernate support, we should take a quick look at how Hibernate works. Hibernate is an object-relational mapping (ORM) tool; it finds, saves, and deletes plain Java objects in a database with minimal impact on the Java code. Let's take a look at a simple LogEntry object defined in Listing 11-1.

Example 11.1. LogEntry Domain Object

public class LogEntry {
    private Long id;
    private String name;
    private Date date;

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

We need to tell Hibernate how we want to store the LogEntry objects. Next, we define the table name and the column names for the properties we want to persist. We must pay special attention to the primary key. Listing 11-2 shows the final Hibernate mapping file.

Example 11.2. Hibernate Mapping for the LogEntry Object

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping default-lazy="false">

    <class name="com.apress.prospring2.ch11.domain.LogEntry" table="t_log_entry">
        <id name="id" type="long" unsaved-value="null">
            <generator class="sequence">
                <param name="sequence">s_log_entry_id</param>
            </generator>
        </id>
        <property name="name" column="name_" not-null="true"/>
        <property name="date" column="date_" not-null="true"/>
    </class>

</hibernate-mapping>

Here, you see that the primary key (the id column and property) is generated from a sequence named s_log_entry_id. Hibernate can tell (again through the information from the configuration file) when to perform a SQL insert and when to do an update. In this particular case, if the value returned from the getId() method is null, Hibernate will issue an insert; otherwise, it will issue an update. The name property is stored in a not null column called name_ and the date property is stored in a not null column called date_.

Now, to insert a new LogEntry object, we simply create its instance and call session.saveOrUpdate(Object). To find a mapped object identified by its primary key, we use session.load(Class, Serializable) or session.get(Class, Serializable). We will explore the differences between these two calls later on in the chapter. To select more than one log entry, we use many of the Query methods (we create the Query instances by calling one of the session.createQuery() methods). Finally, to delete objects, we use the session.delete methods.

Before we get to the details of the Spring Hibernate support, we must take a look at how Spring organizes the support classes in its distribution.

Packaging

There are currently two major versions of Hibernate: Hibernate 2.x and Hibernate 3.x. Since the packaging of the two Hibernate versions in Spring is very similar, the Spring distribution, by default, includes Hibernate 3 support. If you want to use Hibernate 2.x, you must include the spring-hibernate2.jar package and be careful not to accidentally use one of the org.springframework.orm.hibernate3 classes. Alternatively, you may choose the Spring modular distribution and include only the specific Spring packages your application needs. However, for new applications, we recommend that you use Hibernate 3, which means that you can carry on using the default spring.jar package.

The Spring Hibernate support works with versions 2.1.8 for Hibernate 2 support and version 3.2.5ga for Hibernate 3 support.

Introduction to Hibernate Support

The main Hibernate class is Session, which provides methods to find, save, and delete mapped objects. To create a Hibernate Session, you must first create the SessionFactory; creating and configuring SessionFactory to create the Sessions can be quite complex and cumbersome. Luckily, Spring comes to your aid with its implementation of the AbstractSessionFactoryBean subclasses: LocalSessionFactoryBean and AnnotationSessionFactoryBean. The LocalSessionFactoryBean needs to be configured with the mapping file locations, and these file locations need to be local from the application's point of view. In most cases, this means that the mapping resources are on the classpath. The second subclass, the AnnotationSessionFactoryBean, picks up Hibernate mappings from annotations on the classes we wish to use in Hibernate.

Note

We do not encourage the use of annotations on the objects you will persist using Hibernate. In most cases, the persistent objects will be your application's domain objects. Using Hibernate-specific annotations exposes too much information to the tiers above the data access tier. We prefer the old-fashioned way of creating mapping files—leaving the domain objects completely independent of the DAO implementation you choose.

If you annotate your domain objects with Hibernate-specific annotations, you are implying that you have implemented the data access in Hibernate.

Regardless of the mapping information source, we need to supply some common dependencies to construct a SessionFactory using the appropriate factory bean. Listing 11-3 shows the minimalist LocalSessionFactoryBean configuration.

Example 11.3. Minimal Configuration of the LocalSessionFactoryBean

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
          http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
          http://www.springframework.org/schema/jee
         http://www.springframework.org/schema/j2ee/spring-jee-2.5.xsd">

    <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/prospring2/ch11"
        expected-type="javax.sql.DataSource"/>

    <bean id="hibernateSessionFactory"
       class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mappingLocations">
            <list>
<value>classpath*:/com/apress/prospring2/ch11/dataaccess/
Minimal Configuration of the LocalSessionFactoryBean
hibernate/*.hbm.xml</value> </list> </property> <property name="hibernateProperties"> <value>hibernate.dialect=org.hibernate.dialect.Oracle9Dialect</value> </property> </bean> </beans>

First, we declare the dataSource bean of type JndiObjectFactoryBean (for a detailed explanation of why we use JNDI, see Chapter 22). Next, we declare the hibernateSessionFactory bean and set its dataSource, mappingLocations, and hibernateProperties properties. The dataSoruce property is necessary but not really very interesting. The mappingLocations property is much more intriguing. It is a list of values that can be converted into a Spring Resource; in this particular case, we include all *.hbm.xml files in the com.apress.prospring2.ch11.dataaccess.hibernte package. Finally, we need to tell Hibernate what SQL dialect to use. To do that, we set the hibernate.dialect property in the properties list of the hibernateProperties property (we honestly have to use the word property four times!). In this particular case, the database can be Oracle 9 or later (we use Oracle 10g). To verify that everything works, we will run the code from Listing 11-4.

Example 11.4. Sample Application to Verify That the Hibernate Configuration Works

public class DaoDemo {

    public static void buildJndi() {
        try {
            TestUtils.NamingContextBuilder builder;
            builder = TestUtils.NamingContextBuilder.emptyActivatedContextBuilder();

            String connectionString = "jdbc:oracle:thin:@oracle.devcake.co.uk" +
                ":1521:INTL";
            builder.bind("java:comp/env/jdbc/prospring2/ch11",
                     new DriverManagerDataSource(
                    "oracle.jdbc.driver.OracleDriver", connectionString,
                    "PROSPRING", "x******6"));
        } catch (NamingException ignored) {
        }
    }

    public static void main(String[] args) throws NamingException {
        buildJndi();
        ApplicationContext ac =
            new ClassPathXmlApplicationContext("datasource-context-minimal.xml",
                DaoDemo.class);
    }

}

The sample application only creates and destroys the Hibernate beans. It is important to take note of the dependencies of this code. We need to set the following packages on the classpath; otherwise, we will get dreaded ClassNotFound exceptions:

  • antlr-2.7.6.jar

  • cglib-2.1_3.jar

  • commons-logging-1.1.jar

  • spring-2.0.6.jar

  • hibernate-3.2.5ga.jar

  • log4j-1.2.14.jar

When we run the application with the correct classpath, we should see the following log entries as the application starts up:

INFO [main] SessionFactoryImpl.<init>(161) | building session factory
DEBUG [main] SessionFactoryImpl.<init>(173) | 
Sample Application to Verify That the Hibernate Configuration Works
Session factory constructed with filter configurations : {} DEBUG [main] SessionFactoryImpl.<init>(177) | instantiating session factory with
Sample Application to Verify That the Hibernate Configuration Works
properties: {java.runtime.name=Java(TM) 2 Runtime Environment, Standard Edition, ... hibernate.bytecode.use_reflection_optimizer=false, ... hibernate.dialect=org.hibernate.dialect.Oracle9Dialect, ... hibernate.connection.provider_class=
Sample Application to Verify That the Hibernate Configuration Works
org.springframework.orm.hibernate3.LocalDataSourceConnectionProvider, ... } ... DEBUG [main] SessionFactoryObjectFactory.<clinit>(39) |
Sample Application to Verify That the Hibernate Configuration Works
initializing class SessionFactoryObjectFactory DEBUG [main] SessionFactoryObjectFactory.addInstance(76) |
Sample Application to Verify That the Hibernate Configuration Works
registered: 40284884158164670115816467d30000 (unnamed) ...

Notice that Spring builds the Hibernate SessionFactory by providing the properties in the Hibernate Spring context file and the hibernate.connection.provider_class property, which allows Spring to provide an implementation of Hibernate's ConnectionProvider so that Hibernate can access Spring-managed DataSource beans.

Using Hibernate Sessions

If, for some reason, you need to access the low-level Hibernate SessionFactory and Session instances created by calls to the factory, you can easily reference the Spring-managed bean that extends the AbstractSessionFactoryBean. Listing 11-5 shows you how to do that.

Example 11.5. Direct Access to Hibernate SessionFactory and Session

public class DaoDemo {

    public static void buildJndi() { /* same code */ }

    public static void main(String[] args) throws NamingException {
        buildJndi();
        ApplicationContext ac =
            new ClassPathXmlApplicationContext("datasource-context-minimal.xml",
            DaoDemo.class);
SessionFactory sessionFactory =
            (SessionFactory)ac.getBean("hibernateSessionFactory");
        Session session = sessionFactory.openSession();
        // use the Session
        session.close();
    }
}

Notice that you have to manually request a new session and that you have to explicitly close the session. The call to session.close() should really happen even if the operations we perform on the session fail with an exception. In addition to this, Hibernate sessions should typically begin with a call to the beginTransaction() method and end with a call to the Transaction.commit() method. We would also like to roll back the transaction if there are exceptions. The refactored code of the main method is shown in Listing 11-6.

Example 11.6. Refactrored Code of the main Method

public class DaoDemo {

    public static void main(String[] args) throws Exception {
        buildJndi();
        ApplicationContext ac =
            new ClassPathXmlApplicationContext("datasource-context-minimal.xml",
            DaoDemo.class);
        SessionFactory sessionFactory =
            (SessionFactory)ac.getBean("hibernateSessionFactory");

        Session session = null;
        Transaction transaction = null;
        try {
            session = sessionFactory.openSession();
            transaction = session.beginTransaction();
            // do work
            transaction.commit();
        } catch (Exception e) {
            if (transaction != null) transaction.rollback();
            throw e;
        } finally {
            if (session != null) session.close();
        }
    }

}

The code in bold shows the important refactorings: first of all the // do work line performs whatever database work we wish to do. If the work succeeds (i.e., there are no exceptions), we commit the transaction; otherwise, we perform a rollback. Finally, we close the session. This code is acceptable if it only appears in the main method. If we find that we're writing the same code (except for the actual database work represented by the // do work marker here), we should look at using the template method pattern. This is exactly what we have done in Spring. The HibernateTemplate has operations that take instances of the HibernateCallback interface. This interface performs the actual work in the database, but the calling code takes care of session management. Figure 11-1 shows the concept behind the implementation of the HibernateTemplate callback.

UML diagram of the HibernateTemplate concept

Figure 11.1. UML diagram of the HibernateTemplate concept

As you can see, all the hard work gets done in the HibernateTemplate class. All you have to do is supply the HibernateCallback implementation that performs the work you want to do in the session. Usually, the HibernateCallback implementation is an anonymous class; Listing 11-7 shows an example of its use.

Example 11.7. Use of the HibernateTemplate Class

public class HibernateTemplateDemo {

    public static void main(String[] args) throws Exception {
        DaoDemoUtils.buildJndi();
        ApplicationContext ac =
            new ClassPathXmlApplicationContext("datasource-context-minimal.xml",
            DaoDemo.class);
        SessionFactory sessionFactory =
            (SessionFactory)ac.getBean("hibernateSessionFactory");
        HibernateTemplate template = new HibernateTemplate(sessionFactory);
        template.execute(new HibernateCallback() {
            public Object doInHibernate(Session session)
                throws HibernateException, SQLException {
                // do the work
                return null;
            }
        });
    }

}

The preceding listing illustrates most of the points we discussed. We create the HibernateTemplate class and give it the SessionFactory instance. Next, we call its execute method and supply an anonymous implementation of the HibernateCallback that does all the database work. We get the Session as the argument of the doInHibernate method in the callback. The session argument is never null, and we do not have to worry about closing it or about managing exceptions in the implementation. In addition to this, the code in the callback supplied to the doInHibernate method can participate in Spring transactions (we discuss transactions in much more detail in Chapter 16). The value we return from the doInHibernate method simply gets propagated to the caller as the result of the execute method.

In most applications, we will set the HibernateTemplate as a dependency; you can do this because all methods of the HibernateTemplate are thread safe, and you can, therefore, share a single instance of HibernateTemplate among many DAO beans. Listing 11-8 shows how to declare the hibernateTemplate bean; it can then be used in the same manner as the manually created HibernateTemplate instance.

Example 11.8. hibernateTemplate Bean and Its Use

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/jee
           http://www.springframework.org/schema/j2ee/spring-jee-2.5.xsd">

    <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/prospring2/ch11"
        expected-type="javax.sql.DataSource"/>

    <bean id="hibernateSessionFactory"
       class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mappingLocations">
            <list>
                <value>classpath*:/com/apress/prospring2/ch11/dataaccess/
hibernateTemplate Bean and Its Use
hibernate/*.hbm.xml</value> </list> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect"> org.hibernate.dialect.Oracle9Dialect </prop> </props> </property> </bean> <bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate"> <property name="sessionFactory" ref="hibernateSessionFactory"/> </bean> </beans> public class HibernateTemplateBeanDemo {
public static void main(String[] args) throws Exception {
        DaoDemoUtils.buildJndi();
        ApplicationContext ac =
            new ClassPathXmlApplicationContext("datasource-context-ht.xml",
            DaoDemo.class);
        HibernateTemplate template =
            (HibernateTemplate) ac.getBean("hibernateTemplate");
        template.execute(new HibernateCallback() {
            public Object doInHibernate(Session session)
                throws HibernateException, SQLException {
                // do the work
                return null;
            }
        });
    }

}

This change makes the implementation of any potential DAO interfaces quite straightforward; the HibernateTemplate instance is a Spring-managed bean, which means that we do not have to worry about creating new instances in our DAO implementations. The only reservation we may have is that setting up the implementation simply requires too much typing: we need to declare a property of type HibernateTemplate and its setter in every DAO. This is why Spring contains the HibernateDaoSuppot convenience superclass. This class gives us access to the HibernateTemplate and the Hibernate Session and SessionFactory.

Using HibernateDaoSupport

The convenient, abstract HibernateDaoSupport superclass allows us to implement our DAO interfaces in Hibernate with as little code as possible. Let's take a look at the code in Listing 11-9, which shows the LogEntryDao interface and its Hibernate implementation.

Example 11.9. LogEntryDao and Its Implementation

public interface LogEntryDao {

    void save(LogEntry logEntry);

    List<LogEntry> getAll();

}

public class HibernateLogEntryDao extends
    HibernateDaoSupport implements
    LogEntryDao {

    public void save(LogEntry logEntry) {
        getHibernateTemplate().saveOrUpdate(logEntry);
    }

    @SuppressWarnings({"unchecked"})
    public List<LogEntry> getAll() {
        return getHibernateTemplate().find("from LogEntry");
    }
}

You can see that the implementation is incredibly simple—all the code we really need to insert or update a LogEntry object to the database is really getHibernateTemplate().saveOrUpdate(logEntry). Selecting all LogEntry objects from the database is equally simple.

Note

The @SuppressWarnings({"unchecked"}) annotation tells the javac compiler not to report the unchecked operations warning. Hibernate does not support generics (and how could it know what classes to return without first parsing the query?) and neither does HibernateTemplate. You need to unit and integration test your DAO code to make sure you are getting the correct objects back.

Listing 11-10 shows the only configuration needed to create the HibernateLogEntryDao.

Example 11.10. Spring Context File for the HibernateLogEntryDao

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/jee
           http://www.springframework.org/schema/j2ee/spring-jee-2.5.xsd">

    <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/prospring2/ch11"
        expected-type="javax.sql.DataSource"/>

    <bean id="hibernateSessionFactory"
       class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mappingLocations">
            <list>
                <value>classpath*:/com/apress/prospring2/ch11/dataaccess/
Spring Context File for the HibernateLogEntryDao
hibernate/*.hbm.xml</value> </list> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect"> org.hibernate.dialect.Oracle9Dialect </prop> </props> </property> </bean> <bean id="logEntryDao" class="com.apress.prospring2.ch11.dataaccess.hibernate.
Spring Context File for the HibernateLogEntryDao
HibernateLogEntryDao"> <property name="sessionFactory" ref="hibernateSessionFactory"/> </bean> </beans>

We can now use the logEntryDao as a standard Spring-managed bean. This configuration shows only one HibernateSupportDao subclass; in real-world applications, having tens of such DAO implementations is not unusual. We could repeat the <property name="sessionFactory"> . . . line in every DAO bean definition, but we could save even this bit of additional typing by declaring an abstract hibernateDaoSupport bean and creating the real DAO subbeans (see Listing 11-11).

Example 11.11. Abstract hibernateDaoSupport Bean and Its Use

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    ....>
    <bean id="hibernateDaoSupport" abstract="true"
        class="org.springframework.orm.hibernate3.support.HibernateDaoSupport">
        <property name="sessionFactory" ref="hibernateSessionFactory"/>
    </bean>

    <bean id="logEntryDao"
        class="com.apress.prospring2.ch11.dataaccess.hibernate.HibernateLogEntryDao"
       parent="hibernateDaoSupport"/>

</beans>

This is exactly what we need: we can now add a new HibernateDaoSupport subclass with as little Spring configuration as possible.

Deciding Between HibernateTemplate and Session

Recall that the JdbcTemplate's work is twofold: it provides common resource management and translates the JDBC checked exceptions to the Spring data access exceptions. Hibernate 3 uses runtime exceptions, and Spring 2.5's @Repository annotation gives you resource management similar to what you get in HibernateTemplate. Therefore, we can say that, in most cases, using the HibernateTemplate is not necessary at all. Consider the code in Listing 11-12.

Example 11.12. Using Hibernate SessionFactory Instead of HibernateTemplate

@Repository
public class TemplatelessHibernateInvoiceLogEntryDao implements LogEntryDao {
    private SessionFactory sessionFactory;

    public TemplatelessHibernateInvoiceLogEntryDao(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    public LogEntry getById(Long id) {
        return (LogEntry) this.sessionFactory.getCurrentSession().
            get(LogEntry.class, id);
    }

    public void save(LogEntry logEntry) {
        this.sessionFactory.getCurrentSession().saveOrUpdate(logEntry);
    }

    public List<LogEntry> getAll() {
        return this.sessionFactory.getCurrentSession().
            createQuery("from LogEntry").list();
    }
}

This clearly shows that we are using the SessionFactory interface from Hibernate, and we are using its getCurrentSession method. This method, according to the Hibernate documentation, returns a Session bound to the current thread. Listing 11-13 shows how we use the TemplatelessHibernateInvoiceLogEntryDao bean in a sample application.

Example 11.13. Using the templatelessLogEntryDao Bean

public class TemplatelessHibernateLogEntryDaoDemo {

    public static void main(String[] args) throws Exception {
        DaoDemoUtils.buildJndi();
        ApplicationContext ac = new ClassPathXmlApplicationContext(
            "datasource-context-dao.xml", DaoDemo.class);
        LogEntryDao logEntryDao =
            (LogEntryDao) ac.getBean("templatelessLogEntryDao");
        logEntryDao.getAll();
    }

}

When we run the example in Listing 11-13, it fails:

Exception in thread "main" org.hibernate.HibernateException:
No Hibernate Session bound to thread, and configuration does not allow
creation of non-transactional one here.

The exception makes sense: the SessionFactory.getCurrentSession method tries to return the Session bound to the current thread, but there is no such Session, and the SessionFactory configuration does not include information about how to create one. We can tell Hibernate how to create a new thread-bound Session (and which transaction manager to use) by modifying the hibernateSessionFactory bean definition. Listing 11-14 shows the modified datasource-context-dao.xml file.

Example 11.14. The Modified hibernateSessionFactory Bean

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    ...>

...

     <bean id="hibernateSessionFactory"
          class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
          <property name="dataSource" ref="dataSource"/>
          <property name="mappingLocations">
               <list>
                    <value>classpath*:/com/apress/prospring2/ch11/dataaccess/
The Modified hibernateSessionFactory Bean
hibernate/*.hbm.xml</value> </list> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect"> org.hibernate.dialect.Oracle9Dialect </prop>
<prop key="hibernate.current_session_context_class">
                        thread
                    </prop>
                    <prop key="hibernate.transaction.factory_class">
                           org.hibernate.transaction.JDBCTransactionFactory</prop>
              </props>
          </property>
    </bean>
...
</beans>

This change allows Hibernate to create a new thread-bound Session in the call to getCurrentSession. However, the application still fails when we call the createQuery method; the exception is

org.hibernate.HibernateException: createQuery is not valid 
The Modified hibernateSessionFactory Bean
without active transaction

We will need to make one last modification to our code and include an explicit call to create a Hibernate transaction. Listing 11-15 shows the new TemplatelessHibernateLogEntryDao class.

Example 11.15. Modified Templateless DAO

@Repository
public class TemplatelessHibernateInvoiceLogEntryDao implements LogEntryDao {
    ...
    public List<LogEntry> getAll() {
        Transaction transaction = this.sessionFactory.getCurrentSession().
            beginTransaction();
        try {
            return this.sessionFactory.getCurrentSession().
                createQuery("from LogEntry").list();
        } finally {
            transaction.commit();
        }
    }
}

The text in bold shows the code we needed to add to make the sample work. But let's not stop here. We need to take a look at the exception handling and translation. To do this, we will add the getByName(String) method to the LogEntryDao interface but make a deliberate mistake in its implementation. Both implementations of the LogEntryDao interface (HibernateLogEntryDao and TemplatelessLogEntryDao) are going to run the same Hibernate code (see Listing 11-16).

Example 11.16. Incorrect Hibernate Code in the LogEntryDao Implementations

public class HibernateLogEntryDao extends HibernateDaoSupport
    implements LogEntryDao {
...
    public LogEntry getByName(final String name) {
        return (LogEntry) getHibernateTemplate().execute(new HibernateCallback() {
            public Object doInHibernate(Session session)
                throws HibernateException, SQLException {
                return session.
                        createQuery("from LogEntry where name = :name").
                        setParameter("name", name).
uniqueResult();
            }
        });
    }
...
}

@Repository
public class TemplatelessHibernateInvoiceLogEntryDao implements LogEntryDao {
    private SessionFactory sessionFactory;

...
    public LogEntry getByName(String name) {
        Transaction transaction = this.sessionFactory.getCurrentSession().
            beginTransaction();
        try {
            return (LogEntry) this.sessionFactory.getCurrentSession().
                    createQuery("from LogEntry where name = :name").
                    setParameter("name", name).
                    uniqueResult();
        } finally {
            transaction.commit();
        }
    }
...
}

You can see that the code in bold is the same in both classes. However, when we run it, with more than one row with the same value in the name_ column, we get the following exception when we use the HibernateTemplate:

org.springframework.dao.IncorrectResultSizeDataAccessException:
query did not return a unique result: 2

However, we get the following one when we use the Session directly:

org.hibernate.NonUniqueResultException: query did not return a unique result: 2

Obviously, there was no exception translation. However, we can add a bean post processor that will instruct the framework to intercept all calls to the beans annotated with @Repository and perform the standard exception translation. Listing 11-17 shows the one-line post processor definition.

Example 11.17. The Exception Translator Bean Post Processor

<bean class="org.springframework.dao.annotation.
The Exception Translator Bean Post Processor
PersistenceExceptionTranslationPostProcessor"/>

With this bean in place, we can run TemplatelessHibernateLogEntryDaoDemo, and even though we are not using the HibernateTemplate at all, we still get the Spring data access exception when we call the getByName method.

You can see that your DAO implementation does not need to use hardly any Spring code at all, and when you use the Spring declarative transaction management and your DAO templateless bean implementation in a transactional context, you do not even need to worry about the explicit Hibernate transaction management code. Even though this approach makes your Hibernate DAO classes almost completely independent of Spring (save for the @Repository annotation), we still favor the HibernateTemplate approach. Using HibernateTemplate makes your code framework dependent, but chances are that you will not need to change the framework in the life cycle of the application.

Using Hibernate in Enterprise Applications

The code in the examples in the previous section is perfectly functional Hibernate code. In fact, you can take the code we have written and use it in almost any enterprise application. But it will not work as efficiently as we would like.

The first problem is that we have done nothing to prevent updates of stale data. The code we have will simply update the rows in the database without any checks.

Next, we do not consider transactional behavior. Without any additional code, the code in the callback supplied to the doInHibernate method does not automatically run in a transaction. Enterprise applications certainly need to support transactions in the traditional sense of the word, conforming to the ACID (atomicity, consistency, independence, and durability) rules. Moreover, enterprise applications often include other resources (a JMS queue, for example) in a transaction.

Next, the examples we have given work with LogEntry objects. The LogEntry class does not have any associations; it can be fully constructed using data from a single database row. Enterprise applications usually manipulate complex objects with many associations. Handling these associations efficiently is important; otherwise, you may end up with extremely complex SQL statements.

Finally, if the t_log_entry table contained hundreds of thousands of rows, the examples we gave would simply crash with a java.lang.OutOfMemoryException. Real applications need to be able to deal efficiently and elegantly with very large data sets.

In this section, we will take a look at how we can solve each of these problems in a way that is as close to real-world application code as possible.

Preventing Stale Data Updates

Table 11-1 shows a scenario in which our application can overwrite data updated by another user.

Table 11.1. Stale Data Updates Scenario

Time

Thread A

Thread B

Database

0

LogEntry e = load(1L)

 

1, "Test", 12/10/2007

1

 

LogEntry e = load(1L)

1, "Test", 12/10/2007

2

e.setName("X")

 

1, "Test", 12/10/2007

3

save(e)

 

1, "X", 12/10/2007

4

 

e.setName("Z")

1, "X", 12/10/2007

5

 

save(e)

1, "Z", 12/10/2007

The critical operation happens in Thread B at time 5. Thread B is allowed to overwrite an update performed by Thread A at time 3. This is most likely a problem, because Thread B thinks it is changing Name to Z, while in fact it is changing X to Z. In this particular case, it is not a major problem, but if you replace the name column for account_balance, the problem becomes much more serious.

There are several ways to prevent this situation: Thread A can lock the entire row so that Thread B cannot read it until Thread A has finished updating it. This approach is called pessimistic locking—we believe that problems will happen so we lock the row to guarantee exclusive access to it. This approach will prevent the problem described in Table 11-1, but it will also introduce a significant performance bottleneck. If the application is performing a lot of updates, we may end up with the majority of the table locked, and the system will be forever waiting for rows to become unlocked.

If you perform more reads than writes, it is better to leave the rows unlocked and use optimistic locking. With optimistic locking, we do not explicitly lock the row for updates, because we believe that conflicts will not happen. To identify stale data, we add a version column to the table and to our domain object. When we save, we check that the version in the database matches the version in our object. If it does, no other thread has modified the row we are about to update, and we can proceed. If the version in our object differs from the version of the row in the database, we throw an exception indicating that we attempted to save stale data. Hibernate makes optimistic locking very easy; Listing 11-18 shows the optimistic-locking-enabled LogEntry object.

Example 11.18. Optimistic Locking LogEntry Object

public class LogEntry {
    private Long id;
    private String name;
    private Date date;
    private Long version;

    // getters and setters

    public Long getVersion() {
        return version;
    }

    public void setVersion(Long version) {
        this.version = version;
    }
}

Next, take a look at Listing 11-19, where we tell Hibernate to perform optimistic locking checks on the LogEntry object.

Example 11.19. Hibernate Mapping Configuration for the LogEntry Class

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping default-lazy="false">

    <class name="com.apress.prospring2.ch11.domain.LogEntry" table="t_log_entry">
        <id name="id" type="long" unsaved-value="null">
            <generator class="sequence">
                <param name="sequence">s_log_entry_id</param>
            </generator>
        </id>
        <version name="version" column="version" unsaved-value="null" type="long" />
        <property name="name" column="name_" not-null="true"/>
        <property name="date" column="date_" not-null="true"/>
    </class>

</hibernate-mapping>

The code in bold shows the only modification we needed to instruct Hibernate to perform optimistic-locking checks whenever we attempt to save the LogEntry object. To test that Hibernate will do what we expect, we issue the SQL command alter table t_log_entry add version number(19, 0) null and run the code in Listing 11-20.

Example 11.20. Testing the Optimistic Locking

public class VersioningDemo {

    public static void main(String[] args) throws Exception {
        DaoDemoUtils.buildJndi();
        ApplicationContext ac =
            new ClassPathXmlApplicationContext("datasource-context-dao.xml",
           DaoDemo.class);
        LogEntryDao logEntryDao = (LogEntryDao) ac.getBean("logEntryDao");
        // save the original entry
        LogEntry le = new LogEntry();
        le.setName("Name");
        le.setDate(Calendar.getInstance().getTime());
        logEntryDao.save(le);

        // load two instances of the same LogEntry object
        LogEntry le1 = logEntryDao.getById(le.getId());
        LogEntry le2 = logEntryDao.getById(le.getId());
        // modify and save le1
        le1.setName("X");
        logEntryDao.save(le1);
        // now, let's try to modify and save le2.
        // remember, le2 represents the same row as le1
        le2.setName("Z");
        logEntryDao.save(le2);
    }
}

The code simulates the behavior of the application from Table 11-1. It loads two LogEntry objects that refer to the same row, makes a modification to one, and tries to modify the second one. However, once le1 is saved, le2 contains stale data, and the line in bold should not allow it to be persisted. When we run the application, we can see that that is exactly what happens:

* 1 logEntryDao.save(le1);
DEBUG [main] AbstractBatcher.log(401) | update t_log_entry set version=?, name_=?,
 date_=? where id=? and version=?
DEBUG [main] NullableType.nullSafeSet(133) | binding '1' to parameter: 1
DEBUG [main] NullableType.nullSafeSet(133) | binding 'X' to parameter: 2
DEBUG [main] NullableType.nullSafeSet(133) | binding '2007-10-12 10:35:06' to 
Testing the Optimistic Locking
parameter: 3
DEBUG [main] NullableType.nullSafeSet(133) | binding '1160' to parameter: 4
DEBUG [main] NullableType.nullSafeSet(133) | binding '0' to parameter: 5
...

* 2 logEntryDao.save(le2);
DEBUG [main] AbstractBatcher.log(401) | update t_log_entry set version=?, name_=?,
date_=? where id=? and version=?
DEBUG [main] NullableType.nullSafeSet(133) | binding '1' to parameter: 1
DEBUG [main] NullableType.nullSafeSet(133) | binding 'Z' to parameter: 2
DEBUG [main] NullableType.nullSafeSet(133) | binding '2007-10-12 10:35:06' to 
Testing the Optimistic Locking
parameter: 3 DEBUG [main] NullableType.nullSafeSet(133) | binding '1160' to parameter: 4 DEBUG [main] NullableType.nullSafeSet(133) | binding '0' to parameter: 5 ... ERROR [main] AbstractFlushingEventListener.performExecutions(301) | Could not synchronize database state with session org.hibernate.StaleObjectStateException: Row was updated or deleted by anothery
Testing the Optimistic Locking
transaction (or unsaved-value mapping was incorrect):
Testing the Optimistic Locking
[com.apress.prospring2.ch11.domain.LogEntry#1160] at org.hibernate.persister.entity.AbstractEntityPersister.
Testing the Optimistic Locking
check(AbstractEntityPersister.java:1765) ... at com.apress.prospring2.ch11.dataaccess.VersioningDemo.
Testing the Optimistic Locking
main(VersioningDemo.java:34)

The line marked * 1 in the output shows the point where we call logEntryDao.save(le1). The update operation includes both the primary key (id) and the version. The version gets increased on update, and Hibernate checks that exactly one row has been modified. We try to run logEntryDao.save(le2) at the line marked * 2. We see a similar update statement, but the version in the database is now set to 1. Therefore, the update affects zero rows, and at that point, Hibernate throws the StaleObjectStateException.

Object Equality

Now that we can safely prevent stale data updates, we need to consider another important limitation. When we persist collections in Hibernate, we are likely to use some kind of Collection. When we are modeling a 1-to-n relationship, we are most likely to use a Set. A Set is a collection that does not guarantee order of elements and does not allow duplicate elements; this is precisely what a 1-to-n relationship in a database represents. We will be adding our domain objects to the Sets, so we need to consider their equality. We can implement natural equality or database equality. Natural equality means that two objects are equal if they contain the same data from the application logic point of view. Consider the domain object in Listing 11-21.

Example 11.21. The Customer Domain Object

public class Customer {
    private Long id;
    private String title;
    private String firstName;
    private String lastName;
    private String address;

    // getters and setters
}

When should we consider two Customer objects equal? Is it when their IDs are equal or when their title, firstName, lastName, and address fields are equal, regardless of the value of id? When it comes to data persistence, we favor the first approach. The database does not care if it contains two rows with all other columns set to the same value as long as the rows' primary keys are unique and the rows do not violate any other constraints. If you need to enforce natural equality, consider using a unique index. With this in mind, we can take a look at the implementation of the equals and hashCode methods in our LogEntry class; see Listing 11-22.

Example 11.22. LogEntry equals and hashCode Implementation

public class LogEntry {
    // same as before

    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        //noinspection CastToConcreteClass
        final LogEntry that = (LogEntry) o;
        if (this.id != null ? !this.id.equals(that.id) : that.id != null)
            return false;
        //noinspection RedundantIfStatement
        if (this.version != null ?
            !this.version.equals(that.version) : that.version != null) return false;

        return true;
    }

    public int hashCode() {
        int result = super.hashCode();
        result = 31 * result + (this.version != null ? this.version.hashCode() : 0);
        result = 31 * result + (this.id != null ? this.id.hashCode() : 0);
        return result;
    }

}

We have implemented database equality with a twist: two objects are equal if they have the same id and the same version. There is one slight problem with this: it is likely that our domain will contain many other classes, not just LogEntry. We will therefore refactor the code, move the common implementation of equals and hashCode to a new class, and have LogEntry extend the new class. Listing 11-23 shows this refactoring.

Example 11.23. Refactored LogEntry Class

public abstract class AbstractIdentityVersionedObject<T> implements Serializable {
    protected Long version;
    protected T id;

    public AbstractIdentityVersionedObject() {

    }

    public AbstractIdentityVersionedObject(T id) {
        this.id = id;
    }

    protected final boolean idEquals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        //noinspection CastToConcreteClass
        final AbstractIdentityVersionedObject that =
           (AbstractIdentityVersionedObject) o;
if (this.id != null ? !this.id.equals(that.id) : that.id != null)
            return false;
        //noinspection RedundantIfStatement
        if (this.version != null ?
            !this.version.equals(that.version) : that.version != null) return false;

        return true;
    }

    protected final int idHashCode() {
        int result = super.hashCode();
        result = 31 * result + (this.version != null ? this.version.hashCode() : 0);
        result = 31 * result + (this.id != null ? this.id.hashCode() : 0);
        return result;
    }

    @Override
    public boolean equals(final Object o) {
        return idEquals(o);
    }

    @Override
    public int hashCode() {
        return idHashCode();
    }

    public T getId() {
        return id;
    }

    public void setId(final T id) {
        this.id = id;
    }

    public Long getVersion() {
        return version;
    }

    public void setVersion(final Long version) {
        this.version = version;
    }
}

public class LogEntry extends AbstractIdentityVersionedObject<Long> {
    private String name;
    private Date date;

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public String getName() {
        return name;
}

    public void setName(String name) {
        this.name = name;
    }

}

The AbstractIdentityVersionedObject class contains the id and version properties (and the id is a generic type, which allows us to use any type for the primary key value, even a compound value!). It also implements database equality. The LogEntry object simply extends AbstractIdentityVersionedObject<Long> and adds only the columns it declares. This makes our domain code much more readable, and we do not have to worry about forgetting to implement the equals and hashCode methods in a new domain class. However, even though this code breaks the usual contract for the equals and hashCode methods, it is useful for objects that you expect to persist in a database. It assumes that an id with the value null represents an object that you have not yet inserted. Additionally, it assumes that you will not explicitly modify the values of the id and version properties.

This forms a good starting point for our discussion of lazy loading, but before we get to that, we need to take a look at one other crucial enterprise requirement.

Transactional Behavior

The next area we need to look at is the transactional behavior of Hibernate support. Spring uses the PlatformTransactionManager interface in its transactional support; for use with Hibernate, we have the HibernateTransactionManager implementation. This bean needs the SessionFactory reference; Listing 11-24 shows its configuration.

Example 11.24. Transactional Support in Hibernate

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    ....>
    <bean id="hibernateDaoSupport" abstract="true"
        class="org.springframework.orm.hibernate3.support.HibernateDaoSupport">
        <property name="sessionFactory" ref="hibernateSessionFactory"/>
    </bean>

    <bean id="logEntryDao"
        class="com.apress.prospring2.ch11.dataaccess.hibernate.HibernateLogEntryDao"
       parent="hibernateDaoSupport"/>

    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="hibernateSessionFactory"/>
    </bean>

</beans>

We now have the transactionManager bean, which allows us to control Hibernate transactions using the common Spring transaction framework. However, declaring only the transactionManager bean does not mean we get transactional behavior, as code in Listing 11-25 shows.

Example 11.25. Still Nontransactional Behavior

public class HibernateLogEntryDaoTx1Demo {

    private static LogEntry save(LogEntryDao dao, String name) {
        LogEntry le = new LogEntry();
        le.setName(name);
        le.setDate(Calendar.getInstance().getTime());
        dao.save(le);
        return le;
    }

    public static void main(String[] args) throws Exception {
        DaoDemoUtils.buildJndi();
        ApplicationContext ac =
            new ClassPathXmlApplicationContext("datasource-context-dao.xml",
            DaoDemo.class);
        LogEntryDao logEntryDao = (LogEntryDao) ac.getBean("logEntryDao");

        try {
            save(logEntryDao, "Hello, this works");
            save(logEntryDao, null);
        } catch (Exception e) {
            // we don't want anything here, but Alas!
            System.out.println(logEntryDao.getAll());
        }
    }

}

The first Hibernate operation in the save method succeeds, but the second one fails. Because we have not defined the boundary of the transaction, we will find one row in the catch block. We need to rethink our transaction strategy at this point: we could make the HibernateLogEntryDao.save(LogEntry) method transactional, but that would not give us any benefit. In fact, making a single DAO method transactional is not a good practice—it is up to the service layer to define transactional boundaries. The DAO layer should be a relatively simple translator between domain objects and some data store.

Let's create a simple service interface and its implementation. The service implementation will use the log, and we will ensure that the service method is transactional. Figure 11-2 shows a diagram of the service we will create.

UML diagram of the service

Figure 11.2. UML diagram of the service

Figure 11-2 shows that the code in the SampleServiceDemo example does not use the LoggingSampleService implementation of the SampleService; instead, it uses a dynamically generated proxy. This proxy, in turn, uses the HibernateTransactionManager to maintain the transactional behavior of the work() method. The LogEntryDao and its HibernateLogEntryDao implementation are shown for completeness. For further discussion on AOP and transactions, see Chapters 5, 6, and 16.

Listing 11-26 shows the service interface, the implementation, and the Spring context file.

Example 11.26. The Service Layer and Its Configuration

public interface SampleService {

    void work();

}

public class LoggingSampleService implements SampleService {
    private LogEntryDao logEntryDao;

    private void log(String message) {
        LogEntry entry = new LogEntry();
        entry.setDate(Calendar.getInstance().getTime());
        entry.setName(message);
        this.logEntryDao.save(entry);
    }

    public void work() {
        log("Begin.");
        log("Processing...");

        if (System.currentTimeMillis() % 2 == 0) log(null);
        log("Done.");
    }

    public void setLogEntryDao(LogEntryDao logEntryDao) {
        this.logEntryDao = logEntryDao;
    }
}

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       ...>

    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="work"/>
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:pointcut id="sampleServiceOperation"
            expression="execution(* com.apress.prospring2.ch11.service.
The Service Layer and Its Configuration
SampleService.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="sampleServiceOperation"/> </aop:config> <bean id="sampleService" class="com.apress.prospring2.ch11.service.LoggingSampleService"> <property name="logEntryDao" ref="logEntryDao"/> </bean> </beans>

Looking at the code of the LoggingSampleService, we can see that we deliberately try to save the LogEntry object with a null name, which causes an exception. The advice catches the exception and rolls back the transaction and, therefore, the first two log entries. If the timing is favorable, we do not insert the LogEntry object with null name and insert only the final LogEntry object. So, for a successful call, we should get three rows in the t_log_entry table, and we should get no rows for a failed call. Let's verify this in code (see Listing 11-27) as well as in SQL*Plus client.

Example 11.27. Using the SampleService

public class SampleServiceDemo {

    public static void main(String[] args) throws Exception {
        DaoDemoUtils.buildJndi();
        ApplicationContext ac = new ClassPathXmlApplicationContext(new String[] {
            "classpath*:/com/apress/prospring2/ch11/dataaccess/
Using the SampleService
datasource-context-tx.xml", "classpath*:/com/apress/prospring2/ch11/service/*-context.xml" }); SampleService sampleService = (SampleService)ac.getBean("sampleService"); LogEntryDao logEntryDao = (LogEntryDao) ac.getBean("logEntryDao"); int successCount = 0; int failureCount = 0; int before = logEntryDao.getAll().size(); for (int i = 0; i < 10; i++) { if (tryWork(sampleService)) { successCount++; } else { failureCount++; } } System.out.println("Inserted " + (logEntryDao.getAll().size() - before) + ", for " + successCount + " successes and " + failureCount + " failures"); } private static boolean tryWork(SampleService sampleService) { try { sampleService.work(); return true; } catch (Exception e) { // do nothing (BAD in production) } return false; } }

The result of running this class varies every time, but the numbers of inserted objects match the expected results:

Inserted 15, for 5 successes and 5 failures

We can also verify this in SQL*Plus using the code shown in Listing 11-28.

Example 11.28. Further Verification of the Transaction Code

$ sqlplus PROSPRING/x*****6@//oracle.devcake.co.uk/INTL
SQL> truncate table t_log_entry
  2  /

Table truncated.

SQL> exit

$ java -cp $CLASSPATH com.apress.prospring2.ch11.service.SampleServiceDemo
... output truncated ...
Inserted 15, for 5 successes and 5 failures

$ sqlplus PROSPRING/x*****6@//oracle.devcake.co.uk/INTL
SQL> select count(0) from t_log_entry
  2  /

  COUNT(0)
----------
        15

It worked! We have run the sample application and verified that for five successful service calls we have 15 rows in the t_log_entry table, exactly as we expected.

Lazy Loading

This section is just as important as the transactional behavior one; in fact, these two areas are closely connected. The principle of lazy loading is simple: fetch data only when it is needed. It means more trips to the database, but the throughput is better because Hibernate only fetches data the application needs. Lazy loading generally applies to associated collections; imagine an Invoice object with a Set of InvoiceLine objects; each InvoiceLine will have a Set of Discount objects.

Let's consider an application that loads all invoices in a particular date range, for example. Imagine that the number of matching records is 10,000; that means that we will get 10,000 Invoice objects. On average, we can expect an Invoice to have five lines; that means 50,000 InvoiceLine objects. In addition to that, let's say that every other line has a discount applied to it. That means 25,000 Discount objects. In total, a single Hibernate operation will create 85,000 objects! By using lazy loading, we can reduce the number of objects created to just 10,000; we instruct Hibernate to fetch the InvoiceLine objects only when we need them. Hibernate does this by instrumenting the fetched objects' code and using its own implementations of the Collection interfaces.

In theory, it should work quite nicely; let's write code that implements the model from Figure 11-3.

Invoice domain model

Figure 11.3. Invoice domain model

The domain objects are not difficult to implement; the only thing we want to show in Listing 11-29 are the Invoice.addInvoiceLine and InvoiceLine.addDiscount methods.

Example 11.29. The addInvoiceLine and addDiscount Methods

public class Invoice extends AbstractIdentityVersionedObject<Long> {
    public void addInvoiceLine(InvoiceLine invoiceLine) {
        invoiceLine.setInvoice(this);
        this.invoiceLines.add(invoiceLine);
    }
}

public class InvoiceLine extends AbstractIdentityVersionedObject<Long> {
    public void addDiscount(Discount discount) {
        discount.setInvoiceLine(this);
        this.discounts.add(discount);
    }
}

This code clearly shows that the addInvoiceLine and addDiscount methods establish a bidirectional relationship between the object being added and its container. This is important for Hibernate, and it makes our code clearer; it is better and much less error-prone than the code in Listing 11-30.

Example 11.30. Avoiding the Error-Prone addInvoiceLine and addDiscount Methods

Invoice invoice = new Invoice();
InvoiceLine il = new InvoiceLine();
invoice.getInvoiceLines().add(il);    // 1

Discount discount = new Discount();
discount.setInvoiceLine(invoiceLine);    //2
invoiceLine.getDiscounts().add(discount);    //3

The line marked //1 represents a bug: the il instance we've added to the invoice object does not contain a reference to the invoice object. Lines //2 and //3 are simply clumsy code. Even though the tables for the Invoice domain model are straightforward, Listing 11-31 shows the SQL code to create them.

Example 11.31. SQL Code to Create the Invoice Domain Model Tables

create table t_supplier (
    id number(19, 0) not null,
    version number(19, 0) null,
    name varchar2(200) not null,
    constraint pk_supplier primary key (id)
)
/
create sequence s_supplier_id start with 10000
/

create table t_invoice (
    id number(19, 0) not null,
    version number(19, 0) null,
    invoice_date date not null,
    delivery_date date not null,
    supplier number(19, 0) not null,
    constraint pk_invoice primary key (id),
    constraint fk_i_supplier foreign key (supplier) references t_supplier(id)
)
/
create sequence s_invoice_id start with 10000
/

create table t_invoice_line (
    id number(19, 0) not null,
    version number(19, 0) null,
    invoice number(19, 0) not null,
    price number(20, 4) not null,
    vat number(20, 4) not null,
    product_code varchar2(50) not null,
    constraint pk_invoice_line primary key (id),
    constraint fk_il_invoice foreign key (invoice) references t_invoice(id)
)
/
create sequence s_invoice_line_id start with 10000
/

create table t_discount (
    id number(19, 0) not null,
    version number(19, 0) null,
    invoice_line number(19, 0) not null,
    type_ varchar2(50) not null,
    amount number(20, 4) not null,
    constraint pk_discount primary key (id),
    constraint fk_d_invoice_line foreign key (invoice_line)
       references t_invoice_line(id)
)
/
create sequence s_discount_id start with 10000
/

Because we will implement the DAOs in Hibernate, we have to create the Hibernate mapping files for our new domain objects. It is usual practice to keep one mapping file for each domain object; Listing 11-32, therefore, shows four mapping files (for the Supplier, Invoice, InvoiceLine, and Discount objects).

Example 11.32. Mapping Files for the Newly Created Domain Objects

<!—Supplier.hbm.xml -->
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping default-lazy="true">

    <class name="com.apress.prospring2.ch11.domain.Supplier" table="t_supplier">
        <id name="id" type="long" unsaved-value="null">
            <generator class="sequence">
                <param name="sequence">s_supplier_id</param>
            </generator>
        </id>
        <version name="version" column="version" unsaved-value="null" type="long" />
        <property name="name" column="name" not-null="true" />
    </class>

</hibernate-mapping>

<!—Invoice.hbm.xml -->
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping default-lazy="true">

    <class name="com.apress.prospring2.ch11.domain.Invoice" table="t_invoice">
        <id name="id" type="long" unsaved-value="null">
            <generator class="sequence">
                <param name="sequence">s_invoice_id</param>
            </generator>
</id>
        <version name="version" column="version" unsaved-value="null" type="long" />
        <property name="deliveryDate" column="delivery_date" not-null="true" />
        <property name="invoiceDate" column="invoice_date" not-null="true" />
        <many-to-one name="supplier" not-null="true"
             class="com.apress.prospring2.ch11.domain.Supplier"/>
        <set name="lines" cascade="all" inverse="true">
            <key column="invoice" not-null="true"/>
            <one-to-many class="com.apress.prospring2.ch11.domain.InvoiceLine"/>
        </set>
    </class>

</hibernate-mapping>

<!—InvoiceLine.hbm.xml -->
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping default-lazy="true">

    <class name="com.apress.prospring2.ch11.domain.InvoiceLine"
        table="t_invoice_line">
        <id name="id" type="long" unsaved-value="null">
            <generator class="sequence">
                <param name="sequence">s_invoice_line_id</param>
            </generator>
        </id>
        <version name="version" column="version" unsaved-value="null" type="long" />
        <property name="price" column="price" not-null="true" />
        <property name="productCode" column="product_code" not-null="true" />
        <property name="vat" column="vat" not-null="true" />
        <many-to-one name="invoice"
            class="com.apress.prospring2.ch11.domain.Invoice"
            not-null="true"/>
        <set name="discounts" inverse="true" cascade="all">
            <key column="invoice_line" not-null="true"/>
            <one-to-many class="com.apress.prospring2.ch11.domain.Discount"/>
        </set>
    </class>

</hibernate-mapping>

<!—Discount.hbm.xml -->
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping default-lazy="true">

    <class name="com.apress.prospring2.ch11.domain.Discount" table="t_discount">
        <id name="id" type="long" unsaved-value="null">
            <generator class="sequence">
                <param name="sequence">s_discount_id</param>
            </generator>
</id>
        <version name="version" column="version" unsaved-value="null" type="long" />
        <property name="amount" column="amount" not-null="true" />
        <property name="type" column="type_" not-null="true" />
        <many-to-one name="invoiceLine" column="invoice_line"
            class="com.apress.prospring2.ch11.domain.InvoiceLine" not-null="true" />
    </class>

</hibernate-mapping>

The listing shows the simplest mapping first: Supplier has no references to any other objects. The mapping for the Invoice object is quite complex: it shows a many-to-one mapping to the Supplier object and the lines set, which represents one-to-many mapping to the InvoiceLine objects. The InvoiceLine and Discount mappings follow the same pattern we have in the mapping for the Invoice object. Also notice that the default-lazy attribute of the hibernate-mapping element is set to true.

Now that we have the domain objects with convenience methods to set the dependencies and their Hibernate mappings, we need to create the appropriate DAOs and services. Figure 11-4 shows the UML diagram of the code we will write.

UML diagram of the DAOs and services

Figure 11.4. UML diagram of the DAOs and services

Finally, let's modify the dataaccess-context-tx.xml and create the services-context.xml Spring configuration files to wire up the new DAOs and services. We will use the code in Listing 11-33 in a sample application.

Example 11.33. The Changed dataaccess-context-tx.xml and New services-context.xml Files

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    ...>

    <bean id="invoiceDao"
        class="com.apress.prospring2.ch11.dataaccess.hibernate.HibernateInvoiceDao"
       parent="hibernateDaoSupport"/>
    <bean id="supplierDao"
       class="com.apress.prospring2.ch11.dataaccess.hibernate.HibernateSupplierDao"
       parent="hibernateDaoSupport"/>

</beans>

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    ....>

    <bean id="invoiceService"
        class="com.apress.prospring2.ch11.service.DefaultInvoiceService">
        <property name="invoiceDao" ref="invoiceDao"/>
    </bean>
    <bean id="supplierService"
        class="com.apress.prospring2.ch11.service.DefaultSupplierService">
        <property name="supplierDao" ref="supplierDao"/>
    </bean>
</beans>

We have the Spring context files, and we are now ready to try to run the sample application shown in Listing 11-34. The sample application will only verify that the Spring configuration is correct; it does not actually do any database work yet.

Example 11.34. Sample Application

public class InvoiceServiceDemo {
    private SupplierService supplierService;
    private InvoiceService invoiceService;

    private void run() throws Exception {
        DaoDemoUtils.buildJndi();
        ApplicationContext ac = new ClassPathXmlApplicationContext(new String[] {
            "classpath*:/com/apress/prospring2/ch11/dataaccess/
Sample Application
datasource-context-tx.xml", "classpath*:/com/apress/prospring2/ch11/service/*-context.xml" }); this.invoiceService = (InvoiceService)ac.getBean("invoiceService"); this.supplierService = (SupplierService)ac.getBean("supplierService"); findAllInvoices(); }
private void findAllInvoices() {
    }

    public static void main(String[] args) throws Exception {
        new InvoiceServiceDemo().run();
    }

}

We will work on the findAllInvoices() method to find out what Hibernate does behind the scenes. We will begin by creating some sample data by running the code in Listing 11-35.

Example 11.35. Stored Procedure to Create the Sample Data

CREATE OR REPLACE PROCEDURE "PROSPRING"."CREATE_SAMPLE_DATA"
is
    i number;
    j number;
    l number;
begin
    dbms_output.put_line('begin'),
    for i in 1 .. 50 loop
        insert into t_supplier (id, version, name) values (
           s_supplier_id.nextval, 1, 'Supplier '||i);
        for j in 1 .. 100 loop
            insert into t_invoice (id, version, invoice_date,
               delivery_date, supplier)
            values (s_invoice_id.nextval, 1, sysdate,
               sysdate, s_supplier_id.currval);
            for l in 1 .. 5 loop
                insert into t_invoice_line (id, version, invoice, price,
                    vat, product_code)
                values (s_invoice_line_id.nextval, 1, s_invoice_id.currval,
                   dbms_random.value(1, 1000),
                   dbms_random.value(1, 100), 'Product '||l);
            end loop;
        end loop;
    end loop;
end;

This code simply inserts 50 suppliers; each invoice has 100 invoices; and each invoice, five lines. This should give us a representative data set for our experiments. Let's modify the findAllInvoices() method to actually go to the database and return all Invoice objects (see Listing 11-36).

Example 11.36. Finding All Invoices

private void findAllInvoices() {
        List<Invoice> invoices = this.invoiceService.findAll();
        System.out.println(invoices.size());
    }

We expect this code to print 5,000 lines, and running it indeed prints out 5,000. This is perhaps the most inefficient way to count the rows in the t_invoice table. The benefit, however, is that we have not loaded 25,000 InvoiceLine objects! Hibernate runs the SQL statement only on the t_invoice table:

select invoice0_.id as id1_, invoice0_.version as version1_,
invoice0_.delivery_date as delivery3_1_,
invoice0_.invoice_date as invoice4_1_,
invoice0_.supplier as supplier1_ from t_invoice invoice0_

Let's write a method that gets an invoice by its id, the InvoiceService.findById(Long) method, and examine the structure of the Invoice object we get back. Figure 11-5 shows the debugger view of the returned Invoice object.

The debugger view of the Invoice object

Figure 11.5. The debugger view of the Invoice object

The debugger shows that we have successfully loaded the Invoice object, but it also shows that when we try to access the supplier and lines properties, we get the LazyInitializationException. The reason is that the Session that has loaded the Invoice object has ended. Remember that the HibernateTemplate ensures that the session closes correctly at the end of the work performed by the callback. This is where the Hibernate transactional support comes into play. Whenever application code requests Spring Hibernate support to get a session, Spring registers the Session as a transaction synchronization object. Therefore, code that executes in a single transaction always operates under the same Session, even if it uses more than one HibernateTemplate call. You must, however, be careful with how you deal with the transactional nature of your beans. Take a look at Table 11-2, which shows an application that declared its service layer and DAO layer to be transactional and, to make matters even worse, it has declared that operations on both the DAO and the service beans require a new transaction.

Table 11.2. Nested Transactions

Service Call

DAO Call

Hibernate Session

Invoice i = findById(1L)

 

Session 1

 

Invoice i = getById(1L)

Session 2

 

i.getSupplier()

Session 2

i.getSupplier()

 

Session 1

Because we have configured the service bean with the REQUIRES_NEW transaction propagation, the service call starts a new transaction and gets Hibernate session one. It then proceeds to call the DAO's getId method. Because we have configured the DAO bean with the REQUIRES_NEW transaction propagation for every call, it gets Hibernate session two. Using session two, the DAO bean loads the Invoice object: i.getSupplier() will work, because i was loaded in session two. However, when the DAO bean returns back to the service layer, the call to i.getSupplier in the service call will fail. Even though we still have a Hibernate Session open, it is not the same Session that loaded the original Invoice object. Do not worry if you do not fully understand the transaction propagation, we cover this in more detail in Chapter 16.

You can argue that you would not make such a mistake, but imagine that the service call is actually a web tier call and that the DAO call is the service call. Suddenly, you are dealing with the same situation. The problem becomes even worse, because all your integration and DAO tests will work. The solution we take is to perform an explicit eager fetch when we need it in the DAO or to access the objects we need in an active transaction in the service layer. An eager fetch is the opposite of lazy fetch: we instruct Hibernate to select the associations in a single select statement. The service call should always return all data that the nontransactional presentation tier needs.

Explicit Eager Fetching

Let's take a look at the first way to prevent lazy loading exceptions. If we expect that the subsequent layers will require the entire object graph, we can instruct Hibernate to eagerly fetch all objects. This is frequently useful when implementing DAO calls—typically, the getById calls—that return a single result, or a very small number of results. Listing 11-37 shows the modification to the HibernateInvoiceDao's getById() method.

Example 11.37. Eager getById Method

public class HibernateInvoiceDao extends HibernateDaoSupport implements InvoiceDao {

    // other methods omitted

    public Invoice getById(Long id) {
        return (Invoice) DataAccessUtils.uniqueResult(
                getHibernateTemplate().find("from Invoice i inner join fetch" +
                        "i.supplier inner join fetch i.lines il " +
                        "left outer join fetch il.discounts where i.id = ?", id)
        );
    }
}

The eager fetch looks just like a SQL statement with explicit inner and left-outer joins. Figure 11-6 shows the Invoice object we get back in the debugger view.

Debugger view of the eagerly loaded Invoice object

Figure 11.6. Debugger view of the eagerly loaded Invoice object

This is exactly what we need: we have a DAO that returns an eagerly fetched object only when we need it.

Other Lazy Loading Considerations

Even if your code is running in a clean service layer transaction, you should consider the performance hit caused by using lazy loading. Consider the code in Listing 11-38, where the InvoiceDao.getByIdLazy(Long) method returns the Invoice object without eager fetching.

Example 11.38. Very Slow Performance of Lazy Loading

public class Invoice extends AbstractIdentityVersionedObject<Long> {
...
    public BigDecimal getLinesTotalPrice() {
        BigDecimal total = new BigDecimal(0);
        for (InvoiceLine line : this.lines) {
            total = total.add(line.getPrice());
        }
        return total;
    }
...
}

public class DefaultInvoiceService implements InvoiceService {

    // rest of the code omitted
    public void recalculateDiscounts(Long id) {
        Invoice invoice = this.invoiceDao.getByIdLazy(id);
        BigDecimal total = invoice.getLinesTotalPrice();
        if (total.compareTo(BigDecimal.TEN) > 0) {
            // do something special
        }
    }
}

The seemingly innocent call to invoice.getLinesTotalPrice() forces Hibernate to fetch all InvoiceLines for this invoice.

Some applications use the Open Session in View (anti)pattern. The reasoning behind this pattern is that the application keeps the Session open while it displays a view (a JSP page, for example). This simplifies the decision between lazy and eager fetches: the session is open in the view, and it can fetch any lazy association. We sometimes call this an antipattern because it may lead to inconsistent data views. Imagine you display the Invoice object and show only a count of its invoice lines. You then rely on lazy loading to get the invoice lines. In the meantime, the system (or other users) might have updated the invoice lines of the displayed invoice, thus making the actually lazily loaded InvoiceLine objects inconsistent with the count.

Dealing with Large Data Sets

Most applications need to be able to handle very large data sets; the code you have seen so far works quite nicely with hundreds of records in the database, but when we start to approach larger record set sizes, our application may crash with a java.lang.OutOfMemoryException—we may simply select too much data. Worse, a web application would most likely discard the vast majority of the result set to display only one page of results to the users. We need to make the DAO layer aware of paging. Hibernate supports paging in its Query object; it provides the setFirstResult(int) and setMaxResults(int) methods. Our first attempt at implementing paging might look like the code in Listing 11-39.

Example 11.39. The First Attempt at Implementing Paging

public interface InvoiceService {
    List<Invoice> search(int firstResult, int pageSize);
    ...
}

public class DefaultInvoiceService implements InvoiceService {
    private InvoiceDao invoiceDao;

    public List<Invoice> search(int firstResult, int pageSize) {
        return this.invoiceDao.search(firstResult, pageSize);
    }
    ....
}

public interface InvoiceDao {
    ...
    List<Invoice> search(int firstResult, int pageSize);

}

public class HibernateInvoiceDao extends HibernateDaoSupport implements InvoiceDao {
    ...
    @SuppressWarnings({"unchecked"})
    public List<Invoice> search(final int firstResult, final int pageSize) {
        return (List<Invoice>) getHibernateTemplate().execute(
            new HibernateCallback() {
                public Object doInHibernate(Session session)
                    throws HibernateException, SQLException {
                    Query query = session.createQuery("from Invoice");
                    query.setFirstResult(firstResult);
                    query.setMaxResults(pageSize);
return query.list();
                }
            });
    }
}

We can now use the search method and give it the first result and the maximum number of results to be returned. This certainly works, but the downside is that we can't return the maximum number of results. We can, therefore, show a link to only the next page; we can't show the total number of pages (or results). One solution would be to implement a method that would return the total number of rows in the Invoices table. What if we made our search more complicated, perhaps by introducing conditions that can result in selecting only a subset of all rows? We would have to implement a matching counting method for every search method. In addition to this, the code that uses our service would have to explicitly call two methods. There is a better solution; we illustrate it with the UML diagram in Figure 11-7.

UML diagram of paging support

Figure 11.7. UML diagram of paging support

The search method performs all necessary search operations, taking the SearchArgumentSupport subclass as its argument and returning SearchResultSupport subclass. The returned SearchResultSupport contains the page of fetched objects as well as the total number of results. In addition to containing the results, it implements Iterable<T>. That means that we can use the ResultSupport subclass in a JSP's c:forEach loop (see Listing 11-40).

Example 11.40. JSP Page That Lists the Invoice Objects

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html xmlns="http://www.w3.org/1999/xhtml"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://www.w3.org/MarkUp/SCHEMA/xhtml11.xsd"
     xml:lang="en" >
<head>
    <title>Result</title>
</head>
<body>
<h2>Invoices found</h2>
<c:forEach items="${invoices}" var="invoice">
    ${invoice.invoiceDate} <!-- etc -->
</c:forEach>
</body>
</html>

As you can see, no additional effort was needed in our JSP views; in fact, if we change the returned type from a Collection<T> to the ResultSupport<T> subclass later on in the development of a web application, we do not have to change the views in any way.

Handling Large Objects

It is possible to use Hibernate to fetch objects from tables that use large objects (LOBs). There are two types of LOBs: character large binary objects (CLOBs) and binary large objects (BLOBs). CLOBs are usually mapped to String, while BLOBs are usually mapped to byte[]. Because the standard JDBC infrastructure does not deal with large binary objects, we must use an appropriate LobHandler implementation for the database we are using. Take a look at Listing 11-41, which shows a table with BLOB and CLOB fields.

Example 11.41. Table with Large Object Columns

create table t_lob_test (
    id number(19, 0) not null,
    version number(19, 0) null,
    text_content clob not null,
    binary_content blob not null,
    mime_type varchar2(200) not null,
    constraint pk_lob_test primary key (id)
)
/
create sequence s_lob_test_id start with 10000
/

This class is very simple, so its domain object is going to be very simple as well (see Listing 11-42).

Example 11.42. Domain Object for the t_lob_test Table

public class LobTest extends AbstractIdentityVersionedObject<Long> {
    private String textContent;
    private byte[] binaryContent;
    private String mimeType;
public String getTextContent() {
        return textContent;
    }

    public void setTextContent(String textContent) {
        this.textContent = textContent;
    }

    public byte[] getBinaryContent() {
        return binaryContent;
    }

    public void setBinaryContent(byte[] binaryContent) {
        this.binaryContent = binaryContent;
    }

    public String getMimeType() {
        return mimeType;
    }

    public void setMimeType(String mimeType) {
        this.mimeType = mimeType;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("LobTest { id=").append(this.id).append(", ");
        sb.append("textContent=").append(this.textContent).append(", ");
        sb.append("binaryContent=");
        for (int i = 0; i < this.binaryContent.length && i < 50; i++) {
            sb.append(String.format("%x", (int)this.binaryContent[i]));
        }
        sb.append("}");

        return sb.toString();
    }
}

The only addition to the usual getters and setters is the toString() method, but that is really only for our convenience. Next, we need to create the Hibernate mapping file for the LobTest domain object. Listing 11-43 shows that we use subclasses of the AbstractLobType: BlobByteArrayType and ClobStringType.

Example 11.43. Mapping File for the LobTest Domain Object

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping default-lazy="true">

    <class name="com.apress.prospring2.ch11.domain.LobTest" table="t_lob_test">
        <id name="id" type="long" unsaved-value="null">
            <generator class="sequence">
                <param name="sequence">s_lob_test_id</param>
</generator>
        </id>
        <version name="version" column="version" unsaved-value="null" type="long" />
        <property name="binaryContent" column="binary_content" not-null="true"
              type="org.springframework.orm.hibernate3.support.BlobByteArrayType" />
        <property name="textContent" column="text_content" not-null="true"
                type="org.springframework.orm.hibernate3.support.ClobStringType"/>
        <property name="mimeType" column="mime_type" not-null="true" />
    </class>
</hibernate-mapping>

The final complex part of using this domain object is setting up the Hibernate mapping and Spring configuration. We need to tell Hibernate how our database (Oracle 10g) handles LOBs. To do that, we create an instance of the LobHandler implementation and reference it in the HibernateSessionFactoryBean; Listing 11-44 shows how to do this.

Example 11.44. Hibernate Configuration for LOB Handling

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    ...>

    <bean id="nativeJdbcExtractor"
        class="org.springframework.jdbc.support.nativejdbc.
Hibernate Configuration for LOB Handling
SimpleNativeJdbcExtractor"/> <bean id="lobHandler" class="org.springframework.jdbc.support.lob.OracleLobHandler"> <property name="nativeJdbcExtractor" ref="nativeJdbcExtractor"/> </bean> <bean id="hibernateSessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="lobHandler" ref="lobHandler"/> <property name="mappingLocations"> <list> <value>classpath*:/com/apress/prospring2/ch11/dataaccess/
Hibernate Configuration for LOB Handling
hibernate/*.hbm.xml</value> </list> </property> </bean> ... <bean id="lobTestDao" class="com.apress.prospring2.ch11.dataaccess.hibernate.HibernateLobTestDao" parent="hibernateDaoSupport"/> </beans>

The final difficulty we need to deal with is that the LOB support requires an active Spring transaction or a JTA transaction synchronization. Therefore, we must create a service layer interface and its implementation (remember, we do not ever want to make our DAOs transactional!). Listing 11-45 shows the service interface and its implementation.

Example 11.45. Service Code for the LOB Demonstration

public interface LobTestService {

    void save(LobTest lobTest);

    LobTest findById(Long id);

}

public class DefaultLobTestService implements LobTestService {
    private LobTestDao lobTestDao;

    public void save(LobTest lobTest) {
        this.lobTestDao.save(lobTest);
    }

    public LobTest findById(Long id) {
        return this.lobTestDao.getById(id);
    }

    public void setLobTestDao(LobTestDao lobTestDao) {
        this.lobTestDao = lobTestDao;
    }
}

The Spring configuration for the service layer simply defines the LobTestService bean with its dependencies; Listing 11-46 shows only the sample application that uses the service implementation and verifies that the LOB handling works as expected.

Example 11.46. LOB Sample Application

public class LobTestServiceDemo {

    private void run() throws Exception {
        DaoDemoUtils.buildJndi();
        ApplicationContext ac = new ClassPathXmlApplicationContext(new String[] {
            "classpath*:/com/apress/prospring2/ch11/dataaccess/
LOB Sample Application
datasource-context-tx.xml", "classpath*:/com/apress/prospring2/ch11/service/*-context.xml" }); LobTestService lobTestService = (LobTestService)ac.getBean("lobTestService"); LobTest lobTest = new LobTest(); lobTest.setTextContent("Hello, world"); lobTest.setBinaryContent("Hello, world".getBytes()); lobTest.setMimeType("text/plain"); lobTestService.save(lobTest); LobTest lobTest2 = lobTestService.findById(lobTest.getId()); System.out.println(lobTest2); } public static void main(String[] args) throws Exception { new LobTestServiceDemo().run(); new BufferedReader(new InputStreamReader(System.in)).readLine(); } }

Running this application produces output that verifies that the LobTest object gets inserted and fetched without any problems:

DEBUG [main] ConnectionManager.cleanup(380) | performing cleanup
DEBUG [main] ConnectionManager.closeConnection(441) | releasing JDBC connection
[ (open PreparedStatements: 0, globally: 0) (open ResultSets: 0, globally: 0)]
DEBUG [main] JDBCContext.afterTransactionCompletion(215) |
after transa version 3.2.5ga ction completion
DEBUG [main] ConnectionManager.afterTransaction(302) | transaction completed on
session with on_close connection release mode; be sure to close the session
to release JDBC resources!
DEBUG [main] SessionImpl.afterTransactionCompletion(422) |
after transaction completion
LobTest { id=10004, textContent=Hello, world,
binaryContent=48656c6c6f2c20776f726c64}

The only difficulty with this approach is that the LOB columns usually contain fairly large amounts of data, and unfortunately, setting lazy="true" on the column does not work. Using the AbstractLobType subclasses, Hibernate will always fetch the entire contents of the column. This is why, in our large applications, we tend to use Spring JDBC to handle the LOBs and Hibernate to handle all other data access code.

Combining Hibernate with Other DAO Code

You can use Hibernate in almost every application; it can deal with complex object structures, and its lazy loading will decrease the performance of your application only slightly. There is one situation in which we found Hibernate more difficult to use efficiently: if you are dealing with tables with large objects and the large objects are significant in size (10kB and above). In that case, it is best to combine Hibernate with Spring-managed JDBC code. Even though Hibernate supports column-level lazy loading, support for this is not always possible for all column types and all databases.

There is nothing stopping you from implementing part of your DAO in Hibernate and part in JDBC. Because you still need to declare the DataSource bean, you can use it in your Spring JDBC DAOs. In addition to this, both the Hibernate and JDBC DAO implementations participate in the same transaction. This is very useful but can lead to a problem when you insert a row with Hibernate, then use JDBC, and expect to see the inserted row. The HibernateTransactionManager typically does not flush the Hibernate Session until the transaction commits; therefore, the row we have inserted using session.saveOrUpdate() will not appear in the database. This will cause the JDBC operation that expects to see the row to fail. The solution is to call session.flush before performing JDBC DAO operations that depend on rows inserted as a result of the Hibernate session operations.

Summary

In this chapter, you have learned what Hibernate does and how to use it in Spring applications. We started by looking at the simplest code and quickly moved on to more complicated examples. Now, you know how to prevent stale data updates, and we paid special attention to creating efficient data access code. Lazy loading plays an important role in this, and to properly use lazy loading, you must understand how Hibernate and Spring cooperate in handling transactions.

Finally, we discussed how we can use Hibernate to store large object data in a database, even though, as you saw, it may not be the most appropriate solution. We will end the chapter by saying that the best combination is Hibernate with Spring-managed JDBC; this way, you can implement critical pieces of data access code in a very low-level way using JDBC and use Hibernate's incredible convenience and power for all other DAO code.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.128.78.30