I am trying to write a partially dynamic HQL query without resorting to the Criteria API for various reasons. I wanted to know if there is an easy way to short circuit a where restriction using HQLs expressions. For example, here is the original query which works fine:
SELECT customer
FROM Customer as customer
INNER JOIN customer.profile as profile
WHERE profile.status IN :statusCodes
AND profile.orgId IN :orgIds
StatusCodes is a list of Strings and orgIds is a list of Integers. However, either one is optional and shouldn't restrict if null is passed instead of a collection. I've tried to accomplish this like so:
SELECT customer
FROM Customer as customer
INNER JOIN customer.profile as profile
WHERE (:statusCodes IS NULL OR profile.status IN :statusCodes)
AND (:orgIds IS NULL OR profile.orgId IN :orgIds)
This didn't work unfortunately, but is there any other approach that might work, either with using different expressions or passing in default values?
EDIT: Just to be clear I'm looking for a way to use a NamedQuery, not dynamically building the query in any way.
SOLUTION: I used the extra query parameters to accomplish it. I created two helper methods:
private void setRequiredParameter(TypedQuery<?> query, String name, Object value) {
query.setParameter(name, value);
}
private void setOptionalParameter(TypedQuery<?> query, String name, Object value) {
query.setParameter(name, value);
query.setParameter(name + "Optional", value == null ? 1 : 0);
}
And the query like so:
SELECT customer
FROM Customer as customer
INNER JOIN customer.profile as profile
WHERE (:statusCodesOptional = 1 OR profile.status IN :statusCodes)
AND (:orgIdsOptional = 1 OR profile.orgId IN :orgIds)
If you absolutely must avoid dynamic queries, you can do so at the expense of two additional parameters:
SELECT customer
FROM Customer AS customer
JOIN customer.profile AS profile
WHERE (profile.status IN :statusCodes OR :statusCodeCount = 0)
AND (profile.orgId IN :orgIds OR :orgIdCount = 0)
In your Java code you would then do something like:
session.getNamedQuery("your.query.name")
.setParameterList("statusCodes", statusCodes)
.setParameter("statusCodeCount", statusCodes.length)
.setParameterList("orgIds", orgIds)
.setParameter("orgIdCount", orgIds.length);
You'll need to ensure arrays are zero-length rather than null
or supply additional if checks to handle null
scenario.
All that said, HQL is really better suited for well-defined (e.g. static) queries. You can work around dynamic parameters, you won't be able to work around dynamic sorting.
My suggestion is to put all parameters in a map and build the query dynamic, after building before execution set all parameters required by the query taking values from the map:
Map<String, Object> pars = new HashMap<String,Object>();
pars.put("statusCodes", statusCodes);
pars.put("orgIds", orgIds);
StringBuilder b = "SELECT customer FROM Customer as customer INNER JOIN customer.profile as profile where 1 = 1";
if (statusCodes != null) {
b.append(" and profile.status in :statusCodes");
}
if (orgIds != null) {
b.append(" and profile.orgId in :statusCodes");
}
...
Query q = session.createQuery(b.toString());
...
for (String p : q.getNamedParameters()) {
q.setParameter(p, pars.get(p));
}
Of course some improvements are needed for example throw exception when parameters is not set, use typed parameter if complexity is bigger than a few simple parameters and so on.
You'll have to generate your query dynamically:
StringBuilder hql =
new StringBuilder("SELECT customer FROM Customer as customer INNER JOIN customer.profile as profile where 1 = 1")
if (statusCodes != null) {
hql.append(" and profile.status IN :statusCodes");
}
if (orgIds != null) {
hql.append(" and profile.orgId IN :orgIds");
}
Of course, you will also have to set the parameters to the query only if they're not null.
来源:https://stackoverflow.com/questions/10640736/dynamic-hql-query-using-hql-expressions-rather-than-criteria