How many times have you wished to have a generic DAO API? One which would provide you with CRUD (Create, Retrieve, Update, Delete) functionalities? Recently I started working on an open source project (sorry, it's very much in incubation now, so no names out yet) and I decided to use HB as persistence framework. In making that choice, I decided also that I was going to create a generic DAO API which would provide CRUD functionalities for entities using a surrogate key. As a refresher, a surrogate key is a Primary Key which hasn't got any business meaning but which just provides uniqueness to each record. It is composed by a single field which usually is of type numeric (int, long) or string.
Here follows the domain model for such API
The central part of the API is the IDao interface which provides the following methods:
- delete(T)
- List<T> findAll(Class<T>)
- T findByPk(Class<T>, Y)
- T findByPkAssumeExisting(Class<T>, Y)
- T saveOrUpdate(IIdentifiable<T>)
The Java interface follows:
/**
* Copyright (C) 2010 Jemos, http://www.jemos.co.uk Marco Tedone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package uk.co.jemos.persistence.domain.common.dao;
import java.io.Serializable;
import java.util.List;
import uk.co.jemos.persistence.domain.common.exceptions.PersistenceRuntimeException;
/**
*
* @author mtedone
*
*
*/
public interface IDao {
/**
* Persists or updates an entity to the database
*
* @param <T>
* An entity must be {@link Serializable}
* @param entity
* The entity to persist
* @return The entity PK, which in turn must be {@link Serializable}
*/
public <T extends Serializable> T saveOrUpdate(IIdentifiable<T> entity);
/**
* Given an entity type and its PK, it returns a (proxy to the) entity with
* its id set, or it throws a {@link PersistenceRuntimeException} if the
* entity is not found.
*
* <p>
* This method assumes the entity is available (either in some sort of local
* cache or in the database). If the entity is not available, a
* {@link PersistenceRuntimeException} will be thrown and this eventuality
* should be considered an error from the application perspective. Use this
* method if the entity should exist in the datastore and it should be
* considered an error if it doesn't (e.g. reference data should be in the
* datastore but it's not there)
* </p>
*
* <p>
* This method might returns a proxy to the entity, in which case attributes
* other that the identity will be initialised upon use. The only guarantee
* is that, if the entity is found, the identifier will be set with the PK
* value.
* </p>
*
* @param <T>
* The type of the entity
* @param entityType
* The type of the entity
* @param pk
* The PK value
*
* @throws PersistenceRuntimeException
* The entity does not exist
* @return an entity given its type and PK
*/
public <Y extends Serializable, T extends IIdentifiable<Y>> T findByPkAssumeExisting(
Class<T> entityType, Y pk);
/**
* Given an entity type and its PK it either returns the entity if found in
* the datastore or <code>null</code> if the entity hasn't been found in the
* database.
* <p>
* This method always triggers a trip to the datastore to check if the
* entity is there. If the entity is not there a <code>null</code> value is
* returned and this eventuality does not represent an exception from the
* application point of view. Use this method if you want either to retrieve
* an entity that you don't know if it exists in the datastore or if you
* want to check if an entity exists in the datastore.
* </p>
*
* @param <T>
* @param entityType
* The entity type
* @param pk
* The primary key value
* @return Either the entity if present in the datastore or a null value
*/
public <Y extends Serializable, T extends IIdentifiable<Y>> T findByPk(
Class<T> entityType, Y pk);
/**
* It retrieves a {@link List} of all entities for the given type or an
* empty list if none was found
*
* @param <T>
* The type of the entities to find
* @param clazz
* The type of the entities to find
* @return A {@link List} of all entities for the given type or an empty
* list if none was found
*/
public <T> List<T> findAll(Class<T> clazz);
/**
* It causes an entity to be removed from the datastore
*
* @param <Y>
* The entity must be a subtype of {@link Serializable}
* @param <T>
* The entity must be a subtype of {@link IIdentifiable}
* @param entityToDelete
* The entity to delete
*/
public <Y extends Serializable, T extends IIdentifiable<Y>> void delete(
T entityToDelete);
}
There are mainly two things to notice from the above interface:
- The concept of IIdentifiable<T> has been introduced. An identifiable is an entity with surrogate PK. A quick look at the domain model shows that an entity implements this interface, as well, of course, as the Serializable interface (which has not been shown in the diagram since it is given for granted, being this a Hibernate requirement)
- The findByPkAssumeExisting method. This method differs by its sibling findByPk in that it should be invoked only for entities which are expected to be available.This API signature reflects HB difference between Session.get (which goes always to the database and is implemented in the findByPK method) and load (which searches the entity in the persistence context first and it goes to the database if not found. The load functionality is implemented in the findByPkAssumeExisting method). The HB Session.load method though is a bit vicious and can lead to surprises if not understood properly...If an entity is not found for the given PK, HB will return a proxy with the id field set. However on the first attempt to get any other properties on that proxy, an HibernateException will be thrown (for a demonstration of such scenario please refer to the ReferenceImplementationIntegrationTest.testFindByPkAssumeExistingWithNonExistingRecord() test available from the source code attached this article.
Although the interface above does not depend strictly on Hibernate (the code does not show any HB imports), it has been designed with HB in mind and for the HB framework (or frameworks with similar functionalities, e.g. JPA). To provide a means to the API, I wrote a Reference Implementation (RI) available as attachment. The RI has been written using TDD, therefore the implementation has been fully tested both with unit and integration tests.
To use this API, simply extend the AReferenceDao class. Since this DAO API is also an implementation of the Strategy Pattern and Template Method pattern, your DAO (unless you require additional methods than those in the IDao interface) will simply have to declare a SessionFactory and provide a getter for it. For an example, please refer to ReferenceImplementationDao in the RI.
To get the example working you'll need a connection to a database (I used MySQL, therefore the SQL which accompanies the code and which runs as part of the build process counts on a mysql database connection ) update the pom.xml file with values to fit your environment. I also externalised all sensitive information (such as server name, port number, username and password). You can just subsitute placeholders (identified by ${...}) with direct values, or declare a <server>mysql-db-dev</mysql> element in your ~/.m2/settings.xml file.
The other file you'll have to tamper with to get the examples working is the src/test/resources/domain-common-spring-test-app.xml, especially the placeholders ${...} in the jemosDataSource bean.
Happy technology to everyone.
M.
1) tar.gz format
Download Jemos-persistence-common-1.0.0-SNAPSHOT-src.tar
2) zip format
Recent Comments