+ All Categories
Home > Documents > Pro Spring 2.5

Pro Spring 2.5

Date post: 29-Mar-2016
Category:
Upload: mitch-ismaiel
View: 227 times
Download: 0 times
Share this document with a friend
Description:
My Spring Book
43
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. Listing 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; } 399 CHAPTER 11
Transcript
Page 1: Pro Spring 2.5

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 PrimerBefore 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.

Listing 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;

}

399

C H A P T E R 1 1

Page 2: Pro Spring 2.5

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.

Listing 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.

PackagingThere 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

CHAPTER 11 � HIBERNATE SUPPORT400

Page 3: Pro Spring 2.5

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 recom-

mend that you use Hibernate 3, which means that you can carry on using the default spring.jarpackage.

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 SupportThe 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 con-

figuring 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 class-

path. 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 imple-

mented 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.

Listing 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.xsdhttp://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>

CHAPTER 11 � HIBERNATE SUPPORT 401

Page 4: Pro Spring 2.5

<value>classpath*:/com/apress/prospring2/ch11/dataaccess/�

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 explana-

tion 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 ver-

ify that everything works, we will run the code from Listing 11-4.

Listing 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; oth-

erwise, we will get dreaded ClassNotFound exceptions:

CHAPTER 11 � HIBERNATE SUPPORT402

Page 5: Pro Spring 2.5

• 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 factoryDEBUG [main] SessionFactoryImpl.<init>(173) | �

Session factory constructed with filter configurations : {}DEBUG [main] SessionFactoryImpl.<init>(177) | instantiating session factory with �

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=�

org.springframework.orm.hibernate3.LocalDataSourceConnectionProvider, ... }...DEBUG [main] SessionFactoryObjectFactory.<clinit>(39) | �

initializing class SessionFactoryObjectFactoryDEBUG [main] SessionFactoryObjectFactory.addInstance(76) | �

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 SessionsIf, 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.

Listing 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);

CHAPTER 11 � HIBERNATE SUPPORT 403

Page 6: Pro Spring 2.5

SessionFactory sessionFactory = (SessionFactory)ac.getBean("hibernateSessionFactory");

Session session = sessionFactory.openSession();// use the Sessionsession.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.

Listing 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 worktransaction.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 com-

mit 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 HibernateTemplatehas 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.

CHAPTER 11 � HIBERNATE SUPPORT404

Page 7: Pro Spring 2.5

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 exam-

ple of its use.

Listing 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 workreturn null;

}});

}

}

CHAPTER 11 � HIBERNATE SUPPORT 405

Page 8: Pro Spring 2.5

The preceding listing illustrates most of the points we discussed. We create the HibernateTemplateclass and give it the SessionFactory instance. Next, we call its execute method and supply an anony-

mous implementation of the HibernateCallback that does all the database work. We get the Sessionas 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 executemethod.

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 hibernateTemplatebean; it can then be used in the same manner as the manually created HibernateTemplate instance.

Listing 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.xsdhttp://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/�

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 {

CHAPTER 11 � HIBERNATE SUPPORT406

Page 9: Pro Spring 2.5

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 workreturn 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 HibernateTemplateand the Hibernate Session and SessionFactory.

Using HibernateDaoSupportThe 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.

Listing 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");}

}

CHAPTER 11 � HIBERNATE SUPPORT 407

Page 10: Pro Spring 2.5

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.

Listing 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.xsdhttp://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/�

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.�

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"> . . .

CHAPTER 11 � HIBERNATE SUPPORT408

Page 11: Pro Spring 2.5

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).

Listing 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 SessionRecall 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.

Listing 11-12. Using Hibernate SessionFactory Instead of HibernateTemplate

@Repositorypublic 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();}

}

CHAPTER 11 � HIBERNATE SUPPORT 409

Page 12: Pro Spring 2.5

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 documenta-

tion, returns a Session bound to the current thread. Listing 11-13 shows how we use the

TemplatelessHibernateInvoiceLogEntryDao bean in a sample application.

Listing 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 allowcreation 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.

Listing 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/�

