Skip navigation.

exploreopera

| Help

Sign up | Help

BlogtimeException

By Behrang Saeedzadeh (the 3rd incarnation)

Var-Arg DAO Finders, Part 2

,

In my previous blog entry about Var-Arg DAO Finders, I explained how variable length arguments can help us reduce verbosity of DAO finder methods and and also help us get rid of ugly and lenghty method names.

Patrick responded that he is using a similar method, but he is building his finders on top of iBATIS.

Jason had a different point of view:

Ugh... And how do you tell that you've got a prepared statement that can handle those? Or check at runtime that the field names you're passing in make sense? This is an ugly hack to compensate for not working with a better API.

I personally just use the Hibernate Criteria object for this... dynamically populating a Criteria object at runtime is simple and it manages the PreparedStatement handling and checks that the criteria properties are really there on the object you're querying.
As I had said in that post, this class of finders are not benefiting from Java's compile-time type checking system, and this is certainly a downside of this approach. However, I personally belive that the trade-off here is worth the while. After all, all these dynamic languages out there do not have compile-time type checking and at the same time they have DAO or DAO-like classes that are not noticeably less unreliable compared to Java land DAOs.

Jason also says that This is an ugly hack to compensate for not working with a better API. Actually, I think this API is at least as good as the JDBC API. As I am building my finders on top of JDBC, it only makes sense to compare this API to the JDBC API.

Jason moves on and says: I personally just use the Hibernate Criteria object for this.... Actually I fell in love with Criteria objects the same moment I started using them. Before using Criteria objects, we were using HQL, but then we all agreed that using Criteria objects were far more pleasant and results code that is more elegant. But, again, we were using the Criteria objects inside finders, so we were left with no choice but using lengthy and ugly finder names. I have also seen some book authors recommending not to use Criteria objects directly in action classes, etc. I personally am not in favor of this opinion, but when it has been practiced for a long time in a project, one has to live with it.

And you may ask why I am not using Hibernate and instead rely on JDBC? Because I don't have to support different RDBMSes, My domain model is not that complex, and Hibernate just slows down the code-deploy-test cycle so badly that I prefer not to use it at all when it makes sense!

Now, lets see a proof-of-concept implementation of Var-Arg DAO Finders. First, here comes the sample Person DTO class:

package org.behrang.sample.dao;

import java.sql.Date;

/**
 * The Person data object class. Self describing!
 * 
 * @author Behrang 
 */
public class Person {
   
   private String firstName;
   private String lastName;
   private Date birthDate;
   
   public Person() {    
   }
   
   public String getFirstName() {
      return firstName;
   }
   
   public void setFirstName(String name) {
      this.firstName = name;
   }
   
   public String getLastName() {
      return lastName;
   }
   
   public void setLastName(String familyName) {
      this.lastName = familyName;
   }
   
   public Date getBirthDate() {
      return birthDate;
   }
   
   public void setBirthDate(Date birthDate) {
      this.birthDate = birthDate;
   }
}
Then, the DAO:

package org.behrang.sample.dao;

import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

/**
 * A sample DAO for demonstrating the concept of the var-arg finders.
 * 
 * @author Behrang Saeedzadeh
 */
public class PersonDAO {
   
   private Connection conn;
   
   public PersonDAO(Connection connection) {
      if (connection == null) {
         throw new IllegalArgumentException("connection must be non null, but null connection passed in");
      }
      
      this.conn = connection;
   }
   
   /**
    * A var-arg finder method. In the first step, it tokenizes the argList using comma (i.e., ',') 
    * as the separator. The number of tokens must be equal to <code>args.length</code>.
    * 
    * Having the tokens as column names, then this method fetches those rows of the database that their
    * <code><i>token[i]</i></code>-named column has the value <code><i>args[i]</i></code>.
    * 
    * Finally it creates data objects representing these rows and returns them in a list.
    *  
    * @param argList The comma separated list of column names. Must be non-null.
    * @param args column values. Most be non-null.
    * @return List of matching persons
    * @throws SQLException
    */
   public List<Person> find(String argList, Object... args) throws SQLException {
      if (argList == null || args == null) {
         throw new IllegalArgumentException("argList and args must be non null, but one or both of them are null.");
      }

      List<String> argNames = tokenize(argList);
      
      if (argNames.size() != args.length) {
         throw new IllegalArgumentException("Number of tokens in argList must match the length of args, but it does not.");
      }
      
      
      StringBuilder query = new StringBuilder();
      query.append("select * from PERSONS where ");
      
      for (String arg : argNames) {
         query.append(arg);
         query.append(" = ? AND "); 
      }
      
      if (query.length() >= 5) {       
         query.setLength(query.length() - 5);
      }
      
      PreparedStatement stmt = conn.prepareStatement(query.toString());    
      for (int i = 0; i < args.length; i++) {
         if (args[i] instanceof String) {
            stmt.setString((i + 1), (String) args[i]);
         } else if (args[i] instanceof Integer) {
            stmt.setInt((i + 1), (Integer) args[i]);
         } else if (args[i] instanceof Date) {
            stmt.setDate((i + 1), (Date) args[i]);          
         }
         // handling for other types has to be added if needed 
      }
      
      List<Person> foundPersons = new ArrayList<Person>();
      
      ResultSet res = stmt.executeQuery();
      while (res.next()) {
         Person p = new Person();
         p.setFirstName(res.getString("FIRST_NAME"));
         p.setLastName(res.getString("LAST_NAME"));         
         p.setBirthDate(res.getDate("BIRTH_DATE"));
         foundPersons.add(p);
      }     
      
      return foundPersons;    
   }
   
