Overview

Skill Level: Intermediate

There is lot of code that gets repeated when the data access tier and the business service tier is developed for different relationships along with their unit testing partners. So there is a need for a framework which will solve this problem.

Ingredients

It is advisable to have an elementary knowledge of Java and web application development with Spring and Hibernate.

Step-by-step

  1. The Data Access Object (DAO) Tier Interface Pattern

    package com.ibm.framework.dao;

     

    import java.io.Serializable;

    import java.util.List;

     

    /**

     * @author Amritendu

     *

     */

    public interface DAO<T extends Object> {

         

          Serializable create(T t);

          T getById(Serializable id);

          T load(Serializable id);

          List<T> getAll();

          Serializable update(T t);

          void delete(T t);

          void deleteById(Serializable id);

          void deleteAll();

          Long count();

          boolean exists(Serializable id);

    }

    The DAO Interface contains all the CRUD operations on an entity object. The interface operations include create, read object by id, read all objects, update, delete by id, delete all objects, total count of the number of objects and whether the entity object exists. All entities extend the Serializable interface hence the return type for Objects are Serializable. The difference between load and getById is that load return a proxy without hitting the database but get hit the database and returns the specific object. Also load returns ObjectNotFoundException when no object is found, but get returns null if no match found.

  2. The Data Access Object (DAO) Tier Abstract Class Pattern

    package com.ibm.framework.dao.impl;

     

    import java.io.Serializable;

    import java.lang.reflect.ParameterizedType;

    import java.util.List;

     

    import org.hibernate.ObjectNotFoundException;

    import org.hibernate.Session;

    import org.hibernate.SessionFactory;

     

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

     

    import com.ibm.framework.dao.DAO;

     

    /**

     * @author Amritendu

     *

     */

    public abstract class AbstractDAO<T extends Object> implements DAO<T> {

     

          @Autowired

          private SessionFactory sessionFactory;

     

          private Class<T> domainClass;

     

          protected Session getSession() {

                return sessionFactory.getCurrentSession();

          }

     

    ¬†¬†¬†¬†¬† @SuppressWarnings(“unchecked”)

          private Class<T> getDomainClass() {

                if (domainClass == null) {

                      ParameterizedType thisType = (ParameterizedType) getClass().getGenericSuperclass();

                      this.domainClass = (Class<T>) thisType.getActualTypeArguments()[0];

                }

                return domainClass;

          }

     

          private String getDomainClassName() {

                return getDomainClass().getName();

          }

     

          public Serializable create(T t) {

                return getSession().save(t);

          }

     

    ¬†¬†¬†¬†¬† @SuppressWarnings(“unchecked”)

          public T getById(Serializable id) {

                return (T) getSession().get(getDomainClassName(), id);

          }

     

    ¬†¬†¬†¬†¬† @SuppressWarnings(“unchecked”)

          public T load(Serializable id) {

                return (T) getSession().load(getDomainClassName(), id);

          }

     

    ¬†¬†¬†¬†¬† @SuppressWarnings(“unchecked”)

          public List<T> getAll() {

    ¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬† return getSession().createQuery(“from ” + getDomainClassName()).list();

          }

     

          public Serializable update(T t) {

                return (Serializable) (getSession().merge(t));

          }

     

          public void delete(T t) {

                Object merged = null;

                try {

                      merged = getSession().merge(t);

                } catch (ObjectNotFoundException e) {

                      return;

                }

                getSession().delete(merged);

          }

     

          public void deleteById(Serializable id) {

                delete(load(id));

          }

     

          public void deleteAll() {

    ¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬† getSession().createQuery(“delete ” + getDomainClassName()).executeUpdate();

          }

     

          public Long count() {

    ¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬† return (Long) getSession().createQuery(“select count(*) from ” + getDomainClassName()).uniqueResult();

          }

     

          public boolean exists(Serializable id) {

                return (getById(id) != null);

          }

    }

    The AbstractDAO abstract class implements all the methods of the DAO interface using Java Generics. The entity type acts as a parameter to the generic AbstractDAO class. All DAO implementations inherit from the AbstractDAO generic class. There is no need to change any implementation of the CRUD operations until and unless you want to override, the specific implementation and add your own specific implementation for any of the methods. The Hibernate SessionFactory which is thread-safe is initialized with the @Autowired annotation. Also the generic domain class of the entity is initialized using Java Reflection API. The SessionFactory’s getCurrentSession method is used to retrieve the session on which all the operations like get, load, merge, delete and save are called. The methods which are specific to the entity for example getByEmail on an Employee entity are declared in the interface extending the DAO interface and defined in the DAO implementation extending the AbstractDAO class. Those operations are not generic CRUD implementations and hence are specific to the implementations not included in the framework.

  3. The Data Access Object (DAO) Tier Unit Testing Abstract Class Pattern

    package com.ibm.framework.dao;

     

    import java.util.List;

     

    import org.junit.Assert;

    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 org.springframework.test.context.transaction.TransactionConfiguration;

    import org.springframework.transaction.annotation.Transactional;

     

    import com.ibm.framework.entity.AbstractIdentifierObject;

     

    /**

     * @author Amritendu

     *

     */

     

    @SuppressWarnings(“rawtypes”)

    @RunWith( SpringJUnit4ClassRunner.class )

    @ContextConfiguration( locations = { “classpath:context.xml” } )

    @TransactionConfiguration( defaultRollback = true )

    @Transactional

    public abstract class AbstractDAOTest<

    IDAO extends DAO,

                                        BO extends AbstractIdentifierObject

    > {

         

          @Autowired

          protected IDAO dao;

      

          protected BO bo1, bo2;

      

          public abstract void setParams();

       

          @Test

          public void testGetAll() {

                Assert.assertEquals(0L, dao.getAll().size());

          }

         

    ¬†¬†¬†¬†¬† @SuppressWarnings(“unchecked”)

          @Test

          public void testCreate() {

                setParams();

                dao.create(bo1);

                dao.create(bo2);

                Assert.assertEquals(2L, dao.getAll().size());

          }

     

    ¬†¬†¬†¬†¬† @SuppressWarnings(“unchecked”)

          @Test

          public void testGetById() {

                setParams();

                dao.create(bo1);

                dao.create(bo2);

                Assert.assertEquals(2L, dao.getAll().size());

         

                List<BO> list = dao.getAll();

                BO bo3 = list.get(0);

               

                BO bo4 = (BO) dao.getById(bo3.getId());

                Assert.assertNotNull(bo4);

          }

         

    ¬†¬†¬†¬†¬† @SuppressWarnings(“unchecked”)

          @Test

          public void testLoad() {

                setParams();

                dao.create(bo1);

                dao.create(bo2);

                Assert.assertEquals(2L, dao.getAll().size());

         

                List<BO> list = dao.getAll();

                BO bo3 = list.get(0);

               

                BO bo4 = (BO) dao.load(bo3.getId());

                Assert.assertNotNull(bo4);

          }

         

    ¬†¬†¬†¬†¬† @SuppressWarnings(“unchecked”)

          @Test

          public void testDelete() {

                setParams();

                dao.create(bo1);

                dao.create(bo2);

                Assert.assertEquals(2L, dao.getAll().size());

               

                dao.delete(bo1);

                Assert.assertEquals(1L, dao.getAll().size());

          }

         

    ¬†¬†¬†¬†¬† @SuppressWarnings(“unchecked”)

          @Test

          public void testDeleteById() {

                setParams();

                dao.create(bo1);

                dao.create(bo2);

                Assert.assertEquals(2L, dao.getAll().size());

         

                List<BO> list = dao.getAll();

                BO bo3 = list.get(0);

               

                dao.deleteById(bo3.getId());

                Assert.assertEquals(1L, dao.getAll().size());

          }

         

    ¬†¬†¬†¬†¬† @SuppressWarnings(“unchecked”)

          @Test

          public void testDeleteAll() {

                setParams();

                dao.create(bo1);

                dao.create(bo2);

                Assert.assertEquals(2L, dao.getAll().size());

         

                dao.deleteAll();

                Assert.assertEquals(0L, dao.getAll().size());

          }

         

    ¬†¬†¬†¬†¬† @SuppressWarnings(“unchecked”)

          @Test

          public void testCount() {

               

                setParams();

                dao.create(bo1);

                dao.create(bo2);

               

                Assert.assertEquals(new Long(2), dao.count());

          }

         

    ¬†¬†¬†¬†¬† @SuppressWarnings(“unchecked”)

          @Test

          public void testExists() {

                setParams();

                dao.create(bo1);

                dao.create(bo2);

                Assert.assertEquals(2L, dao.getAll().size());

               

                List<BO> list = dao.getAll();

                BO bo3 = list.get(0);

                boolean doesExist = dao.exists(bo3.getId());

                Assert.assertEquals(true, doesExist);

          }

         

    ¬†¬†¬†¬†¬† @SuppressWarnings(“unchecked”)

          @Test

          public void testUpdate() {

                setParams();

                dao.create(bo1);

                dao.create(bo2);

                Assert.assertEquals(2L, dao.getAll().size());

     

                List<BO> list = dao.getAll();

                BO bo3 = list.get(0);

               

                dao.update(bo3);

                Assert.assertNotNull(bo3);

          }

    }

    The AbstractDAOTest abstract class is the generic implementation for unit testing the DAO classes which are inherited by the specific DAOTest classes. There is one abstract method called setParams which initializes the two entity objects. The parameters passed to the unit testing class includes the DAO implementation class along with the related entity which inherits from AbstractIdentifierObject mapped superclass. If there are more specific methods in addition to the CRUD methods those can be unit tested in the specific implementation DAOTest class. An iterative approach is presented which starts with the getAll method checking if there are no instances of the entity and then followed by a series of other CRUD methods which checks if there two instances of the entity and so on. Please note that the identifier of the entity object need not be hard-coded and should be derived from the get implementations. If there is a need to test something beyond the generic CRUD unit testing methods one can always override them and implement their specific methods. The unit tests are transactional in nature and gets rolled back at the end of the execution so that no data persists in the tables.

  4. The Business Service Tier Interface Pattern

    package com.ibm.framework.service;

     

    import java.io.Serializable;

    import java.util.List;

     

    /**

     * @author Amritendu

     *

     */

    public interface Service<T extends Object> {

     

          Serializable create(T t);

          T getById(Serializable id);

          T load(Serializable id);

          List<T> getAll();

          Serializable update(T t);

          void delete(T t);

          void deleteById(Serializable id);

          void deleteAll();

          Long count();

          boolean exists(Serializable id);

    }

    The Service interface contains all the CRUD operations on a data transfer object (DTO). The DTO is received from the web tier and converted to an entity object in the DAO tier. All the CRUD operations are present in this interface. The DTO objects inherit from the Serializable interface hence all the return types are Serializable. It is a good idea to have the DTO present in the business service tier because the related objects can be set in the conversion to the entity objects and persisted in one go.

  5. The Business Service Tier Abstract Class Pattern

    package com.ibm.framework.service.impl;

     

    import java.io.Serializable;

    import java.util.ArrayList;

    import java.util.List;

     

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

     

    import com.ibm.framework.dao.DAO;

    import com.ibm.framework.mapper.AbstractMapper;

    import com.ibm.framework.service.Service;

     

    /**

     * @author Amritendu

     *

     */

    @SuppressWarnings({ “rawtypes” })

    public abstract class AbstractService     <     IDAO extends DAO,

                                                    DTO extends Object,

                                                    MAP extends AbstractMapper,

                                                    BO extends Object >

                                                    implements Service<DTO> {

       

          @Autowired

          protected IDAO dao;

      

          @Autowired

          protected MAP mapper;

      

          @Override

    ¬†¬†¬† ¬† @SuppressWarnings(“unchecked”)

          public Serializable create(DTO dto) {

                return dao.create(mapper.mapDtoToEntity(dto));

          }

       

    ¬†¬†¬† ¬† @SuppressWarnings(“unchecked”)

          public DTO getById(Serializable id) {

                return (DTO) mapper.mapEntityToDto(dao.getById(id));

          }

         

    ¬†¬†¬†¬†¬† @SuppressWarnings(“unchecked”)

          public DTO load(Serializable id) {

                return (DTO) mapper.mapEntityToDto(dao.load(id));

          }

         

    ¬†¬†¬†¬†¬† @SuppressWarnings(“unchecked”)

          public List<DTO> getAll() {

                List<BO> boList = dao.getAll();

                List<DTO> dtoList = new ArrayList<DTO>();

                for(BO bo:boList) {

                      dtoList.add((DTO) mapper.mapEntityToDto(bo));

                }

                return dtoList;

          }

         

    ¬†¬†¬†¬†¬† @SuppressWarnings(“unchecked”)

          public Serializable update(DTO dto) {

                return (dao.update(mapper.mapDtoToEntity(dto)));

          }

         

    ¬†¬†¬†¬†¬† @SuppressWarnings(“unchecked”)

          public void delete(DTO dto) {

                dao.delete(mapper.mapDtoToEntity(dto));

        }

         

          public void deleteById(Serializable id) {

                dao.deleteById(id);

          }

         

          public void deleteAll() {

                dao.deleteAll(); 

          }

         

          public Long count() {

                return dao.count();

          }

         

          public boolean exists(Serializable id) {

                return dao.exists(id);

          }

    }

    The AbstractService abstract class is the generic implementation of the business service implementation. All services inherit from this base class. The class takes the DAO, mapper, DTO and entity object as an input. The mapper converts an entity object to DTO and vice versa. The methods like delete, update and create takes the DTO as a parameter, converts the DTO to an entity via the mapper and then calls the DAO tier to persist or remove the object. The methods like getById and getAll takes the identifier as an input, calls the DAO to access the entity object and then returns the DTO by converting the entity to a DTO via the mapper. All complex relationships can be handled via the mapper implementation and when one implements the framework there is only need to code the entity and the mapper rest all are taken care by the framework. The DAO and the mapper are embedded in the AbstractService via the @Autowired annotation.

  6. The Business Service Tier Unit Testing Abstract Class Pattern

    package com.ibm.framework.service;

     

    import java.util.List;

     

    import org.junit.Assert;

    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 org.springframework.test.context.transaction.TransactionConfiguration;

    import org.springframework.transaction.annotation.Transactional;

     

    import com.ibm.framework.dto.AbstractIdentifierDTO;

     

    /**

     * @author Amritendu

     *

     */

     

    @SuppressWarnings(“rawtypes”)

    @RunWith( SpringJUnit4ClassRunner.class )

    @ContextConfiguration( locations = { “classpath:context.xml” } )

    @TransactionConfiguration( defaultRollback = true )

    @Transactional

    public abstract class AbstractServiceTest<IService extends Service,

                                              BO extends AbstractIdentifierDTO> {

     

          @Autowired

          protected IService service;

      

          protected BO bo1, bo2;

      

          public abstract void setParams();

       

          @Test

          public void testGetAll() {

                Assert.assertEquals(0L, service.getAll().size());

          }

         

    ¬†¬†¬†¬†¬† @SuppressWarnings(“unchecked”)

          @Test

          public void testCreate() {

                setParams();

                service.create(bo1);

                service.create(bo2);

                Assert.assertEquals(2L, service.getAll().size());

          }

     

    ¬†¬†¬†¬†¬† @SuppressWarnings(“unchecked”)

          @Test

          public void testGetById() {

                setParams();

                service.create(bo1);

                service.create(bo2);

                Assert.assertEquals(2L, service.getAll().size());

         

                List<BO> list = service.getAll();

                BO bo3 = list.get(0);

               

                BO bo4 = (BO) service.getById(bo3.getId());

                Assert.assertNotNull(bo4);

          }

         

    ¬†¬†¬†¬†¬† @SuppressWarnings(“unchecked”)

          @Test

          public void testLoad() {

                setParams();

                service.create(bo1);

                service.create(bo2);

                Assert.assertEquals(2L, service.getAll().size());

         

                List<BO> list = service.getAll();

                BO bo3 = list.get(0);

               

                BO bo4 = (BO) service.load(bo3.getId());

                Assert.assertNotNull(bo4);

          }

         

    ¬†¬†¬†¬†¬† @SuppressWarnings(“unchecked”)

          @Test

          public void testDelete() {

                setParams();

                service.create(bo1);

                service.create(bo2);

                Assert.assertEquals(2L, service.getAll().size());

               

                List<BO> list = service.getAll();

                BO bo3 = list.get(0);

               

                service.delete(bo3);

                Assert.assertEquals(1L, service.getAll().size());

          }

         

    ¬†¬†¬†¬†¬† @SuppressWarnings(“unchecked”)

          @Test

          public void testDeleteById() {

                setParams();

                service.create(bo1);

                service.create(bo2);

                Assert.assertEquals(2L, service.getAll().size());

         

                List<BO> list = service.getAll();

                BO bo3 = list.get(0);

               

                service.deleteById(bo3.getId());

                Assert.assertEquals(1L, service.getAll().size());

          }

         

    ¬†¬†¬†¬†¬† @SuppressWarnings(“unchecked”)

          @Test

          public void testDeleteAll() {

                setParams();

                service.create(bo1);

                service.create(bo2);

                Assert.assertEquals(2L, service.getAll().size());

         

                service.deleteAll();

                Assert.assertEquals(0L, service.getAll().size());

          }

         

    ¬†¬†¬†¬†¬† @SuppressWarnings(“unchecked”)

          @Test

          public void testCount() {

               

                setParams();

                service.create(bo1);

                service.create(bo2);

               

                Assert.assertEquals(new Long(2), service.count());

          }

         

    ¬†¬†¬†¬†¬† @SuppressWarnings(“unchecked”)

          @Test

          public void testExists() {

                setParams();

                service.create(bo1);

                service.create(bo2);

                Assert.assertEquals(2L, service.getAll().size());

               

                List<BO> list = service.getAll();

                BO bo3 = list.get(0);

                boolean doesExist = service.exists(bo3.getId());

                Assert.assertEquals(true, doesExist);

          }

         

    ¬†¬†¬†¬†¬† @SuppressWarnings(“unchecked”)

          @Test

          public void testUpdate() {

                setParams();

                service.create(bo1);

                service.create(bo2);

                Assert.assertEquals(2L, service.getAll().size());

     

                List<BO> list = service.getAll();

                BO bo3 = list.get(0);

               

                service.update(bo3);

                Assert.assertNotNull(bo3);

          }

     

    }

    The AbstractServiceTest abstract class is the generic implementation for unit testing the Service classes which are inherited by the specific ServiceTest classes. There is one abstract method called setParams which initializes the two DTO objects. The parameters passed to the unit testing class includes the Service implementation class along with the related DTO which inherits from AbstractIdentifierDTO superclass. One can always test the other specific methods of the Service implementation and also can override any generic implementation if the need arises. Please note that the identifier of the DTO need not be hard-coded and should be derived from the get implementations.

  7. Conclusion

    The business service and data access tier is the core of the backend services in any transactional enterprise web application. The two tiers can be developed in Spring like shown in this document or Java EE technologies like Enterprise Java Beans (EJB). The front-end technologies and the web tier can be developed in Struts, Spring, JSF, JSP/Servlet, JavaScript oriented frameworks or other variety of technologies. If the front-end is in a non-Java environment the business service tier can be converted to a web service and consumed from the non-Java environment. This framework is particularly useful in developing rapid CRUD single-page applications (SPA). I hope that you enjoyed reading this document as much as I enjoyed writing it.

Join The Discussion