Use of Hibernate
Hibernate is simple ORM (Object-Relational-Mapping) tool. More information you can found on Hibernate official site.
The main package for Hibernate in Project.net is net.project.hibernate. JavaBeans and Hibernate mapping files in Project.net can be found in this package net.project.hibernate.model. JavaBeans in Hibernate are called offten models. Every database table has one Hibernate mapping xml file and one or two JavaBeans. The name of the mapping xml file is created using this convention: If TABLE_NAME is the name of a database table, then its Hibernate mapping file is named TableName.hbm.xml. For the table's primary key the file would be named TableNamePK.java.
Here in Project.net, we'll apply a simple CRUD (Create-Read-Update-Delete) pattern in the database layer. This pattern represents group of actions heavily used on every database entity. We'll use this pattern and its implementation in the Spring framework. Hibernate has one main configuration file, called hibernate.cfg.xml, which is placed in this location in SVN /trunk/core/config/hibernate/hibernate.cfg.xml. Since we'll use Hibernate from the Spring framework all the settings will remain the same, but will be moved to Spring's configuration files.
Since the Project.net already uses Spring framework we'll only need to extend its use. Spring application context is loaded via WebApplicationContext, so here in backend we'll need reference to that context on order to use Spring beans and bussines logic created there. There are more ways how this can be solved:
- We'll pass reference from web part of application to backend through methods called, as method parameter,
- We'll read context again
- We'll pass reference to Singelton class via context listener
- We'll load reference to Singelton class via context listener for other Beans created in separate configuration file.
So here we'll use thrid approach, probably there are more solutions for this problem, but this is the most elegant so far. First one obligates programmer to pass reference through all methods that intend to call database, second will duplicate context and take more memory on Application Server. The idea is to have reference on WebApplicationContext in easiest way. Third way occurs some internal errors in Weblogic application server (in Tomcat 5.5 works fine). Instead of context listener we can use Servlet and place in init() method same code as in context listener class. Here is used last way to implement appropriate architecture. Here are several files and they are cruical for Hibernate + Spring implementation in application:
- all Hibernate mapping files and Java models are placed in this package net.project.hibernate.model.
- the main configuration file for Hibernate + Spring called bussinessContext.xml is placed in this location in trunk trunk\core\config\spring. This file contains all Hibernate and Spring bean properties. Programmers should look carefully this file, since thay will add mappings for new Spring beans they'll create.
- IDAO interface, which reside in net.project.hibernate.dao package. All DAO interfaces must extend this interface with appropriate parameters. This will be latter explained in more details. Programmers should only to look this class, since thay will not change or add any code there.
- AbstractHibernateDAO abstract class which contains implementations for all CRUD methods. Programmers should only to look this class, since thay will not change or add any code there.
- ServiceFactory abstract class. This class implements Singleton pattern and it is crucial class for this pattern. It can be found in net.project.hibernate.service package. Programmers should look carefully this class, since thay will add declarations for abstract getter methods for new beans they'll create.
- ServiceFactoryImpl is implementation of ServiceFactory class. It resides in net.project.hibernate.service.impl package. This class actually reads bussinessContext.xml file and make possible Spring beans methods invocation.Programmers should look carefully this class, since thay will add declarations for getter methods for new beans they'll create.
- SpringContextListener class passes reference to ServiceFactory for Spring context. Actually it calls init method in it. This class isn't important for programmers. It is defined in web.xml file and this action occurs during application startup. Programmers should only to look this class, since thay will not change or add any code there.
Example 1.
Here is one simple example how to create new Spring bean. We begin with model classes. Now all of these classes are created and all mappings for it but just in case you need to check, is there appropriate model class and Hibernate mapping class for it. This is the most simple example because it uses only one model.
Lets say that we have Java model class called Conference. With two fields:
package net.project.hibernate.model;
import java.io.Serializable;
public class Conference implements Serializable {
private Integer id;
private String conferenceName;
public String getConferenceName() {
return conferenceName;
}
public void setConferenceName(String conferenceName) {
this.conferenceName = conferenceName;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
What is important for these model classes is that they must implements Serializable interface, and they must have getters and setters methods for all fields. Hibernate mapping file can look like this:
?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> <class name="net.project.hibernate.model.Conference" table="CONFERENCE" lazy="false"> <id name="id" type="java.lang.Integer" column="ID"> <generator class="native" /> </id> <property name="conferenceName" type="java.lang.String"column="CONFERENCE_NAME" not-null="true" length="50" /> </class> </hibernate-mapping>
Second step is DAO layer classes creation. First we'll take a look at IDAO interface which will all DAO layer classes implement.
package net.project.hibernate.dao;
import java.io.Serializable;
import java.util.List;
/**
* Interface that define database methods from the CRUD pattern.
*/
public interface IDAO<OBJECT, PK extends Serializable> {
/**
* Create/insert new record into the database.
* @param object Bean (domain model object) that contains values to insert.
* @return Primary key of the newly created object.
*/
public PK create(OBJECT object);
/**
* Return list of objects populated with records from the database.
* @return List of domain model objects.
*/
public List<OBJECT> findAll();
/**
* Find an record identified by primary key and return populated bean.
* @param key Primary key of the record to return.
* @return Bean populated with data from the record.
*/
public OBJECT findByPimaryKey(PK key);
/**
* Update an record into the database.
* @param object Domain model object with data to update.
*/
public void update(OBJECT object);
/**
* Delete an record from the database.
* @param object Object that represents record to delete.
*/
public void delete(OBJECT object);
}
This interface declares all important methods for one database entity. All primary actions are covered. For our Conference entity, we'll create one new interface called !IConderenceDAO with few only few lines of code (for now):
package net.project.hibernate.dao;
import net.project.hibernate.model.Conference;
/**
* Interface for the conference database access object.
*/
public interface IConferenceDAO extends IDAO<Conference, Integer> {
}
Please pay attention on parameters we pass to DAO interface. First one is Java model class name, and second is object type for its primary key. Primery key can be also some particular class object if there is composite primary key for that entity. Also pay attention that this interface resides in net.project.hibernate.dao package. Second file we need to create for DAO layer is one class which extends AbstractHibernateDAO class and implements interface we just create.
package net.project.hibernate.dao.impl;
import net.project.hibernate.dao.IConferenceDAO;
import net.project.hibernate.model.Conference;
/**
* Conference database object implementation.
*/
public class ConferenceDAOImpl extends AbstractHibernateDAO<Conference, Integer> implements IConferenceDAO {
/**
* Default constructor.
*/
public ConferenceDAOImpl() {
super(Conference.class);
}
}
This class must have only default constructor. If we don't implement CRUD methods in this class they are inherited from AbstractHibernateDAO class where they have its default implementation. If we need to modify some of them than we should override it here in this implementation class (ConferenceDAOImpl in this case). Third step is to create service classes and interfaces for this entity. First we'll create one interface called IConferenceService and there we'll have declared methods for our service layer implementation class. Here is example for this class:
package net.project.hibernate.service;
import java.util.List;
import net.project.hibernate.model.Conference;
/**
* Conference management service interface.
*
*/
public interface IConferenceService {
/**
* Get the conference identified with primary key.
* @param primaryKey Primary key of the conference to return.
* @return Conference bean.
*/
public Conference getConference(Integer primaryKey);
/**
* Get list of all conferences.
* @return List of conference beans.
*/
public List<Conference> getAllConferences();
}
Here are only two methods declared, just to show how this pattern should work. Last thig regarding service part in this pattern is creation of implementation class for this interface. We'll create class called ConferenceServiceImpl like this:
package net.project.hibernate.service.impl;
import java.util.List;
import net.project.hibernate.dao.IConferenceDAO;
import net.project.hibernate.model.Conference;
import net.project.hibernate.service.IConferenceService;
/**
* Conference management service implementation.
*/
public class ConferenceServiceImpl implements IConferenceService {
/**
* Conference database access object.
*/
private IConferenceDAO conferenceDAO;
public List<Conference> getAllConferences() {
return conferenceDAO.findAll();
}
public Conference getConference(Integer primaryKey) {
return (Conference) conferenceDAO.findByPimaryKey(primaryKey);
}
/**
* Sets the conferenceDAO value.
* @param conferenceDAO The conferenceDAO to set.
*/
public void setConferenceDAO(IConferenceDAO conferenceDAO) {
this.conferenceDAO = conferenceDAO;
}
}
It is very important to notice that this service implementation class have private member of DAO layer interface, and setter method for this member (getter is needless). During application running when this context loads this set method will be called and this member will have value of implementation class for that interface (!ConferenceDAOImpl in this case). Here in this class we have also those two methods we defined earlier in IConferenceService interface. Also notice that for getConference method in IConferenceService interface we call findByPimaryKey method in DAO layer and for getAllConferences we call findAll (Delegate method pattern). We don't need to implement these methods in DAO layer because they are already implemented in AbstractHibernateDAO class, as I sad before. If you want to call some other of several basic methods implemented in AbstractHibernateDAO layer you only have to declare it in this service interface and to delegate method in implementation class. Forth thing you need to do is to declare getter methods in ServiceFactory and ServiceFactoryImpl classes in following way, first we'll give example for ServiceFactory abstract class:
. . . public abstract IConferenceService getConferenceService(); . . .
and for ServiceFactoryImpl class:
.
.
.
/**
* Spring's bean factory.
*/
private BeanFactory beanFactory;
@Override
public IConferenceService getConferenceService() {
return (IConferenceService) beanFactory.getBean("conferenceService");
}
.
.
.
In this way we made reference for Spring's bean called conferenceService. We'll call methods declared in Service layer and those methods call methods in DAO layer. Now the last thing we need to do is to define new bean in Spring's bussinessContext.xml file like this.
<bean id="conferenceDAO" class="net.project.hibernate.dao.impl.ConferenceDAOImpl"> <property name="hibernateTemplate" ref="hibernateTemplate"/> </bean> <bean id="conferenceService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" lazy-init="true"> <property name="transactionManager" ref="transactionManager"/> <property name="target"> <bean class="net.project.hibernate.service.impl.ConferenceServiceImpl"> <property name="conferenceDAO" ref="conferenceDAO"/> </bean> </property> <property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean>
We need to declare two beans. First is conferenceDAO and we have to declare it in order to Spring inject appropriate reference of implementation class. The other bean we have to create is bean called conferenceService. We define also transaction management in this xml file. Please take a look at Spring's official site to see more details about transaction management. Now we finished declaration for one bean. At the end we have to know how to call this methods we defined. Here is one code sample for one posible select scenario:
IConferenceService conferenceService = ServiceFactory.getInstance().getConferenceService();
List<Conference> conferences = conferenceService.getAllConferences();
for (Conference c : conferences) {
System.out.println(c.getConferenceName());
}
In code above we select all Conferences and we print, Conference's name. Please note that we have list of Conference beans as resultset.
It is really important to understand th entire structure for this pattern. If done right, we can switch the entire implementation layer without any code changes to the layers above it. At first, it looks like there are too many files to create and other tasks to do for simple cases, but the effort pays off when you begin to do really complicated things. I'll add more examples as soon I find some free time.
All of these Hibernate mapping classes are created with Middlegen mapping tool. This tool works fine, but the "problem" with an Oracle database is that columns with a DATE datatype can be stored data with hours, minutes and seconds resolution. In the application, this data is represented as java.sql.Date class, but it needs to be represented with java.sql.Timestamp datatype. So, if you see problems and errors in the application regarding this time issue, check first the datatype mapping in hbm.xml the files and the appropriate Java Beans mappings.