   private List<String> tokenize(String argList) {
      List<String> args = new ArrayList<String>();
      StringTokenizer t = new StringTokenizer(argList, ",");
      while (t.hasMoreTokens()) {
         args.add(t.nextToken());
      }
      
      return args;      
   }

}

Finally, a simple driver test class:

package org.behrang.sample.dao;

import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.Statement;
import java.util.Calendar;
import java.util.List;

import static java.lang.System.out;

public class TestPersonDAO {
   
   public static void main(String[] args) throws Exception {
      Class.forName("org.h2.Driver");
      Connection conn = DriverManager.getConnection("jdbc:h2:test;hsqldb.default_table_type=cached", "sa", "");
      
      Statement stmt = conn.createStatement();
      
      stmt.executeUpdate("drop table if exists PERSONS");
      stmt.executeUpdate("create table PERSONS (FIRST_NAME varchar(100), LAST_NAME varchar(100), BIRTH_DATE date)");
      stmt.executeUpdate("insert into PERSONS values ('Alan',   'Turing',      '1954-6-7')");
      stmt.executeUpdate("insert into PERSONS values ('John',   'von Nuemann', '1903-12-28');");
      stmt.executeUpdate("insert into PERSONS values ('Edsger', 'Dijkstra',    '1930-5-11');");
      
      PersonDAO dao = new PersonDAO(conn);
      
      Calendar cal = Calendar.getInstance();
      cal.set(1930, 4, 11, 0, 0, 0);
      List<Person> persons = dao.find("BIRTH_DATE", new Date(cal.getTimeInMillis()));     
      assert persons.size() == 1;
      
      Person dijkstra = persons.get(0);
      assert dijkstra.getFirstName().equals("Edsger");
      assert dijkstra.getLastName().equals("Dijsktra");
      assert dijkstra.getBirthDate().getTime() == cal.getTimeInMillis();
      
      out.printf("%s %s - %s\n", dijkstra.getFirstName(), dijkstra.getLastName(), dijkstra.getBirthDate());
      
      cal.set(1954, 5, 7);
      persons = dao.find("FIRST_NAME, LAST_NAME, BIRTH_DATE", "Alan", "Turing", new Date(cal.getTimeInMillis()));
      assert persons.size() == 1;
      
      Person turing = persons.get(0);
      assert turing.getFirstName().equals("Alan");
      assert turing.getLastName().equals("Turing");
      assert turing.getBirthDate().getTime() == cal.getTimeInMillis();
      
      out.printf("%s %s - %s\n", turing.getFirstName(), turing.getLastName(), turing.getBirthDate());
      
      conn.close();
      
   }

}

Where Bindows is gone?Just Released: IDEA 6 and TeamCity 1

Comments

avatar
Anonymous writes:


I experimented with JDBC and varargs in 2005:

http://diaz.sourceforge.net/xref/net/sf/diaz/AbstractJdbcDao.html

By anonymous user, # 24. September 2006, 18:50:44

avatar
Andreas Andreou writes:

Well, if you do like
dao.find("FIRST_NAME, LAST_NAME, BIRTH_DATE", "Alan", "Turing", new Date(cal.getTimeInMillis()));
I don't see why you wouldn't prefer this:
dao.find("FIRST_NAME=? and LAST_NAME=? and BIRTH_DATE=?", "Alan", "Turing", new Date(cal.getTimeInMillis()));

It's much more powerful and has the same drawbacks that your version has :smile:

What i'm trying to say is that we'll probably end up with an API where the first argument is an
SQL (or HQL) part... i'm not sure if this is good or bad, however.


By anonymous user, # 25. September 2006, 09:05:08

avatar
Sean,

I see that you had come up with the idea sooner than me :smile:

Andreas,

If I ever need to support more operations than "equal" (which I certainly do ;-)), your syntax is just preferred. Certainly I also am not sure how much should we extend this approach, but one thing I need is for the API not to make my application startup N times slower, where N > 1 ;-)

By behrangsa, # 28. September 2006, 22:35:54

Write a comment

Comment
(BBcode and HTML is turned off for anonymous user comments.)

Please type this security code : 29f1ef

Smilies