hibernate/*.hbm.xml</value></list>

</property><property name="hibernateProperties">

<props><prop key="hibernate.dialect">

org.hibernate.dialect.Oracle9Dialect</prop>

CHAPTER 11 � HIBERNATE SUPPORT410

Page 13: Pro Spring 2.5

<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 �

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.

Listing 11-15. Modified Templateless DAO

@Repositorypublic 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 imple-

mentation. Both implementations of the LogEntryDao interface (HibernateLogEntryDao and

TemplatelessLogEntryDao) are going to run the same Hibernate code (see Listing 11-16).

Listing 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).

CHAPTER 11 � HIBERNATE SUPPORT 411

Page 14: Pro Spring 2.5

uniqueResult();}

});}

...}

@Repositorypublic 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 per-

form the standard exception translation. Listing 11-17 shows the one-line post processor definition.

Listing 11-17. The Exception Translator Bean Post Processor

<bean class="org.springframework.dao.annotation.�

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

CHAPTER 11 � HIBERNATE SUPPORT412

Page 15: Pro Spring 2.5

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 ApplicationsThe 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. Enter-

prise 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 appli-

cations 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 UpdatesTable 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 chang-

ing 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

CHAPTER 11 � HIBERNATE SUPPORT 413

Page 16: Pro Spring 2.5

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 opti-

mistic 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.

Listing 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.

Listing 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>

CHAPTER 11 � HIBERNATE SUPPORT414

Page 17: Pro Spring 2.5

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 Hiber-

nate will do what we expect, we issue the SQL command alter table t_log_entry add versionnumber(19, 0) null and run the code in Listing 11-20.

Listing 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 entryLogEntry le = new LogEntry();le.setName("Name");le.setDate(Calendar.getInstance().getTime());logEntryDao.save(le);

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

}}

The code simulates the behavior of the application from Table 11-1. It loads two LogEntryobjects 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: 1DEBUG [main] NullableType.nullSafeSet(133) | binding 'X' to parameter: 2DEBUG [main] NullableType.nullSafeSet(133) | binding '2007-10-12 10:35:06' to �

parameter: 3DEBUG [main] NullableType.nullSafeSet(133) | binding '1160' to parameter: 4DEBUG [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: 1DEBUG [main] NullableType.nullSafeSet(133) | binding 'Z' to parameter: 2DEBUG [main] NullableType.nullSafeSet(133) | binding '2007-10-12 10:35:06' to �

CHAPTER 11 � HIBERNATE SUPPORT 415

Page 18: Pro Spring 2.5

parameter: 3DEBUG [main] NullableType.nullSafeSet(133) | binding '1160' to parameter: 4DEBUG [main] NullableType.nullSafeSet(133) | binding '0' to parameter: 5...ERROR [main] AbstractFlushingEventListener.performExecutions(301) |

Could not synchronize database state with sessionorg.hibernate.StaleObjectStateException: Row was updated or deleted by another�

transaction (or unsaved-value mapping was incorrect): �

[com.apress.prospring2.ch11.domain.LogEntry#1160]at org.hibernate.persister.entity.AbstractEntityPersister.�

check(AbstractEntityPersister.java:1765)...at com.apress.prospring2.ch11.dataaccess.VersioningDemo.�

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 EqualityNow that we can safely prevent stale data updates, we need to consider another important limita-

tion. 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.

Listing 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.

CHAPTER 11 � HIBERNATE SUPPORT416

Page 19: Pro Spring 2.5

Listing 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 CastToConcreteClassfinal LogEntry that = (LogEntry) o;

if (this.id != null ? !this.id.equals(that.id) : that.id != null) return false;

//noinspection RedundantIfStatementif (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 com-

mon implementation of equals and hashCode to a new class, and have LogEntry extend the new

class. Listing 11-23 shows this refactoring.

Listing 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 CastToConcreteClassfinal AbstractIdentityVersionedObject that =

(AbstractIdentityVersionedObject) o;

CHAPTER 11 � HIBERNATE SUPPORT 417

Page 20: Pro Spring 2.5

if (this.id != null ? !this.id.equals(that.id) : that.id != null) return false;

//noinspection RedundantIfStatementif (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;

}

@Overridepublic boolean equals(final Object o) {

return idEquals(o);}

@Overridepublic 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;

CHAPTER 11 � HIBERNATE SUPPORT418

Page 21: Pro Spring 2.5

}

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 BehaviorThe 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.

Listing 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>

CHAPTER 11 � HIBERNATE SUPPORT 419

Page 22: Pro Spring 2.5

We now have the transactionManager bean, which allows us to control Hibernate transactions

using the common Spring transaction framework. However, declaring only the transactionManagerbean does not mean we get transactional behavior, as code in Listing 11-25 shows.

Listing 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.

CHAPTER 11 � HIBERNATE SUPPORT420

Page 23: Pro Spring 2.5

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 gener-

ated 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.

CHAPTER 11 � HIBERNATE SUPPORT 421

Page 24: Pro Spring 2.5

Listing 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.�

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>

CHAPTER 11 � HIBERNATE SUPPORT422

Page 25: Pro Spring 2.5

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 suc-

cessful 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.

Listing 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/�

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

CHAPTER 11 � HIBERNATE SUPPORT 423

Page 26: Pro Spring 2.5

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

Listing 11-28. Further Verification of the Transaction Code

$ sqlplus PROSPRING/x*****6@//oracle.devcake.co.uk/INTLSQL> 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/INTLSQL> 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 LoadingThis 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 Invoiceobject 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 Invoiceobjects. On average, we can expect an Invoice to have five lines; that means 50,000 InvoiceLineobjects. 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.

CHAPTER 11 � HIBERNATE SUPPORT424

Page 27: Pro Spring 2.5

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.

Listing 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);

}}

CHAPTER 11 � HIBERNATE SUPPORT 425

Page 28: Pro Spring 2.5

This code clearly shows that the addInvoiceLine and addDiscount methods establish a bidirec-

tional 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.

Listing 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); //2invoiceLine.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.

Listing 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)

)/

CHAPTER 11 � HIBERNATE SUPPORT426

Page 29: Pro Spring 2.5

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).

Listing 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>

CHAPTER 11 � HIBERNATE SUPPORT 427

Page 30: Pro Spring 2.5

</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>

CHAPTER 11 � HIBERNATE SUPPORT428

Page 31: Pro Spring 2.5

</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 InvoiceLineobjects. 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.

Figure 11-4. UML diagram of the DAOs and services

CHAPTER 11 � HIBERNATE SUPPORT 429

Page 32: Pro Spring 2.5

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.

Listing 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.

Listing 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/�

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();}

CHAPTER 11 � HIBERNATE SUPPORT430

Page 33: Pro Spring 2.5

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.

Listing 11-35. Stored Procedure to Create the Sample Data

CREATE OR REPLACE PROCEDURE "PROSPRING"."CREATE_SAMPLE_DATA"is

i number;j number;l number;

begindbms_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 loopinsert 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).

Listing 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 per-

haps 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:

CHAPTER 11 � HIBERNATE SUPPORT 431

Page 34: Pro Spring 2.5

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.

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 applica-

tion 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 transac-

tional 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

CHAPTER 11 � HIBERNATE SUPPORT432

Page 35: Pro Spring 2.5

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 oppo-

site 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 subse-

quent 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.

Listing 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.

CHAPTER 11 � HIBERNATE SUPPORT 433

Page 36: Pro Spring 2.5

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.

Listing 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 omittedpublic void recalculateDiscounts(Long id) {

Invoice invoice = this.invoiceDao.getByIdLazy(id);BigDecimal total = invoice.getLinesTotalPrice();if (total.compareTo(BigDecimal.TEN) > 0) {

// do something special}

}}

CHAPTER 11 � HIBERNATE SUPPORT434

Page 37: Pro Spring 2.5

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 pat-

tern 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 incon-

sistent 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 SetsMost 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 sim-

ply 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.

Listing 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);

CHAPTER 11 � HIBERNATE SUPPORT 435

Page 38: Pro Spring 2.5

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 num-

ber 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 imple-

ment 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.

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 SearchResultSupportsubclass. 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).

CHAPTER 11 � HIBERNATE SUPPORT436

Page 39: Pro Spring 2.5

Listing 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 ObjectsIt 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 LobHandlerimplementation for the database we are using. Take a look at Listing 11-41, which shows a table

with BLOB and CLOB fields.

Listing 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).

Listing 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;

CHAPTER 11 � HIBERNATE SUPPORT 437

Page 40: Pro Spring 2.5

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;

}

@Overridepublic 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.

Listing 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>

CHAPTER 11 � HIBERNATE SUPPORT438

Page 41: Pro Spring 2.5

</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.

Listing 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.�

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/*.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 trans-

action 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!). List-

ing 11-45 shows the service interface and its implementation.

CHAPTER 11 � HIBERNATE SUPPORT 439

Page 42: Pro Spring 2.5

Listing 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 implementa-

tion and verifies that the LOB handling works as expected.

Listing 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/�

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();

}

}

CHAPTER 11 � HIBERNATE SUPPORT440

Page 43: Pro Spring 2.5

Running this application produces output that verifies that the LobTest object gets inserted

and fetched without any problems:

DEBUG [main] ConnectionManager.cleanup(380) | performing cleanupDEBUG [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 completionDEBUG [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 completionLobTest { 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 CodeYou 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 situa-

tion 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 Hiber-

nate, 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 perform-

ing JDBC DAO operations that depend on rows inserted as a result of the Hibernate session operations.

SummaryIn 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.

CHAPTER 11 � HIBERNATE SUPPORT 441


Recommended