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 data type 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 data type. So, if you see problems and errors in the application regarding this time issue, check first the data type mapping in hbm.xml the files and the appropriate Java Beans mappings.
Changes you need to make when you add new field in database table
Here are the changes you'll need to perform in Hibernate layer when you add new column in database:
- All tables have their own corresponding Java model class in net.project.hibernate.model package. You'll need to find the right model and to edit it. There you should add data type you'll need in Java (String, Integer, Date etc.).
- Every model has .hbm.xml XML file. There you will need to all appropriate mapping between property in Java and database new column. For example:
<property name="address" type="java.lang.String" column="ADDRESS" length="80" />
Here you can see that we have property name "address" in Java it is type of java.lang.String, and for this property in Java we have appropriate property in database table name "ADDRESS" which is also string type and it is 80 characters long. This is only the basic possibility how you can add some additional mapping into Hibernate mapping files. There are much more possibilities and if you want to have a look into all of them you can find more into Hibernate official site.
Hibernate best practices
Selecting list of appropriate beans
Hibernate always gives you java.util.List object as a result. So now it is possible to have a list of already predefines Java beans as a result. This can be done very easily. For example, if we have a class Conference and you want to execute some query over that table with some filter etc. In the query you can select some of existing columns in this table. You can do this in the following way:
String query = " select new Conference(c.conferenceId, c.conferenceName, c.conferencePlace, c.conferenceDisplayName) from Conference c where c.conferenceId <> :conferenceId ";
List result = getHibernateTemplate().getSessionFactory().getCurrentSession().createQuery(query).setInteger("conferenceId ", conferenceId ).list();
Here in the "result" you will have list of Conference objects with those preoperties set: conferenceId, conferenceName, conferencePlace, conferenceDisplayName. If there are some other properties they will have their default values they had already set in Conference class.
One additional thing you'll need to take care about is constructor in class must exists. Please take care in the column order you used in query, because exactly that constructor will be called via reflection from Hibernate and if you don't have such constructor you'll get exceltion here. So in Constructor class you should have:
public Conference(Integer conferenceId, String conferenceName, String conferencePlace, String conferenceDisplayName){
this.conferenceId = .conferenceId;
this.conferenceName = .conferenceName;
this.conferencePlace = .conferencePlace;
this.conferenceDisplayName = .conferenceDisplayName;
}
This way you don't need to handle Hibernate output and to get all columns from query one by one. This way you'll have all columns you need in only few lines of code.
Creating join with multiple tables
With Hibernate, of course, it is possible to easily write queries with table joins. There are more way how you can do this and the best resource is to have a look at Hibernate related section in documentation Hibernate Joins. Here will be presented only one technique similar to one in previous section how you can make your code shorter and more easy to maintain. For example, if you have two tables Conference and Participant and you want to have all participants who will take part in one specific conference. You'll need to join those two tables of course. Here is example of that query:
String query = " select c.conferenceId, c.conferenceName, c.conferencePlace, c.conferenceDisplayName, p.participantId, p.firstName, p.lastName from Conference c, Participant p where c.conferenceId = p.participantId and c.conferenceId <> :conferenceId ";
List result = getHibernateTemplate().getSessionFactory().getCurrentSession().createQuery(query).setInteger("conferenceId ", conferenceId ).list();
if (result.hasNext()) {
Object[] row = (Object[]) result.next();
Integer column0 = (Integer) row[0];
String column1 = (String) row[1];
String column2 = (String) row[2];
String column3 = (String) row[3];
Integer column4 = (Integer) row[4];
String column5 = (String) row[5];
String column6 = (String) row[6];
}
Here as a result you'll get a list of Object[] objects and you can refer to specific columns as Object[0], Object[1] etc. Depending on which column from query you want to get.
So after this query you'll have a dozens of lines of code in order to extract results. This isn't some big step ahead whey you compare it to JDBC. Here we can do one thing to make the process faster and more programmer friendly for programmer further usage.
We can crate additional Java Bean which will contain all fields we have in query. For example:
public class ConferenceParticipants {
private Integer conferenceId;
private String conferenceName;
private String conferencePlace;
private String conferenceDisplayName;
private Integer participantId;
private String firstName;
private String lastName;
public ConferenceParticipants(Integer conferenceId, String conferenceName, String conferencePlace, String conferenceDisplayName, Integer participantId, String firstName,
String lastName) {
super();
this.conferenceId = conferenceId;
this.conferenceName = conferenceName;
this.conferencePlace = conferencePlace;
this.conferenceDisplayName = conferenceDisplayName;
this.participantId = participantId;
this.firstName = firstName;
this.lastName = lastName;
}
public Integer getConferenceId() {
return conferenceId;
}
public void setConferenceId(Integer conferenceId) {
this.conferenceId = conferenceId;
}
public String getConferenceName() {
return conferenceName;
}
public void setConferenceName(String conferenceName) {
this.conferenceName = conferenceName;
}
public String getConferencePlace() {
return conferencePlace;
}
public void setConferencePlace(String conferencePlace) {
this.conferencePlace = conferencePlace;
}
public String getConferenceDisplayName() {
return conferenceDisplayName;
}
public void setConferenceDisplayName(String conferenceDisplayName) {
this.conferenceDisplayName = conferenceDisplayName;
}
public Integer getParticipantId() {
return participantId;
}
public void setParticipantId(Integer participantId) {
this.participantId = participantId;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((conferenceDisplayName == null) ? 0 : conferenceDisplayName.hashCode());
result = prime * result + ((conferenceId == null) ? 0 : conferenceId.hashCode());
result = prime * result + ((conferenceName == null) ? 0 : conferenceName.hashCode());
result = prime * result + ((conferencePlace == null) ? 0 : conferencePlace.hashCode());
result = prime * result + ((firstName == null) ? 0 : firstName.hashCode());
result = prime * result + ((lastName == null) ? 0 : lastName.hashCode());
result = prime * result + ((participantId == null) ? 0 : participantId.hashCode());
return result;
}
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final ConferenceParticipants other = (ConferenceParticipants) obj;
if (conferenceDisplayName == null) {
if (other.conferenceDisplayName != null)
return false;
} else if (!conferenceDisplayName.equals(other.conferenceDisplayName))
return false;
if (conferenceId == null) {
if (other.conferenceId != null)
return false;
} else if (!conferenceId.equals(other.conferenceId))
return false;
if (conferenceName == null) {
if (other.conferenceName != null)
return false;
} else if (!conferenceName.equals(other.conferenceName))
return false;
if (conferencePlace == null) {
if (other.conferencePlace != null)
return false;
} else if (!conferencePlace.equals(other.conferencePlace))
return false;
if (firstName == null) {
if (other.firstName != null)
return false;
} else if (!firstName.equals(other.firstName))
return false;
if (lastName == null) {
if (other.lastName != null)
return false;
} else if (!lastName.equals(other.lastName))
return false;
if (participantId == null) {
if (other.participantId != null)
return false;
} else if (!participantId.equals(other.participantId))
return false;
return true;
}
}
So here we have complete new class with appropriate fields, getter/setter, equals, hasCode methods. All those methods are generated automatically in Eclipse so you don't have to waste your time on this. Now we can shrink our code. The new code will be like:
String query = " select cp.conferenceId, cp.conferenceName, cp.conferencePlace, cp.conferenceDisplayName, cp.participantId, cp.firstName, cp.lastName from ConferenceParticipants cp where cp.conferenceId = cp.participantId and cp.conferenceId <> :conferenceId ";
List result = getHibernateTemplate().getSessionFactory().getCurrentSession().createQuery(query).setInteger("conferenceId ", conferenceId ).list();
As you can see here our code is now much more cleaner and you don't have to handle all columns in query separately. Now you get a list of ConferenceParticipants? Beans and you can use it further without casting and handling additional exceptions. If you need Conference or Participant class with predefined properties you can use BeanUtils? from Apache Commons and copy all properties from one bean to another. Here is the documentations for this tool how it works and how you can use it. The only thing you'll need to take care about coping properties from one bean to another is ALL properties should have the same name. If some bean doesn't have appropriate property it will be skipped. So here ConferenceParticipants? has more properties then Conference or Participant so when you copy from ConferenceParticipants? to Conference/Participant they will have those properties you used in query set. If you copy in oposite direction from Conference/Participant to ConferenceParticipants? then properties which exists in ConferenceParticipants? and doesn't exist in Conference/Participant will be null.
Placement of packages/classes for custom Spring/Hibernate classes
Right now we have following package structure for Spring/Hibernate DAO layer:
net.project.hibernate -- Main folder for hibernate related DAO classes.
dao -- Folder where all DAO interfaces are placed.
impl -- Folder where implementation classes for DAO interfaces are placed.
model -- Folder where all model classes are placed.
service -- Folder where all service interfaces are placed.
impl -- Folder where implementation classes for service interfaces are placed.
Everything is organized via interfaces. This way we can pull out our implementation for DAO or Service layer and change it with something completely different. Because our database has a lots of tables and every table has at least one class/interface in all packages it is crucial to have clean package organization of this layer from now on. There are too many classes right now in those packages and because of that we suggest to place in the same structure of packages all classes which are related only to specific modules. So for example, if there are some DAO related classes only for business module, then the folder structure should be:
net.project.business -- Main folder/package for hibernate related DAO classes for business module.
dao -- Folder/package where all DAO interfaces are placed for business module.
impl -- Folder/package where implementation classes for DAO interfaces are placed for business module.
model -- Folder/package where all model classes are placed for business module. This means if you create some models on your own for some specific queries with joins you make.
service -- Folder/package where all service interfaces are placed for business module.
impl -- Folder/package where implementation classes for service interfaces are placed for business module.
model -- Database packages related files are placed here. Every package has two files one for body and one for specification with extension bdy and spc, respectively.
service -- Database sequences. Every sequence has its own script.
