A lot of hibernate tutorials in internet use paradigm "one dao per entity, one service per dao". So, if we have 10 entities in our application we have to create 10 dao components(each for each entity) and 10 service components. Tutorials about using generics for daos and services are not very common, unfortunately. So, let's make one more generic dao tutorial! :)
Non generic architecture: one pair Dao/Service per POJO object:
Using generics: only one pair of DAO/Service objects for all POJO objects:
0. prject generating
For generating we can use maven-archtype-quickstart maven archtype:
1. adding dependencies
We need to add 3 dependecies to our project: Log4j - logger, Hibernate, H2db - for H2 database support.
2. Hibernate configuration.
in src/main directory we have to create sub-directory "resources". Here will create file "hibernate.cfg.xml" with settings for hibernate:
it's a simple configuration for working with H2 database. Beside information about connection with H2 database, you can see 2 entities in "mapping class" tags: Region and Country - we will create them little later.
3. Log4j configuration
To make logger work properly we have to create configuration file for it.
Location of this file is the same(src/main/resources), file name - log4j.properties. Content of file:
- we will use log level "INFO" for our application. Of course in real application it's better to use WARNING or even ERROR level.
4. Entity creating.
Let's create two simple entity.
Nothing special here. Just 2 entities with JPA tags for declaring table and column information. Implementing of Serializable class here can be ommitted. I included it because in the future this entity might be using in services with serialisation.
4.1. Region entity
4.2 Country entity.
It has relationship many-to-one with region entity.
5. Generic dao
It's most interesting part of the post. Of couce, we can create dao without any interfaces, but using pair daoInterface+daoImplementation is VERY VERY common, so let's do it this way.
5.1. Dao interface - declaring methods(contract) of dao.
6.2. Service
Mostly service methods just calling correspondig mothods of dao. Mostly, except 2 methods extended in service interface: the use dao.query method this different sql statements.
It's a regular "helper" class for working with Hibernate. We will use getSessionFactory method from it.
p.s.
final directory structure
sources can be downloaded from here.
Non generic architecture: one pair Dao/Service per POJO object:
Using generics: only one pair of DAO/Service objects for all POJO objects:
0. prject generating
For generating we can use maven-archtype-quickstart maven archtype:
mvn archetype:generate -DgroupId=com.demien.hibgeneric -DartifactId=manualtx -DarchtypeArtifactId=maven-archtype-quickstart -DinteractiveMode=false
1. adding dependencies
We need to add 3 dependecies to our project: Log4j - logger, Hibernate, H2db - for H2 database support.
<properties> <application.name>Demien :: generic hibernate with manux transactions </application.name> <hibernate.version>4.2.5.Final</hibernate.version> <h2.version>1.3.173</h2.version> <log4j.version>1.2.17</log4j.version> </properties> <dependencies> <!-- HIBERNATE --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>${hibernate.version}</version> </dependency> <!-- H2 DB --> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>${h2.version}</version> </dependency> <!-- LOG4J --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j.version}</version> </dependency> </dependencies>
2. Hibernate configuration.
in src/main directory we have to create sub-directory "resources". Here will create file "hibernate.cfg.xml" with settings for hibernate:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="hibernate.connection.driver_class">org.h2.Driver</property> <property name="hibernate.connection.url">jdbc:h2:~/test</property> <property name="hibernate.connection.password">sa</property> <property name="hibernate.connection.username">sa</property> <property name="hibernate.dialect">org.hibernate.dialect.H2Dialect</property> <property name="hibernate.hbm2ddl.auto">create</property> <property name="hibernate.current_session_context_class">thread</property> <mapping class="com.demien.hibgeneric.domain.Region" /> <mapping class="com.demien.hibgeneric.domain.Country" /> </session-factory> </hibernate-configuration>
it's a simple configuration for working with H2 database. Beside information about connection with H2 database, you can see 2 entities in "mapping class" tags: Region and Country - we will create them little later.
3. Log4j configuration
To make logger work properly we have to create configuration file for it.
Location of this file is the same(src/main/resources), file name - log4j.properties. Content of file:
log4j.rootCategory=INFO, console log4j.appender.console=org.apache.log4j.ConsoleAppender log4j.appender.console.layout=org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern= %p %c: %m%n
- we will use log level "INFO" for our application. Of course in real application it's better to use WARNING or even ERROR level.
4. Entity creating.
Let's create two simple entity.
Nothing special here. Just 2 entities with JPA tags for declaring table and column information. Implementing of Serializable class here can be ommitted. I included it because in the future this entity might be using in services with serialisation.
4.1. Region entity
package com.demien.hibgeneric.domain; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; @Entity(name = "REGIONS") public class Region implements Serializable { private static final long serialVersionUID = 8268800253932817168L; @Id @Column(name = "REGION_ID") private Integer regionId; @Column(name = "REGION_NAME") private String regionName; public Region() { } public Integer getRegionId() { return regionId; } public void setRegionId(Integer regionId) { this.regionId = regionId; } public String getRegionName() { return regionName; } public void setRegionName(String regionName) { this.regionName = regionName; } @Override public String toString() { return "[REGION_ID=" + getRegionId().toString() + " REGION_NAME=" + getRegionName() + "]"; // return "["+getRegionId().toString()+"] "+getRegionName(); } }
It has relationship many-to-one with region entity.
package com.demien.hibgeneric.domain; import java.io.Serializable; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Entity; @Entity(name = "COUNTRIES") public class Country implements Serializable { private static final long serialVersionUID = 7566184847780781908L; @Id @Column(name = "COUNTRY_ID") private String countryId; @Column(name = "COUNTRY_NAME") private String countryName; @ManyToOne(cascade = CascadeType.ALL) @JoinColumn(name = "REGION_ID") private Region region; public Country() { } public String getCountryId() { return countryId; } public void setCountryId(String countryId) { this.countryId = countryId; } public String getCountryName() { return countryName; } public void setCountryName(String countryName) { this.countryName = countryName; } public Region getRegion() { return region; } public void setRegion(Region region) { this.region = region; } @Override public String toString() { return "[COUNTRY_ID=" + getCountryId().toString() + " COUNTRY_NAME=" + getCountryName() + " REGION=" + region.toString() + "]"; // return "["+getCountryId()+"] "+getCountryName(); } }
It's most interesting part of the post. Of couce, we can create dao without any interfaces, but using pair daoInterface+daoImplementation is VERY VERY common, so let's do it this way.
5.1. Dao interface - declaring methods(contract) of dao.
package com.demien.hibgeneric.dao; import java.util.List; import java.util.Map; public interface IGenericDAO<T> { public T get(Class<T> cl, Integer id); public T save(T object); public void update(T object); public void delete(T object); public List<T> query(String hsql, Map<String, Object> params); }
5.2. Dao interface implementation - dao itself.
package com.demien.hibgeneric.dao; import java.util.List; import java.util.Map; import org.apache.log4j.Logger; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.SessionFactory; public class GenericDAOImpl<T> implements IGenericDAO<T> { private Logger LOGGER; private SessionFactory sessionFactory; public GenericDAOImpl(Class<T> cl, SessionFactory sessionFactory) { this.LOGGER = Logger.getLogger(cl.getName() + "GenericDAO"); this.sessionFactory = sessionFactory; if (sessionFactory == null) throw new RuntimeException("Session factory is null!!!"); } @Override public T get(Class<T> cl, Integer id) { LOGGER.info("STARTED - get"); Session session = sessionFactory.getCurrentSession(); session.beginTransaction(); @SuppressWarnings("unchecked") T element = (T) session.get(cl, id); session.getTransaction().commit(); LOGGER.info("FINISHED - get"); return element; } @Override public T save(T object) { LOGGER.info("STARTED - save"); Session session = sessionFactory.getCurrentSession(); session.beginTransaction(); session.save(object); session.getTransaction().commit(); LOGGER.info("FINISHED - save"); return object; } @Override public void update(T object) { LOGGER.info("STARTED - update"); Session session = sessionFactory.getCurrentSession(); session.beginTransaction(); session.update(object); session.getTransaction().commit(); LOGGER.info("FINISHED - update"); } @Override public void delete(T object) { LOGGER.info("STARTED - delete"); Session session = sessionFactory.getCurrentSession(); session.beginTransaction(); session.delete(object); session.getTransaction().commit(); LOGGER.info("FINISHED - delete"); } @SuppressWarnings("unchecked") @Override public List<T> query(String hsql, Map<String, Object> params) { LOGGER.info("STARTED - query"); Session session = sessionFactory.getCurrentSession(); session.beginTransaction(); Query query = session.createQuery(hsql); if (params != null) { for (String i : params.keySet()) { query.setParameter(i, params.get(i)); } } List<T> result = null; if ((hsql.toUpperCase().indexOf("DELETE") == -1) && (hsql.toUpperCase().indexOf("UPDATE") == -1) && (hsql.toUpperCase().indexOf("INSERT") == -1)) { result = query.list(); LOGGER.info("FINISHED - query. Result size=" + result.size()); } else { LOGGER.info("FINISHED - query. "); } session.getTransaction().commit(); return result; } }
As you can see, we use generics in out dao without any binding to real entities!
6. Generic service
Here, the same thing as for previous step: interface+implementation. Service interface is extending dao interface to add some more useful methods.
Here, the same thing as for previous step: interface+implementation. Service interface is extending dao interface to add some more useful methods.
6.1. Service Interface
package com.demien.hibgeneric.service; import java.util.List; import com.demien.hibgeneric.dao.IGenericDAO; public interface IGenericService<T> extends IGenericDAO<T> { List<T> getAll(); void deleteAll(); }
6.2. Service
Mostly service methods just calling correspondig mothods of dao. Mostly, except 2 methods extended in service interface: the use dao.query method this different sql statements.
package com.demien.hibgeneric.service; import java.util.List; import java.util.Map; import org.hibernate.SessionFactory; import com.demien.hibgeneric.dao.GenericDAOImpl; import com.demien.hibgeneric.dao.IGenericDAO; public class GenericServiceImpl<T> implements IGenericService<T> { private IGenericDAO<T> dao; private Class<T> cl; public GenericServiceImpl(Class<T> cl, SessionFactory sessionFactory) { this.cl=cl; dao=new GenericDAOImpl<T>(cl, sessionFactory); } @Override public T get(Class<T> cl, Integer id) { //LOGGER.trace("STARTED - get"); return (T) dao.get(cl, id); } @Override public T save(T object) { return (T)dao.save(object); } @Override public void update(T object) { dao.update(object); } @Override public void delete(T object) { dao.delete(object); } @Override public List<T> query(String hsql, Map<String, Object> params) { return (List<T>)dao.query(hsql, params); } @Override public List<T> getAll() { return query("from "+cl.getName(), null); } @Override public void deleteAll() { query("delete from "+cl.getName(),null); } }
The same as with dao: just generics, no binding with real entity classes.
7. HibernateUtil class
What a hibernate based program without HibernateUtil class? :)It's a regular "helper" class for working with Hibernate. We will use getSessionFactory method from it.
package com.demien.hibgeneric; import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; import org.hibernate.service.ServiceRegistry; import org.hibernate.service.ServiceRegistryBuilder; public class HibernateUtil { private static final ServiceRegistry serviceRegistry; private static final SessionFactory ourSessionFactory; static { try { Configuration configuration = new Configuration(); configuration.configure(); serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties()).buildServiceRegistry(); ourSessionFactory = configuration.buildSessionFactory(serviceRegistry); } catch (Throwable ex) { throw new ExceptionInInitializerError(ex); } } public static SessionFactory getSessionFactory() { return ourSessionFactory; } public static Session getSession() throws HibernateException { return ourSessionFactory.openSession(); } }
8. Main application class
And now we finally ready to execute out application. In listing we will create 2 services for 2 different entities using ONLY ONE GENERIC SERVICE CLASS! Of course, nothing can stop you for using more 2 (as many as you want) with one service. Now in case of changing(for instance, changing transaction strategy) in DB part - we will have to change ONLY ONE DAO CLASS.package com.demien.hibgeneric; import java.util.List; import com.demien.hibgeneric.service.GenericServiceImpl; import com.demien.hibgeneric.service.IGenericService; import com.demien.hibgeneric.domain.Country; import com.demien.hibgeneric.domain.Region; public class App { public static void main(String[] args) { Region region=null; Country country=null; IGenericService<Region> regionService = new GenericServiceImpl<Region>( Region.class, HibernateUtil.getSessionFactory()); IGenericService<Country> countryService = new GenericServiceImpl<Country>( Country.class, HibernateUtil.getSessionFactory()); regionService.deleteAll(); region = new Region(); region.setRegionId(1); region.setRegionName("Africa"); regionService.save(region); country=new Country(); country.setCountryId("EGY"); country.setCountryName("Egypt"); country.setRegion(region); countryService.save(country); country=new Country(); country.setCountryId("TUN"); country.setCountryName("Tunis"); country.setRegion(region); countryService.save(country); region = new Region(); region.setRegionId(2); region.setRegionName("America"); regionService.save(region); country=new Country(); country.setCountryId("CAN"); country.setCountryName("Canada"); country.setRegion(region); countryService.save(country); country=new Country(); country.setCountryId("USA"); country.setCountryName("USA"); country.setRegion(region); countryService.save(country); List<Country> countryList = countryService.getAll(); if (countryList != null) { for (Country c : countryList) { System.out.println(c.toString()); } } } }
9. Result
All works fine ![COUNTRY_ID=EGY COUNTRY_NAME=Egypt REGION=[REGION_ID=1 REGION_NAME=Africa]] [COUNTRY_ID=TUN COUNTRY_NAME=Tunis REGION=[REGION_ID=1 REGION_NAME=Africa]] [COUNTRY_ID=CAN COUNTRY_NAME=Canada REGION=[REGION_ID=2 REGION_NAME=America]] [COUNTRY_ID=USA COUNTRY_NAME=USA REGION=[REGION_ID=2 REGION_NAME=America]]
p.s.
final directory structure
sources can be downloaded from here.
This is the best article, of those that i read,about DAO and Hibernate for beginners!
ReplyDelete