Thursday, January 30, 2014

Hibernate with generic DAO, H2 database and manual transactions

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:

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

}


4.2 Country entity.
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();
 }

}

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.

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.

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.

Thursday, January 23, 2014

Complex application directory structure with MAVEN

Example of creating application directory structure for maven.
On that article I want to describe "mockup" of application: directory structure which I usually use for large and complex applications. It is main older with parent(root) pom.xml file and sub-directories with sub-projects(small application components). Sometimes it very good then application is decomposed and we can just take one of it components and use it in another project. So, let's start!

1. creating parent(main project)
For creating "root" project(it called mockup-root) folder we can use maven archetype generation by next command:

mvn archetype:generate -DgroupId=com.demien.mockup -DartifactId=mockup-root -DarchtypeArtifactId=maven-archtype-quickstart -DinteractiveMode=false

It will create folder mockup-root with maven directory structure and pom.xml file inside.
Now we have to make some improvements for generated files by
1.1 editing root pom.xml(in mockup-root directory) file
   1.1.1. Let's create properties tag(or just add "application.name" element if it exists):

   <properties>
     <application.name>Demien :: mockup </application.name>
   </properties>
   Now all child projects will be able to use that name. Later we will define here some useful information like "spring version", "junit version" etc.
   Using just created parameter we can re-write project name in such tag(<name>): <name>${application.name}</name>
 
   1.1.2. change project packaging.
   Root project will be empty because it's just "container" for child projects. So current packaging tag should be replaced to:

<packaging>pom</packaging>

2. creating "cmd"(in mockup-root directory) files for building application and generating eclipse project file. 
Now we can create very simple script for building our application. Unfortunately eclipse works very bad with maven, so if you use eclipse in development it's better to create also script for generating eclipse project files. Of course in scripts you have to use YOUR path to maven. Or, better, put maven directory into system "classpath" variable and exclude first line at all.
 2.1 file _quick_build.cmd:

set path=c:\java\apache-maven-3.1.1\bin
set MAVEN_OPTS=-Xmx512m -Xms256m -XX:MaxPermSize=128m
mvn clean package -e -Dmaven.test.skip=true

 2.2 file _quick_eclipse.cmd

set path=c:\java\apache-maven-3.1.1\bin
set MAVEN_OPTS=-Xmx512m -Xms256m -XX:MaxPermSize=128m
mvn eclipse:clean eclipse:eclipse -Dmaven.test.skip=true

   Now you can try to build your project by running _quick_build. In my case I have output with my changed application name:
   [INFO] Building Demien :: mockup 1.0-SNAPSHOT
 
 
3. creating childs(subprojects inside mockup-root directory): 
  Most interesting part: creating our sub-projects. Commands for creating child projects:

mvn archetype:generate -DgroupId=com.demien.mockup -DartifactId=mockup-core -DarchtypeArtifactId=maven-archtype-quickstart -DinteractiveMode=false
mvn archetype:generate -DgroupId=com.demien.mockup -DartifactId=mockup-domain -DarchtypeArtifactId=maven-archtype-quickstart -DinteractiveMode=false
mvn archetype:generate -DgroupId=com.demien.mockup -DartifactId=mockup-database -DarchtypeArtifactId=maven-archtype-quickstart -DinteractiveMode=false
mvn archetype:generate -DgroupId=com.demien.mockup -DartifactId=mockup-rest -DarchtypeArtifactId=maven-archtype-quickstart -DinteractiveMode=false
mvn archetype:generate -DgroupId=com.demien.mockup -DartifactId=mockup-webapp -DarchtypeArtifactId=maven-archtype-webapp -DinteractiveMode=false

 Of course, real count (and names) of child projets depends of your application.

 After executing described commands we will have next directory structure:
<DIR>          mockup-core
<DIR>          mockup-database
<DIR>          mockup-domain
<DIR>          mockup-rest
<DIR>          mockup-webapp
           996 pom.xml
<DIR>          src
           139 _quick_build.cmd
           152 _quick_eclipse.cmd

 src directory we can delete, because we not going to keep any files in root project.

 4. finalization
Now let's make some steps for maintain our application:
4.1. Child cross-dependencies.
 In child projects we have to add cross-subproject dependencies.
  For instance, if in webapp project use domain project, we have to add such dependency:

 <dependency>
   <groupId>com.demien.mockup</groupId>
      <artifactId>mockup-domain</artifactId>
      <version>1.0-SNAPSHOT</version>
 </dependency>

 4.2 Child names
 To make child names more "pretty" we can rename them this way:
 <name>${application.name} :: mockup-webapp</name>

 5. final build
 After all, when I tried to build my application, I got next listing :
[INFO] Reactor Summary:
[INFO]
[INFO] Demien :: mockup .................................. SUCCESS [0.236s]
[INFO] Demien :: mockup :: mockup-core ................... SUCCESS [2.024s]
[INFO] Demien :: mockup :: mockup-domain ................. SUCCESS [0.160s]
[INFO] Demien :: mockup :: mockup-database ............... SUCCESS [0.157s]
[INFO] Demien :: mockup :: mockup-rest ................... SUCCESS [0.165s]
[INFO] Demien :: mockup :: mockup-webapp ................. SUCCESS [0.184s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

You can download this example from here.