One of the sweetiest things the Spring framework gives is Hibernate support is built-in by the time it's born.
Following is a quick guide to configure Hibernate's SessionFactory in Spring. For a detailed version about Spring's Hibernate support, read
http://www.springframework.org/docs/data_access.html
.
With Spring, Hibernate's SessionFactory no longer needs to bind itself to JNDI; nor use Hibernate's own hibernate.cfg.xml method, which is a little bit tricky to code in Hibernate 2.x (as Hibernate2 doesn't use the once-and-only-once configure() anymore).
Instead, we use
org.springframework.orm.hibernate.LocalSessionFactoryBean
.
<bean id=
"MySessionFactory"
class
=
"org.springframework.orm.hibernate.LocalSessionFactoryBean"
>
<property name=
"mappingResources"
>
<list>
<value>mappings/Book.hbm.xml</value>
<value>mappings/Patron.hbm.xml</value>
<value>mappings/BorrowRecord.hbm.xml</value>
</list>
</property>
<property name=
"hibernateProperties"
>
<props>
<prop key=
"hibernate.dialect"
>net.sf.hibernate.dialect.MySQLDialect</prop>
<prop key=
"hibernate.query.substitutions"
>
true
=
1
false
=
0
</prop>
<prop key=
"hibernate.show_sql"
>
false
</prop>
<prop key=
"hibernate.use_outer_join"
>
false
</prop>
</props>
</property>
<property name=
"dataSource"
><ref bean=
"MyDataSource"
/></property>
</bean>
The above parameters are simple and verbose:
However, we don't need to configure a transaction manager inside the SessionFactory, as we will see below.
After configuring this, we need to provide a setter method in our business objects that need to use Hibernate's SessionFactory:
import
net.sf.hibernate.SessionFactory;
....
public class
MyBusinessObjectImpl
implements
MyBusinessObject
{
private
SessionFactory sessionFactory;
public
void
setSessionFactory(SessionFactory sessionFactory)
{
this
.sessionFactory = sessionFactory;
}
public
SessionFactory getSessionFactory()
{
return this
.sessionFactory;
}
....
and hook it up to Spring.
<bean id=
"MyBusinessObject"
class
=
"library.MyBusinessObjectImpl"
>
<property name=
"sessionFactory"
>
<ref bean=
"MySessionFactory"
/>
</property>
</bean>
How about transactions support? And how do I get Hibernate's Session inside my business objects?
That's Spring framework's another power - HibernateInterceptor and TransactionInterceptor. With them, together with configurations made in Spring, methods inside business objects don't need to write a single line of code for that; instead, a Session will be bound to the business object's current thread, opened and closed automatically; and a transaction will also begin and end automatically.
The sequence is like this:
-
Transaction begins
-
Hibernate session opened and bound to the current thread
-
Actual method execution
-
Hibernate session bound to the current thread closed
-
Transaction ended
This is done with the help of Spring's AOP capability.
However, we need to configure a transaction manager first.
<bean id=
"MyTransactionManager"
class
=
"org.springframework.transaction.jta.JtaTransactionManager"
/>
The above configures a transaction manager that will access an UserTransaction inside the environment, usually in a J2EE container, or a servlet container with transaction support.
Alternately you may want to have a look at
org.springframework.orm.hibernate.HibernateTransactionManager
.
Next, we need to define the transaction attribute for our business methods. This is done in
org.springframework.transaction.interceptor.TransactionInterceptor
?
.
<bean id=
"MyTransactionInterceptor"
class
=
"org.springframework.transaction.interceptor.TransactionInterceptor"
>
<property name=
"transactionManager"
><ref bean=
"MyTransactionManager"
/></property>
<property name=
"transactionAttributeSource"
>
<value>
library.MyBusinessImpl.borrowBook=PROPAGATION_REQUIRED
library.MyBusinessImpl.returnBook=PROPAGATION_REQUIRED
library.BookSearchImpl.*=PROPAGATION_SUPPORTS
</value>
</property>
</bean>
You can use a wildcard to tell every method in that business object uses the same transaction attribute. However it is not recommended, as the private methods used inside the business object will have transactions too, which you may not need to.
By the way, the TransactionInterceptor's default behaviour is to commit an transaction anyway, and rollback whenever a RuntimeException is caught - much the same as EJB's behaviour. If you want to control TransactionInterceptor's behaviour when exception is caught, you may tell it with a plus (+) or minus (-) sign, followed by the name of the exception, to commit or rollback a transaction even if an exception mentioned is caught.
An example:
library.MyBusinessImpl.addBook=PROPAGATION_REQUIRED,-SeriesNotFoundException,+CategoryNotFoundException
This means that when SeriesNotFoundException is thrown inside the addBook() method, the transaction will roll back; on the other hand when CategoryNotFoundException is thrown the transaction will be commited anyway.
In most cases you don't need to specify the exception's name with the package it belongs; you just need to specify simply the exception's name.
Then our business objects will need to define as an AOP "target". This is just a change in a name; make sure your code will not call these business objects directly. So change the above business object declaration to
<bean id=
"MyBusinessObjectTarget"
class
=
"library.MyBusinessObjectImpl"
>
<property name=
"sessionFactory"
>
<ref bean=
"MySessionFactory"
/>
</property>
</bean>
Final step is to expose the business object on Spring's ApplicationContext, but not the business object itself; instead we use a ProxyFactoryBean, provided by Spring.
<bean id=
"MyBusinessObject"
class
=
"org.springframework.aop.framework.ProxyFactoryBean"
>
<property name=
"proxyInterfaces"
>
<value>library.MyBusinessObject</value>
</property>
<property name=
"interceptorNames"
>
<list>
<value>MyTransactionInterceptor</value>
<value>MyHibernateInterceptor</value>
<value>MyBusinessObjectTarget</value>
</list>
</property>
</bean>
-
proxyInterfaces: the interface implemented by your business object.
-
interceptorNames: the interceptors to be applied when methods on the business interface is called. Be careful, they have to be placed in order.
Then in our business method we just need to obtain the Session instance, and ignore everything else - everything is done behind the scene.
import
net.sf.hibernate.*;
import
org.springframework.orm.hibernate.SessionFactoryUtils;
....
public
Book findBook(
int
bookID)
throws
BookNotFoundException, DataAccessException
{
//get the Session instance already bound to current thread and opened
Session session = SessionFactoryUtils.getSession(getSessionFactory(),
false
);
try
{
Book book = (Book)session.load(Book.class, bookID);
return
book;
}
catch
(ObjectNotFoundException e)
{
throw new
BookNotFoundException();
}
catch
(HibernateException e)
{
throw
SessionFactoryUtils.convertHibernateAccessException(e);
}
}
Alternately you may also use HibernateTemplate and TransactionTemplate, though the above method is simpler. Read
http://www.hibernate.org/110.html
for details (this page seems more updated than the one in Spring?).