REFACTOR: Clean dao code for a query with multiple optional params - java

Is there a possiblity to clean up such a dao method?
I don't like the 2 if's for each param, but there isn't something like "query.setSql()" so i could update the sql after adding the params and building the sql.
public List<OriginLabel> findByCriteria(Link1 l1, Link2 l2, String att) {
String sql = "FROM MyEntity e WHERE 1=1";
if(l1 != null){
sql += " AND e.link1 = :l1 ";
}
if(l2 != null){
sql += " AND e.link2 = :l2 ";
}
if(att != null){
sql += " AND e.attribute = :att ";
}
Query query = getEntityManager().createQuery(sql);
if(l1 != null){
query.setParameter("l1", l1);
}
if(l2 != null){
query.setParameter("l2", l2);
}
if(att != null){
query.setParameter("att", att);
}
return (List<MyEntity>)query.getResultList();
}

I store the parameters in a hashmap in the first if:
public List<OriginLabel> findByCriteria(Link1 l1, Link2 l2, String att) {
String sql = "FROM MyEntity e WHERE 1=1";
HashMap parameters = new HashMap();
if(l1 != null){
sql += " AND e.link1 = :l1 ";
parameters.put("l1", l1);
}
if(l2 != null){
sql += " AND e.link2 = :l2 ";
parameters.put("l2", l2);
}
if(att != null){
sql += " AND e.attribute = :att ";
parameters.put("att", l1);
}
Query query = getEntityManager().createQuery(sql);
SQLUtility.setParameters(query, parameters);
SQLUtility.setParameters (easy to implement, write it yourself) loops over the HashMap keys and sets the parameters on the query.

I would suggest looking into visitor design pattrn.
You can create visitor that visits each of your parameter (which become visitable) and while doing so adds condition to your query string.

Maybe QueryDSL http://www.querydsl.com/ is something you can use to improve your SQL-related code?
When using QueryDSL your statements would be built using only one if per parameter (and your statements will be build statically typed without any string acrobatics) :
public List<OriginLabel> findByCriteria(Link1 l1, Link2 l2, String att) {
QOriginLabel e = QOriginLabel.originLabel;
JPAQuery query = new JPAQuery(em).from(e);
if (l1 != null) {
query.where(e.link1.eq(l1));
}
if (l2 != null) {
query.where(e.link2.eq(l2));
}
if (att != null) {
query.where(e.attribute.eq(att));
}
return query.list(e);
}

If performance is not an issue, I like to code it like this:
public List<OriginLabel> findByCriteria(Link1 l1, Link2 l2, String att) {
String sql = "FROM MyEntity e " +
" WHERE e.link1 = isnull(:l1, e.link1) " +
" AND e.link2 = isnull(:l2, e.link2)" +
" AND e.attribute = isnull(:att, e.attribute)";
Query query = getEntityManager().createQuery(sql);
query.setParameter("l1", l1);
query.setParameter("l2", l2);
query.setParameter("att", att);
return (List<MyEntity>)query.getResultList();
}

Related

How can I find out the type of sparql query using the code?

String qb = "PREFIX dc: <http://purl.org/dc/elements/1.1/>\n" +
"INSERT DATA\n" +
"{ \n" +
" <http://example/book1> dc:title \"A new book\" ;\n" +
" dc:creator \"A.N.Other\" .\n" +
"}";
// Here I need to check what type of query I got
String type = ... //some code for checking
if (type == "select") {
ParsedTupleQuery q = (ParsedTupleQuery)parser.parseQuery(qb, null);
}else if(type == "costruct") {
ParsedGraphQuery q = (ParsedGraphQuery)parser.parseQuery(qb, null);
}else if(type == "update"){ //here can be insert or delete
ParsedUpdate q = parser.parseUpdate(qb, null);
}
I can't find a way to find out what type of query it is.
Maybe somebody's ever seen it before?
Rdf4j has a QueryParserUtil with a convenience method for this. You can use it as follows:
ParsedOperation operation = QueryParserUtil.parseOperation(QueryLanguage.SPARQL, qb, null);
if (operation instanceof ParsedTupleQuery) {
ParsedTupleQuery q = (ParsedTupleQuery)operation;
...
} else if (operation instanceof ParsedGraphQuery) {
ParsedGraphQuery q = (ParsedGraphQuery)operation;
...
} else if (operation instance ParsedUpdate) {
ParsedUpdate u = (ParsedUpdate)operation;
...
}

Is there a way to update PreparedStatement

Is there a simple way to add something to PreparedStatement?
For example:
ps = connection.prepareStatement("SELECT DISTINCT nazivpredmeta, iddokumenta, nazivdokumenta, putanja, datumskeniranja FROM skeniranidokumenti , predmet WHERE "
if(osnovnibroj!=null){
ps.add("predmet.osnovnibroj=? ");
ps.setObject(1, osnovnibroj);
}
if(iddepartmana!=null){
if(osnovnibroj!=null){
ps.add("AND skeniranidokumenti=? ");
ps.setObject(1, iddepartmana);
}else{
ps.add("skeniranidokumenti=? ");
ps.setObject(1, iddepartmana);
}
I know that there is not add method, my question is: is there something i can use?
No you can't.. Simply you can do like this:
String query="SELECT DISTINCT nazivpredmeta, iddokumenta, nazivdokumenta, putanja, datumskeniranja FROM skeniranidokumenti , predmet WHERE ";
if(osnovnibroj!=null){
query=query+"predmet.osnovnibroj=? ";
ps = connection.prepareStatement(query);
ps.setObject(1, osnovnibroj);
}
You could construct your SELECT statement using a StringBuilder, and then pass the completed string to connection.prepareStatement().
StringBuilder sb = new StringBuilder("SELECT DISTINCT nazivpredmeta, iddokumenta, nazivdokumenta, putanja, datumskeniranja FROM skeniranidokumenti , predmet WHERE TRUE");
if (osnovnibroj != null)
{
sb.append(" AND predmet.osnovnibroj = ?");
}
if (iddepartmana != null)
{
sb.append(" AND skeniranidokumenti = ?");
}
ps = connection.prepareStatement(sb.toString());
if (iddepartmana != null)
{
if (osnovnibroj != null)
{
ps.setObject(1, osnovnibroj);
ps.setObject(2, iddepartmana);
}
else
{
ps.setObject(1, iddepartmana);
}
}
else if (osnovnibroj != null)
{
ps.setObject(1, osnovnibroj);
}
Note that I've used the WHERE TRUE trick to simplify the logic of constructing the WHERE clause.

set parameter which is null in #NamedQuery (JPA QL 1.0)

I am writing a JPA QL named query that would search for documents. This should be done in only one query and I cannot switch to native SQL (that's non functional requirement).
The query I wrote looks as follows:
query = "select doc from Doc doc " +
" join doc.Csts cst " +
" where cst.cstFrstNm like :FrstNm " +
" and cst.cstLastNm like :LastNm " +
" and doc.resId = :id ";
This query is parametrized using following instructions:
query.setParameter("FrstNm ", firstName + '%');
query.setParameter("LastNm ", lastName + '%');
query.setParameter("id", resId);
firstName, lastName, resId in the above code are my search criteria and they are all Strings and they can be null. When one of them is null, it shouldn't be taken into consideration when the query is invoked.
For instance:
Let's say that: firstName = "John", lastName = "Doe" and resId is null, then the query should return all the entries from Doc table that are for user John Doe, no matter what is their resId.
I was trying to put additional OR and AND conditions into query that'd check if resId is null but it didn't work. I'm testing it on HSQLDB.
Is it any way to modify this JPA query to handle null values?
Two solutions:
First one, don't use a named query and craft a new query for each request:
boolean first = true;
query = query = "select doc from Doc doc " +
" join doc.Csts cst " +
if (firstName != null) {
query += (first ? " where " : " and ");
query += " cst.cstFrstNm like :FrstNm ";
first = false;
}
query += ";";
// ...
Second solution, you are using wildcard. If your parameter is empty, just put a '%' in your query, which will match all corresponding strings:
query.setParameter("FrstNm ", (firstName != null ? firstName : "") + '%');
query.setParameter("LastNm ", (lastName != null ? lastName : "") + '%');
If firstName is empty, your query will look like:
... cst.cstFrstNm like '%' ...
and will match all the values. It will result in the parameter not filtering any result, with a behaviour similar as if it wasn't present.
You may need to build the query dynamically to satisfy your parameters. For instance:
public List<Employee> findEmployees(String name, String deptName, String projectName, String city) {
StringBuffer query = new StringBuffer();
query.append("SELECT DISTINCT e ");
query.append("FROM Employee e LEFT JOIN e.projects p ");
query.append("WHERE ");
List<String> criteria = new ArrayList<String>();
if (name != null) { criteria.add("e.name = :name"); }
if (deptName != null) { criteria.add("e.dept.name = :dept"); }
if (projectName != null) { criteria.add("p.name = :project"); }
if (city != null) { criteria.add("e.address.city = :city"); }
if (criteria.size() == 0) {
throw new RuntimeException("no criteria");
}
for (int i = 0; i < criteria.size(); i++) {
if (i > 0) { query.append(" AND "); }
query.append(criteria.get(i));
}
Query q = em.createQuery(query.toString());
if (name != null) { q.setParameter("name", name); }
if (deptName != null) { q.setParameter("dept", deptName); }
if (projectName != null) { q.setParameter("project", projectName); }
if (city != null) { q.setParameter("city", city); }
return (List<Employee>)q.getResultList();
}
You migt also like to consider Criteria API if you are using JPA 2.0 for this kind of task.

preparing query based on input values in java

I dont know whether it is possible or not,But here is my question:
I am getting 13 input fields,based on input
Ex:String firstname=request.getParameter("firstname"); ......
I have to prepare sql where clause like if (firstname!=null){ where firstname='test' and ..}
Any advises for this kind of scenario.
Regards,
Raj
If I understand correctly, you would like to generate queries dynamically, depending on the value of input fields. There are frameworks helping to do that, like MyBatis. But you could roll your own solution with prepared statements :
String query = "select * from foo f";
List<String> clauses = new ArrayList<String>();
List<Object> parameters = new ArrayList<Object>();
if (firstName != null) {
clauses.add("f.name = ?");
parameters.add(firstName);
}
// ...
if (!clauses.isEmpty()) {
query += " where " + StringUtils.join(clauses, " and ");
}
PreparedStatement ps = connection.prepareStatement(query);
for (int i = 0; i < parameters.size(); i++) {
ps.setObject(i + 1, paremeters.get(i));
}
You could make it even better by supporting SQL types, by using the builder pattern, etc., but you should get the idea with this simple example.
I assume you are using a JDBC connection to your database. You should use prepared statements, otherwise you are wide open for SQL injection attacks.
The second question is how to prevent a WHERE clause involving a field which the user did not supply. There are many (2^13 == 8192) combinations, so it is not practical to have a different statement for each possible user input. It would be possible to build the prepared statement dynamically in your case:
String statement = "SELECT * FROM " + dbName + "." + tableName;
String condition = " WHERE";
List<String> params = new ArrayList<String>();
if ( firstname != null ){
statement += condition + " firstname = ?";
condition = " AND";
params.add(firstname);
}
if ( familyname != null ){
statement += condition + " familyname = ?";
condition = " AND";
params.add(familyname);
}
connection.prepareStatement(updateString);
Then you will need to add the contents of params when you execute the prepared statement.
You will need to dynamically build the query in Java or use a stored procedure that will not filter on a field if it is null.
I was curious about this as well so I created a new answer. This is what I came up with. It can be optimized but this does what you want using the Builder pattern. You can see from my test I pass in a null and it is omitted from the where string.
public class WhereBuilder {
private final String requestParm1;
private final String requestParm2;
private final String requestParm3;
private final String requestParm4;
private final String requestParm5;
private StringBuilder whereString = new StringBuilder();
public static class Builder {
private String requestParm1 = null;
private String requestParm2 = null;
private String requestParm3 = null;
private String requestParm4 = null;
private String requestParm5 = null;
private StringBuilder whereString = new StringBuilder("WHERE ");
public Builder() {}
public Builder requestParm1(String value) {
if (value != null) {
requestParm1 = value;
whereString.append(" requestParm1 = '" + requestParm1 + "' AND");
}
return this;
}
public Builder requestParm2(String value) {
if (value != null) {
requestParm2 = value;
whereString.append(" requestParm2 = '" + requestParm2 + "' AND");
}
return this;
}
public Builder requestParm3(String value) {
if (value != null) {
requestParm3 = value;
whereString.append(" requestParm3 = '" + requestParm3 + "' AND");
}
return this;
}
public Builder requestParm4(String value) {
if (value != null) {
requestParm4 = value;
whereString.append(" requestParm4 = '" + requestParm4 + "' AND");
}
return this;
}
public Builder requestParm5(String value) {
if (value != null) {
requestParm5 = value;
whereString.append(" requestParm5 = '" + requestParm5 + "' AND");
}
return this;
}
public WhereBuilder build() {
return new WhereBuilder(this);
}
}
private WhereBuilder(Builder builder) {
requestParm1 = builder.requestParm1;
requestParm2 = builder.requestParm2;
requestParm3 = builder.requestParm3;
requestParm4 = builder.requestParm4;
requestParm5 = builder.requestParm5;
whereString = builder.whereString;
}
public String getWhereString() {
whereString.delete(whereString.length()-3, whereString.length());
return whereString.toString();
}
public static void main(String[] args) {
WhereBuilder wb = new WhereBuilder.Builder().requestParm1("hello").requestParm2("how")
.requestParm3("are").requestParm4(null).requestParm5("you").build();
String whereString = wb.getWhereString();
System.out.println(whereString);
}
}
The output of the main method is
WHERE requestParm1 = 'hello' AND requestParm2 = 'how' AND requestParm3 = 'are' AND requestParm5 = 'you'

Find by Object Attributes in JPA

i was wondering whether it is possible to find an object using an example object like you can in hibernate with:
Cat cat = new Cat();
cat.Sex = 'F';
cat.Color = Color.Black;
List results = session.CreateCriteria(typeof(Cat)).Add( Example.Create(cat)).List();
I know i can find by primary key, just not looking forward to writing a million lines of findByX, findByY etc etc.
thanks.
Nico
It seems as if the Criteria API is being considered for the next JPA release. There is some discussion about it here.
It would seem that at the moment if you want the Query by Example and Criteria features then you will have to use Hibernate.
so since it's not available in the current JPA API, the only way i can see how to implement it would be by using this:
public <T> List<T> findByAttribute(T object) {
List<T> found = new ArrayList<T>();
Map m = null;
try {
m = BeanUtils.describe(object);
} catch (Exception ex) {
return null;
}
String query = "select c from " + object.getClass().getSimpleName() + " c where ";
if (m != null) {
for (Object key : m.keySet()) {
if (!key.equals("class")) {
Object value = m.get(key);
if (value != null) {
try {
ConvertUtils.convert(m.get(key), PropertyUtils.getPropertyType(object, key.toString()));
query += " c." + key + " = :" + key + " and";
} catch (Exception ex) {
// the reason for this noncy try catch is so that you don't add parameters that are not primitives
}
}
}
}
query = query.substring(0, query.lastIndexOf("and"));
Query q = getEntityManager().createQuery(query);
for (Object key : m.keySet()) {
if (!key.equals("class")) {
if (m.get(key) != null) {
try {
Object o = ConvertUtils.convert(m.get(key), PropertyUtils.getPropertyType(object, key.toString()));
q.setParameter(key.toString(), o);
} catch (Exception ex) {
System.out.println("what we have here is a failure to communicate");
System.out.println("only primitive types allowed");
}
}
}
}
List resultList = q.getResultList();
if (resultList != null) {
found.addAll(resultList);
}
}
return found;
}
but this will only work for primitive types i think. I guess it's something.
Thanks anyway
N

Categories