Monday, November 18, 2013

DataNucleus 3.0 vs Hibernate 3.5

DataNucleus Access Platform, as stated at the official product site, is the most standards-compliant Open Source Java persistence product in existence. It is fully compliant with the JDO1JDO2JDO2.1JDO2.2JDO3JPA1 and JPA2 Java standards. It also complies with the OGC Simple Feature Specification for persistence of geospatial Java types to RDBMS. It utilities an OSGi-based plugin mechanism meaning that it is extremely extensible.
Hibernate, as stated at the official “about” page of the product, is a high-performance Object/Relational persistence and query service. The most flexible and powerful Object/Relational solution on the market, Hibernate takes care of the mapping from Java classes to database tables and from Java data types to SQL data types. It provides data query and retrieval facilities that significantly reduce development time.
For the purpose of this article we will use the aforementioned, well known, products as the actual implementations of the persistence API. Our goal is to be able to compare their performance when applying CRUD (Create – Retrieve – Update – Delete) operations against a database.
To do so, we are going to implement two distinct Spring based WEB applications that will act as our “test bases”. The two applications will be identical in terms of their service and data access layer definitions – interfaces and implementation classes. For data access we will utilize JPA2 as the Java Persistence API. For the database we will use an embedded Derby instance. Last but not least we are going to implement and perform the same suit of performance tests against the five “basic” data access operations described below :
  • Persist a record
  • Retrieve a record by its ID
  • Retrieve all records
  • Update an existing record
  • Delete a record
All tests are performed against a Sony Vaio with the following characteristics :
  • System : openSUSE 11.1 (x86_64)
  • Processor (CPU) : Intel(R) Core(TM)2 Duo CPU T6670 @ 2.20GHz
  • Processor Speed : 1,200.00 MHz
  • Total memory (RAM) : 2.8 GB
  • Java : OpenJDK 1.6.0_0 64-Bit
The following tools are used :
Final notices :
  • You can download the full source code for the two “test bases” here and here. These are Eclipse – Maven based projects.
  • In order to be able to compile and run the tests yourself you will need to install the Java Benchmarking framework binary – jar files to your Maven repository. Alternatively, as a “one click” solution, you may use the Java Benchmarking Maven bundle created by us. You can download it from here, unzip it to your Maven repository and you are good to go.
The “test bases” …
We will start by providing information on how we have implemented the “test base” projects. This is imperative in order to be crystal clear about the specifics of the environment our tests will run against. As previously stated, we have implemented two multi-tier Spring based WEB applications. In each application two layers have been implemented, the Service Layer and the Data Access Layer. These layers have identical definitions – interfaces and implementation specifics.
Our domain model consists of just an “Employee” object. The Service Layer provides a trivial “business” service that exposes CRUD (Create – Retrieve – Update – Delete) functionality for the “Employee” object whereas the Data Access Layer consists of a trivial Data Access Object that utilizes Spring JpaDaoSupport abstraction in order to provide the actual interoperability with the database.
Below are the Data Access Layer specific classes :
import javax.annotation.PostConstruct;
import javax.persistence.EntityManagerFactory;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.javacodegeeks.springdatanucleus.dto.EmployeeDTO;


@Repository("employeeDAO")
public class EmployeeDAO extends JpaDAO {
 
 @Autowired
 EntityManagerFactory entityManagerFactory;
 
 @PostConstruct
 public void init() {
  super.setEntityManagerFactory(entityManagerFactory);
 }
 
}
As you can see our Data Access Object (DAO) extends the JpaDAO class. This class is presented below :
import java.lang.reflect.ParameterizedType;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceException;
import javax.persistence.Query;

import org.springframework.orm.jpa.JpaCallback;
import org.springframework.orm.jpa.support.JpaDaoSupport;

public abstract class JpaDAO extends JpaDaoSupport {
 protected Class entityClass;

 @SuppressWarnings("unchecked")
 public JpaDAO() {
  ParameterizedType genericSuperclass = (ParameterizedType) getClass()
    .getGenericSuperclass();
  this.entityClass = (Class) genericSuperclass
    .getActualTypeArguments()[1];
 }

