One of those features was an ease of building simple CRUD applications really fast. In my opinion, the major concepts allowing building CRUD so easy are:
* Automatic Dao objects generation
* Provided availability of the most important methods like: retrieve by PK, select by criteria, selection with limited results
* Criteria abstraction layer
The funny stuff is that about year ago I have started working on a private project and I was missing this features, I couldn't name them though.
Let's start with automatic Dao generation first. I don't like the idea of code generation. I'm a fan of meta-data and stuff like that, but that's it. I have focused on a flexible DaoAdapter object. This object could serve as a parent to any dao implementation providing the most important methods.
The interface of my Dao is:
import java.util.List;
import torfox.dao.helper.FilterCriteria;
import torfox.dao.helper.SortCriteria;
/**
* Basic interface for data access objects
*
* @author Bartosz Jankiewicz
*
*/
public interface Dao
{
public final static String ID = "id";
/**
* Saves object to database, creating new record when necessary.
*
* @param object Object being saved
* @throws DataAccessException
*/
public E saveObject( E object) throws DataAccessException;
/**
* Returns object that corresponds given identifier
*
* @param id Object identifier
* @return Entity that matches identifier
* @throws DataAccessException
*/
public E retrieveById( Integer id) throws DataAccessException;
/**
* Returns object that corresponds given identifier. It's for String
* based identity columns.
*
* @param id Object identifier
* @return Matching object or null.
* @throws DataAccessException
*/
public E retrieveById( String id) throws DataAccessException;
/**
* Removes object from database.
*
* @param object
* @throws DataAccessException
*/
public void removeObject( E object) throws DataAccessException;
/**
* Usuwa z bazy obiekt o określonym identyfikatorze i typie na podstawie generic
*
* @param id
*/
public void removeObject( Integer id) throws DataAccessException;
/**
* Loading objects basing on the given criteria parameter.
*
* @param page Page number - used for paging results
* @param count Number of results per page - used in conjunction with page
* @param filter Filter object
* @param criteria Optional sorting
* @return List of objects
*/
public ListdoSelectPaged( final int page, final int count, final FilterCriteria filter, final SortCriteria ... criteria) throws DataAccessException;
/**
* Counts objects matchign criteria. Usually used for paging
*
* @param filterCriteria Yes, that's criteria
* @return
*/
public int getObjectCount( FilterCriteria filterCriteria) throws DataAccessException;
/**
* Retrieves a single entity matching the criteria. The implementation
* could throw an exception if 0 or more than 1 objects are matching the
* criteria.
*
* @param filterCriteria Criteria object
* @return Matching entity
*/
public E getSingleEntity( FilterCriteria filterCriteria) throws DataAccessException;
/**
* Selects all objects - no paging is provided by this method.
*
*/
public ListdoSelect( FilterCriteria filter, SortCriteria ... criteria) throws DataAccessException;
}
FilterCriteria and SortCriteria implementations is pretty simple. I could also use JPA (the final spec. wasn't ready when I was working on that) or Hibernate DetachedCriteria implementation, but I wanted the implementation to be totally independent.
I have prepared an implementation of this interface using Spring+Hibernate framework:
/*
* @(#) DaoAdapter.java
*
* Copyright 2004 Bartosz Jankiewicz. All rights reserved.
*
*/
package mayflower.dao.hibernate;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import torfox.dao.Dao;
import torfox.dao.DataAccessException;
import torfox.dao.helper.ConjunctionCriteria;
import torfox.dao.helper.DisjunctionCriteria;
import tofox.dao.helper.FilterCriteria;
import torfox.dao.helper.NotCriteria;
import torfox.dao.helper.Operator;
import torfox.dao.helper.PropertyCompareCriteria;
import torfox.dao.helper.SortCriteria;
import torfox.dao.helper.ValueCompareCriteria;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Junction;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Property;
import org.hibernate.criterion.Restrictions;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* Business object access adapter
*
* @author bartosz.jankiewicz
*
* @param
*/
@SuppressWarnings("unchecked")
public abstract class DaoAdapterextends HibernateDaoSupport implements Dao
{
protected Logger logger = Logger.getLogger( getClass());
@Transactional( readOnly = false, propagation = Propagation.REQUIRES_NEW)
public E saveObject( E object)
{
getHibernateTemplate().saveOrUpdate( object);
return object;
}
public E retrieveById( Integer id) throws DataAccessException
{
FilterCriteria criteria = new ValueCompareCriteria( "id", id, Operator.EQ);
return getSingleEntity( criteria);
}
public E retrieveById( String id) throws DataAccessException
{
FilterCriteria criteria = new ValueCompareCriteria( "id", id, Operator.EQ);
return getSingleEntity( criteria);
}
public void removeObject( E object)
{
getHibernateTemplate().delete( object);
}
public void removeObject( Integer id)
{
Object object = getHibernateTemplate().get( getEntityClass(), id);
getHibernateTemplate().delete( object);
}
public ListdoSelectPaged( final int page, final int count, final FilterCriteria filter, final SortCriteria ... criteria)
{
ListentityList = getHibernateTemplate().executeFind( new HibernateCallback() {
public Object doInHibernate( Session session) throws HibernateException, SQLException
{
Criteria query;
try
{
query = buildQuery( session, filter, criteria);
}
catch (DataAccessException e)
{
throw new HibernateException( e);
}
addListAssociations( query);
if( count > 0)
{
query.setFirstResult( count * (page - 1)).setMaxResults( count);
}
return query.list();
}
});
return entityList;
}
public ListdoSelect( final FilterCriteria filter, final SortCriteria ... criteria)
{
ListentityList = getHibernateTemplate().executeFind( new HibernateCallback() {
public Object doInHibernate( Session session) throws HibernateException, SQLException
{
Criteria query;
try
{
query = buildQuery( session, filter, criteria);
}
catch (DataAccessException e)
{
throw new HibernateException( e);
}
addListAssociations( query);
return query.list();
}
});
return entityList;
}
public E getSingleEntity( final FilterCriteria filterCriteria)
{
E entity = (E) getHibernateTemplate().execute( new HibernateCallback() {
public Object doInHibernate( Session session) throws HibernateException, SQLException
{
Criteria query;
try
{
query = buildQuery( session, filterCriteria);
}
catch (DataAccessException e)
{
throw new HibernateException( e);
}
query = addEntityAssociations( query);
query.setResultTransformer( Criteria.ROOT_ENTITY);
return query.uniqueResult();
}
});
return entity;
}
public int getObjectCount( final FilterCriteria criteria)
{
return ((Number) getHibernateTemplate().execute( new HibernateCallback() {
public Object doInHibernate( Session session) throws HibernateException, SQLException
{
Criteria query;
try
{
query = buildQuery( session, criteria);
query.setProjection( Projections.rowCount());
}
catch (DataAccessException e)
{
throw new HibernateException( e);
}
addListAssociations( query);
return query.uniqueResult();
}
})).intValue();
}
protected Criteria buildQuery( Session session, FilterCriteria filterCriteria, SortCriteria ... criteria) throws DataAccessException
{
MapaliasMap = new HashMap (); //Mapa aliasów użytych przy tworzeniu filtrów i sortowaniu
Criteria query = session.createCriteria( getEntityClass());
Criterion criterion = decodeFiler( filterCriteria, query, aliasMap);
if( criterion != null)
{
query.add( criterion);
}
if( criteria != null && criteria.length > 0)
{
for( SortCriteria field : criteria)
{
addSort( query, field, aliasMap);
}
}
return query;
}
protected void addSort( Criteria criteria, SortCriteria sortCriteria, MapaliasMap)
{
if( sortCriteria == null) return;
Property property = getProperty( sortCriteria.getProperty(), criteria, aliasMap);
if( sortCriteria.isAsc())
{
criteria.addOrder( property.asc());
}
else
{
criteria.addOrder( property.desc());
}
}
protected Property getProperty( String path, Criteria criteria, MapaliasMap)
{
if( StringUtils.isEmpty( path)) return null;
String[] parts = path.split( "\\.");
if( parts.length > 1)
{
String newPath = parts[0];
String alias = null;
for( int part = 1; part < parts.length; part++)
{
if( !aliasMap.containsKey( newPath))
{
alias = "_alias" + aliasMap.size();
aliasMap.put( newPath, alias);
criteria.createAlias( newPath, alias);
newPath = alias;
}
else
{
alias = aliasMap.get( newPath);
}
newPath = alias + "." + parts[part];
}
return Property.forName( newPath);
}
return Property.forName( path);
}
private Criterion decodeFiler( FilterCriteria filterCriteria, Criteria query, MapaliasMap) throws DataAccessException
{
if( filterCriteria == null) return null;
if( filterCriteria instanceof ValueCompareCriteria)
{
ValueCompareCriteria criteria = (ValueCompareCriteria) filterCriteria;
Property property = getProperty( criteria.getProperty(), query, aliasMap);
switch( criteria.getOperator())
{
case EQ: return property.eq( criteria.getValue());
case GE: return property.ge( criteria.getValue());
case GT: return property.gt( criteria.getValue());
case LE: return property.le( criteria.getValue());
case LT: return property.lt( criteria.getValue());
case MA: return property.like( criteria.getValue().toString(), MatchMode.ANYWHERE);
case NE: return property.ne( criteria.getValue());
default: throw new DataAccessException( "Filter operator " + criteria.getOperator() + " not supported");
}
}
if( filterCriteria instanceof PropertyCompareCriteria)
{
PropertyCompareCriteria criteria = (PropertyCompareCriteria) filterCriteria;
Property property = getProperty( criteria.getProperty(), query, aliasMap);
Property otherProperty = getProperty( criteria.getOtherProperty(), query, aliasMap);
switch( criteria.getOperator())
{
case EQ: return property.eqProperty( otherProperty);
case GE: return property.geProperty( otherProperty);
case GT: return property.gtProperty( otherProperty);
case LE: return property.leProperty( otherProperty);
case LT: return property.ltProperty( otherProperty);
case NE: return property.neProperty( otherProperty);
default: throw new DataAccessException( "Filter operator " + criteria.getOperator() + " not supported");
}
}
if( filterCriteria instanceof ConjunctionCriteria)
{
Junction junction = Restrictions.conjunction();
for( FilterCriteria criteria : ((ConjunctionCriteria)filterCriteria).getCriterias())
{
junction.add( decodeFiler( criteria, query, aliasMap));
}
return junction;
}
if( filterCriteria instanceof DisjunctionCriteria)
{
Junction junction = Restrictions.disjunction();
for( FilterCriteria criteria : ((DisjunctionCriteria)filterCriteria).getCriterias())
{
junction.add( decodeFiler( criteria, query, aliasMap));
}
return junction;
}
if( filterCriteria instanceof NotCriteria)
{
return Restrictions.not( decodeFiler( ((NotCriteria)filterCriteria).getCriteria(), query, aliasMap));
}
throw new DataAccessException( "Filter criteria not supported: " + filterCriteria);
}
/**
* Returns managed entity class
*
* @return Managed entity class
*/
protected abstract ClassgetEntityClass();
/**
* TODO: adds additional associations to the criteria when calling select
* for list of objects.
* @param criteria Query object
* @return Modified Criteria object
*/
protected Criteria addListAssociations( Criteria criteria)
{
return criteria;
}
/**
* TODO: adds additional associations to the criteria when object relation
* requires that
* @param criteria Obiekt zapytania
* @return Mofied Criteria object
*/
protected Criteria addEntityAssociations( Criteria criteria)
{
return criteria;
}
}
What I have achieved is a very quick implementation of DAO objects providing the most commonly used methods. Using DaoAdapter all I have to do is to extend DaoAdapter and implement only 1 method providing managed object class. For those who think, that this class is already provided with the generics
So, an example of implementation of DAO for Person entity whould require the following steps:
1) create Person entity and mappings
2) create an empty PersonDao interface extending Dao
3) create PersonDaoImpl class extending DaoAdapter
4) implement getEntityClass method putting the single line of code:
return Person.class
Using this mini-framework allowed me to implement a simple CRUD application with 7 DAO objects within 1 day and it's working pretty well so far :)
Full implementation of the code can be downloaded from: http://torfox.pl/examples/dao_facade.zip
No comments:
Post a Comment