I applied Adam Bien’s Generic CRUD service which he described in his book, Real World Java EE patterns – Rethinking Best Practices, and it greatly simplified the CRUD operations for the project I am working on. He also discussed about it on his blog.
However, I have queries which vary considerably depending on some options selected by a user, so I needed to create my queries on the fly instead of having named queries which keep growing. I therefore extended the Generic CRUD service to generate dynamic queries.
In the CRUDService interface I added findByDynamicQuery():
public interface CrudService {
//...
<T> List<T> findByDynamicQuery(Class<T> type,
List<ParameterDefinition> parameters);
}
Class<T> type represents the entity class for the query to be made and List<ParameterDefinition> parameters is used for specifying the query parameters (more on that).
The DynamicQueryParameter
I needed to specify parameters for OR conditions as well; Map<String, Object> could not satisfy the requirement since each key can map to at most one value and some queries may define one key (field) for many values. E.g. “SELECT c FROM Candidate WHERE c.program = :prog1 OR c.program = :prog2″ (program twice).
To solve the problem I used List<ParameterDefinition> to specify the parameters. ParameterDefinition is a simple class that defines the parameter type, name and value:
public class ParameterDefinition {
private final String name;
private final Object value;
private final ParameterType paramType;
public ParameterDefinition(ParameterType paramType, String name, Object value) {
this.name = name;
this.value = value;
this.paramType = paramType;
}
public ParameterType getParameterType() {
return paramType;
}
public String getName() {
return name;
}
public Object getValue() {
return value;
}
}
ParameterType is defined using an Enum:
public enum ParameterType {
WITH, AND, OR
}
The dynamic version of the QueryParameter is therefore as follows:
public class DynamicQueryParameter {
private List<ParameterDefinition> parameters = null;
private DynamicQueryParameter(String name, Object value) {
this.parameters = new ArrayList<ParameterDefinition>();
addParameter(ParameterType.WITH, name, value);
}
public static DynamicQueryParameter with(String name, final Object value) {
return new DynamicQueryParameter(name, value);
}
public DynamicQueryParameter and(String name, final Object value) {
addParameter(ParameterType.AND, name, value);
return this;
}
public DynamicQueryParameter or(String name, final Object value) {
addParameter(ParameterType.OR, name, value);
return this;
}
public List<ParameterDefinition> parameters() {
return this.parameters;
}
private void addParameter(ParameterType paramType,
String name, final Object value)
{
parameters.add(new ParameterDefinition(paramType, name, value));
}
}
And finally the CrudService implementation:
@Stateless
@Local(CrudService.class)
@TransactionAttribute(TransactionAttributeType.MANDATORY)
public class CrudServiceBean implements CrudService {
@PersistenceContext
private EntityManager em;
//...
public <T> List<T> findByDynamicQuery(Class<T> type,
List<ParameterDefinition> parameters)
{
//generate query
String queryString = generateQuery(type, parameters);
Query query = this.em.createQuery(queryString);
//set parameters
int k = 1; //parameter position
for (ParameterDefinition param : parameters) {
query.setParameter(k++, param.getValue());
}
return query.getResultList();
}
//helper method
private <T> String generateQuery(Class<T> type,
List<ParameterDefinition> parameters)
{
final String VAR = "obj";
StringBuilder queryBuilder = new StringBuilder(
String.format("SELECT %s FROM %s %s",
VAR, type.getSimpleName(), VAR)
);
if (parameters.size() == 0) { //no conditions
return queryBuilder.toString();
}
//WHERE clause
Iterator<ParameterDefinition> itr = parameters.iterator();
int k = 1; //parameter position
ParameterDefinition firstParam = itr.next(); //always type WITH
queryBuilder.append(String.format(" WHERE %s.%s = ?%d",
VAR, firstParam.getName(), k++));
//append parameters
for ( ; itr.hasNext(); ) {
ParameterDefinition nextParam = itr.next();
queryBuilder.append(String.format(
" %s %s.%s = ?%s", nextParam.getParameterType(), VAR,
nextParam.getName(), k++));
}
return queryBuilder.toString();
}
}
The query can be constructed from a client as follows:
List<Candidate> candidate = crudServiceBean.findByDynamicQuery(Candidate.class,
DynamicQueryParameter.
with("id", "111").
and("program", "SE").
or("program", "IT").
and("mode", "FT").
parameters());
where Candidate is an entity bean and crudServiceBean is a reference to CrudServiceBean session bean.