 public void persist(E entity) {
  getJpaTemplate().persist(entity);
 }

 public void remove(E entity) {
  getJpaTemplate().remove(entity);
 }
 
 public E merge(E entity) {
  return getJpaTemplate().merge(entity);
 }
 
 public void refresh(E entity) {
  getJpaTemplate().refresh(entity);
 }

 public E findById(K id) {
  return getJpaTemplate().find(entityClass, id);
 }
 
 public E flush(E entity) {
  getJpaTemplate().flush();
  return entity;
 }
 
 @SuppressWarnings("unchecked")
 public List findAll() {
  Object res = getJpaTemplate().execute(new JpaCallback() {

   public Object doInJpa(EntityManager em) throws PersistenceException {
    Query q = em.createQuery("SELECT h FROM " +
      entityClass.getName() + " h");
    return q.getResultList();
   }
   
  });
  
  return (List) res;
 }

 @SuppressWarnings("unchecked")
 public Integer removeAll() {
  return (Integer) getJpaTemplate().execute(new JpaCallback() {

   public Object doInJpa(EntityManager em) throws PersistenceException {
    Query q = em.createQuery("DELETE FROM " +
      entityClass.getName() + " h");
    return q.executeUpdate();
   }
   
  });
 }
 
}
Following is our domain class, the EmployeeDTO class :
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "EMPLOYEE")
public class EmployeeDTO implements java.io.Serializable {
 
 private static final long serialVersionUID = 7440297955003302414L;

 @Id
 @Column(name="employee_id")
 private long employeeId;
 
 @Column(name="employee_name", nullable = false, length=30)
 private String employeeName;
 
 @Column(name="employee_surname", nullable = false, length=30)
 private String employeeSurname;
 
 @Column(name="job", length=50)
 private String job;
  
 public EmployeeDTO() {
 }

 public EmployeeDTO(int employeeId) {
  this.employeeId = employeeId;  
 }

 public EmployeeDTO(long employeeId, String employeeName, String employeeSurname,
   String job) {
  this.employeeId = employeeId;
  this.employeeName = employeeName;
  this.employeeSurname = employeeSurname;
  this.job = job;
 }

 public long getEmployeeId() {
  return employeeId;
 }

 public void setEmployeeId(long employeeId) {
  this.employeeId = employeeId;
 }

 public String getEmployeeName() {
  return employeeName;
 }

 public void setEmployeeName(String employeeName) {
  this.employeeName = employeeName;
 }

 public String getEmployeeSurname() {
  return employeeSurname;
 }

 public void setEmployeeSurname(String employeeSurname) {
  this.employeeSurname = employeeSurname;
 }

 public String getJob() {
  return job;
 }

 public void setJob(String job) {
  this.job = job;
 }
}
Last but not least the “business” service interface and implementation classes are presented below :
import java.util.List;

import com.javacodegeeks.springdatanucleus.dto.EmployeeDTO;

public interface EmployeeService {
 
 public EmployeeDTO findEmployee(long employeeId);
 public List findAllEmployees();
 public void saveEmployee(long employeeId, String name, String surname, String jobDescription) throws Exception;
 public void updateEmployee(long employeeId, String name, String surname, String jobDescription) throws Exception;
 public void saveOrUpdateEmployee(long employeeId, String name, String surname, String jobDescription) throws Exception;
 public void deleteEmployee(long employeeId) throws Exception;
 
}
import java.util.List;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.javacodegeeks.springdatanucleus.dao.EmployeeDAO;
import com.javacodegeeks.springdatanucleus.dto.EmployeeDTO;
import com.javacodegeeks.springdatanucleus.services.EmployeeService;

@Service("employeeService")
public class EmployeeServiceImpl implements EmployeeService {
 
 @Autowired
 private EmployeeDAO employeeDAO;

 @PostConstruct
 public void init() throws Exception {
 }
 
 @PreDestroy
 public void destroy() {
 }

 public EmployeeDTO findEmployee(long employeeId) {
  
  return employeeDAO.findById(employeeId);
  
 }
 
 public List findAllEmployees() {
  
  return employeeDAO.findAll();
  
 }
 
