Skip navigation.

exploreopera

| Help

Sign up | Help

BlogtimeException

By Behrang Saeedzadeh (the 3rd incarnation)

Posts tagged with "DAO"

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();
      
   }

}

Var-Arg DAO Finders

, ,

Almost every Java DAO class I have seen contains finder methods with unfortunate ugly and lengthy names:

  • findByNameAndFamilyName
  • findByPositionAndDepartment
  • findByMonthAndCategoryAndTitle
  • findByBlahBlahDamnBlahBlahAndGodDamnBlahBlah

This has always been distracting to me. Once I see a class with such stupid method names, I try my best to keep myself away from it. I DON'T LIKE LENGTHY METHOD NAMES! I DON'T LIKE METHODS WITH LARGE NUMBER OF ARGUMENTS (unless, well continue reading...!) It's the sign of a bad design. It might be an inherent weakness of the host language, but that doesn't solve anything. Some languages support named arguments which is just fine and pleasant to work with:

  • find(name: "James" familyName: "Gosling")

However, the declarations still remain just as lengthy. But thanks to variable length arguments introduced in Java 5, it's very easy to write finder methods that not only are concise to declare and invoke, but also more generic and flexible. The idea is to pass a comma separated list of arguments as a string followed by the arguments themselves:

  • find ("name, familyName", "James", "Gosling")

This even eliminates the need to have more than one equity-based finder method in your DAOs. A negative side-effect is the loss of type-safety, but I belive, overall, it won't hurt that much and is neglectible.

In a follow-up post, I'll provide a simple JDBC DAO implementation as well as a more flexible finder that not only supports equity comparision, but also LIKE, between, less than, greater than, and more operators as well.

Neat! Izhn't dish jusht sho shibit?