 @Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
 public void saveEmployee(long employeeId, String name, String surname, String jobDescription) throws Exception {
   
  EmployeeDTO employeeDTO = employeeDAO.findById(employeeId);
  
  if(employeeDTO == null) {
   employeeDTO = new EmployeeDTO(employeeId, name,surname, jobDescription);
   employeeDAO.persist(employeeDTO);
  }
  
 }
 
 @Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
 public void updateEmployee(long employeeId, String name, String surname, String jobDescription) throws Exception {
  
  EmployeeDTO employeeDTO = employeeDAO.findById(employeeId);
  
  if(employeeDTO != null) {
   employeeDTO.setEmployeeName(name);
   employeeDTO.setEmployeeSurname(surname);
   employeeDTO.setJob(jobDescription);
  }

 }
 
 @Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
 public void deleteEmployee(long employeeId) throws Exception {
  
  EmployeeDTO employeeDTO = employeeDAO.findById(employeeId);
  
  if(employeeDTO != null)
   employeeDAO.remove(employeeDTO);

 }
 
 @Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
 public void saveOrUpdateEmployee(long employeeId, String name, String surname, String jobDescription) throws Exception {

  EmployeeDTO employeeDTO = new EmployeeDTO(employeeId, name,surname, jobDescription);
  
  employeeDAO.merge(employeeDTO);
  
 }

}
What follows is the “applicationContext.xml” file that drives the Spring IoC container. The contents of this file is also identical between the two “test base” projects.


 

 

 
  
 

 
  
 


To be able to launch the Spring application from a Servlet container (do not forget that we have implemented Spring based WEB applications) we have included the following listener into the “web.xml” file for both our “test base” applications :

 org.springframework.web.context.ContextLoaderListener

The only file that is different between the two “test base” projects is the one that defines the actual implementation of the Java Persistent API (JPA) to be used – the “persistence.xml” file. Below is the one that we have used to utilize DataNucleus Access Platform :

 
 
  org.datanucleus.api.jpa.PersistenceProviderImpl
  com.javacodegeeks.springdatanucleus.dto.EmployeeDTO
  true
  
   
   
   
   
   
   
   

   
   
   
   
   
  
 
 

What follows is the “persistence.xml” file that we have used to utilize Hibernate as our JPA2 implementation framework :

 
 
  org.hibernate.ejb.hibernatePersistence
  com.javacodegeeks.springhibernate.dto.EmployeeDTO
  true
  
   
   
   
   
   
   

   
   
   
   
   
  
 
 

Finally we demonstrate the class that implements all test cases to be executed. This class is identical for both the “test base” projects :
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;

import java.util.List;
import java.util.concurrent.Callable;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import bb.util.Benchmark;

import com.javacodegeeks.springhibernate.dto.EmployeeDTO;
import com.javacodegeeks.springhibernate.services.EmployeeService;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"file:src/main/webapp/WEB-INF/applicationContext.xml"})
public class EmployeeServiceTest {

 @Autowired
 EmployeeService employeeService;
 
 @Test
 public void testSaveEmployee() {
  try {
   employeeService.saveEmployee(1, "byron", "kiourtzoglou", "master software engineer");
   employeeService.saveEmployee(2, "ilias", "tsagklis", "senior software engineer");
  } catch (Exception e) {
   fail(e.getMessage());
  }
 }
 
 @Test
 public void testFindEmployee() {
  assertNotNull(employeeService.findEmployee(1));
 }
 
 @Test
 public void testFindAllEmployees() {
  assertEquals(employeeService.findAllEmployees().size(), 2);
 }

 @Test
 public void testUpdateEmployee() {
  try {
   employeeService.updateEmployee(1, "panagiotis", "paterakis", "senior software engineer");
   assertEquals(employeeService.findEmployee(1).getEmployeeName(), "panagiotis");
  } catch (Exception e) {
   fail(e.getMessage());
  }
 }

 @Test
 public void testDeleteEmployee() {
  try {
   employeeService.deleteEmployee(1);
   assertNull(employeeService.findEmployee(1));
  } catch (Exception e) {
   fail(e.getMessage());
  }
 }

 @Test
 public void testSaveOrUpdateEmployee() {
  try {
   employeeService.saveOrUpdateEmployee(1, "byron", "kiourtzoglou", "master software engineer");
   assertEquals(employeeService.findEmployee(1).getEmployeeName(), "byron");
  } catch (Exception e) {
   fail(e.getMessage());
  }
 }
 
 @Test
 public void stressTestSaveEmployee() {
  
     Callable task = new Callable() { 
      public Integer call() throws Exception {
       int i;
       for(i = 3;i < 2048; i++) {
     employeeService.saveEmployee(i, "name-" + i, "surname-" + i, "developer-" + i);
       }
       return i;
      }
     };
      
     try {
   System.out.println("saveEmployee(...): " + new Benchmark(task, false, 2045));
  } catch (Exception e) {
   fail(e.getMessage());
  }

  assertNotNull(employeeService.findEmployee(1024));
  
 }
 
 @Test
 public void stressTestFindEmployee() {
  
     Callable task = new Callable() { 
      public Integer call() { 
       int i;
       for(i = 1;i < 2048; i++) {
        employeeService.findEmployee(i);
       }
       return i;
      }
     };
      
     try {
   System.out.println("findEmployee(...): " + new Benchmark(task, 2047));
  } catch (Exception e) {
   fail(e.getMessage());
  }

 }
 
 @Test
 public void stressTestFindAllEmployees() {
  
     Callable> task = new Callable>() { 
      public List call() {
       return employeeService.findAllEmployees();
      }
     };
      
     try {
   System.out.println("findAllEmployees(): " + new Benchmark(task));
  } catch (Exception e) {
   fail(e.getMessage());
  }

 }
 
 @Test
 public void stressTestUpdateEmployee() {
  
     Callable task = new Callable() { 
      public Integer call() throws Exception { 
       int i;
       for(i=1;i<2048 2047="" assertequals="" benchmark="" callable="" catch="" e.getmessage="" e="" employeeservice.findemployee="" employeeservice.updateemployee="" est="" fail="" false="" getemployeename="" i="" new="" new_developer-="" new_name-1="" new_name-="" new_surname-="" nteger="" public="" return="" stresstestdeleteemployee="" system.out.println="" task="" try="" updateemployee="" void="" xception=""> task = new Callable() { 
      public Integer call() throws Exception {
       int i;
       for(i = 1;i < 2048; i++) {
     employeeService.deleteEmployee(i);
       }
       return i;
      }
     };
      
     try {
   System.out.println("deleteEmployee(...): " + new Benchmark(task, false, 2047));
  } catch (Exception e) {
   fail(e.getMessage());
  }
  
  assertEquals(true, employeeService.findAllEmployees().isEmpty());

 }

}
The results …
All test results are presented in the graph below. The vertical axis represents the mean execution time for each test in microseconds (us) thus lower values are better. The horizontal axis represents the test types. As you can see from the test cases presented above, we insert a total number of 2047 “employee” records to the database. For the retrieval test cases (findEmployee(…) and findAllEmployees(…)) the benchmarking framework performed 60 repeats of each test case in order to calculate statistics. All other test cases are executed just once.
As you can see, Hibernate outperforms DataNucleus in every test case. Especially in the retrieval by ID (Find) scenario Hibernate is almost 9 times faster than DataNucleus!
To my opinion DataNucleus is a fine platform. It can be used when you want to handle data in all of its forms, wherever it is stored. This goes from persistence of data into heterogeneous data-stores, to providing methods of retrieval using a range of query languages.
The main advantage of using such a versatile platform to manage your application data, is that you don’t need to take significant time in learning the oddities of particular data-stores, or query languages. Additionally you can use a single common interface for all of your data, thus your team can concentrate their application development time on adding business logic and let DataNucleus take care of data management issues.
On the other hand versatility comes to a cost. Being a “hard-core” Object to Relational Mapping (ORM) framework, Hibernate easily outperformed DataNucleus in all of our ORM tests.
Like most of the time, its up to the application architect to decide what best suits his needs – versatility or performance, until theDataNucleus team evolve their product to the point where it can excel Hibernate that is ;-)
Happy coding and do not forget to share!
Byron

No comments: