4

This is another debated subject, but this time i am searching only for the simple and documented answers. The scenario :

Let's assume the following method:
 public static Hashtable<Long, Dog> getSomeDogs(String colName, String colValue) {
  Hashtable<Long, Dog> result = new Hashtable<Long, Dog>();
  StringBuffer sql = null;
  Dog dog = null;
  ResultSet rs = null;
      try {
          sql = new StringBuffer();
          sql.append("SELECT * FROM ").append("dogs_table");
          sql.append(" WHERE ").append(colName).append("='");
          sql.append(colValue).append("'");
          rs = executeQuery(sql.toString());
              while (rs.next()) {
                  dog= new Dog();
                  //...initialize the dog from the current resultSet row
              result.put(new Long(dog.getId()), dog);
              }
          }
     catch (Exception e) {
         createErrorMsg(e);
         result = null; //i wonder....
         }
     finally {
         closeResultSet(rs); //this method tests for null rs and other stuff when closing the rs.
     }
   return result;
 }

Questions :

1. What ways do u suggest on improving this technique of returning some dogs, with some attribute?

2. rs.next() will return false for a null ResultSet, or will generate an exception, like this one :

String str = null; System.out.println(str.toString());

3. What if, while initializing a dog object from the current row of the ResultSet, something bad happens, like : connection failure, incompatible value has been passed to dog property setter, etc? I might have 10 elements in the hashtable by now, or none (first row). What will be the next action : a) return null hashtable; b) return the result hashtable, the way it is at this stage; c) throw an exception : what will be the exception type here?

4. I think u will all agree on this one : nothing bad happens, there are no rows on the interrogation, a null value will be return. However, @Thorbjørn Ravn Andersen said here that i should return a NullObject instead of a null value. I wonder what that is.

5. I've noticed people and groups of people saying that one should split the application into layers, or levels of some kind. Considering the example above, what layers are here, except these ones i can think of :

Layer1 :: Database layer, where actions are performed : this method.

Layer2 :: ??? : some layer where the new Dog object is constructed : my Dog object.

Layer3 :: ? : some layer where i intend to do something with the collection of dogs : GUI layer, mostly, or a sub-layer of user interface.

Following the application flow, if an exception occures in the 1st layer, what is the best think to do with it? My thoughts : catch the exception, log the exception, return some value. Is that the best practice?

Manny thanks for your answers, i look forward to see what other people think of these matters.
Community
  • 1
  • 1
hypercube
  • 958
  • 2
  • 16
  • 34
  • Please indent code with 4 spaces. – Dark Falcon Oct 09 '09 at 16:58
  • I'm not fussed re. the indenting, but just actually formatting it via the code formatter, which seems to happen less and less :-( – Brian Agnew Oct 09 '09 at 17:02
  • I apologise for the question aspect, but how should i do this? I've pushed n-times the "code" button over these lines "String str = null; System.out.println(str.toString()); "..nothing happened :-(. – hypercube Oct 09 '09 at 17:27
  • That's not a problem, but I wonder if the formatting is getting confused with the additional HTML elements (e.g. the

    s)
    – Brian Agnew Oct 10 '09 at 22:36
  • Hyper: Select the lines you want formatted as code, then press the code button. – Jorn Oct 11 '09 at 15:55
  • Oh, but as i said, i have pushed the button dozen times :-). It must be a bud :-D, related to basic HTML inserts. – hypercube Oct 11 '09 at 19:06

6 Answers6

8

I'd avoid the following

   sql.append("SELECT * FROM ").append("dogs_table");
   sql.append(" WHERE ").append(colName).append("='");
                        sql.append(colValue).append("'");

and instead use a PreparedStatement with its associated parameter setter methods (setString()) etc. This will prevent problems with values for colValue having quotes, and SQL injection attacks (or more generally, colValue forming some SQL syntax).

I would never return a null if the collection was merely empty. That seems very counter-intuitive, and completely unexpected from the client's point of view.

I wouldn't recommend returning a null in error conditions, since your client has to explicitly check for this (and will probably forget). I would return an empty collection if need be (this may be analogous to your comment re. a null object), or more likely throw an exception (depending on the circumstances and the severity). The exception is useful in that it will carry some information relating to the error encountered. Null tells you nothing.

What should you do if encountering a problem whilst building a Dog object ? I think that depends on how robust and resilient you want your application to be. Is it a problem to return a subset of Dogs, or would it be completely catastrophic and you need to report this ? That's an application requirement (I've had to cater for either scenario in the past - best-effort or all-or-nothing).

A couple of observations. I'd use HashMap rather than the old Hashtable (synchronised for all access and , more importantly, not a proper Collection - if you have a Collection you can pass it to any other method expecting any Collection), and StringBuilder over StringBuffer for similar reasons. Not a major problem, but worth knowing.

Brian Agnew
  • 268,207
  • 37
  • 334
  • 440
  • 1
    +1 for `PreparedStatement` and for using `HashMap` over `Hashtable`. There's no good reason to use `Hashtable` in modern code. And any time you build SQL manually as strings you set yourself up for all kinds of SQL injection vulnerabilities. – Daniel Pryden Oct 09 '09 at 17:15
  • Throwing an exception here, in my opinion will add no value to my code, as it needs to be catched on a higher level (wich we can forget to do) and will tell me nothing more, as i already logged the error details. Should the user be prompted or not here that an error has occured? – hypercube Oct 09 '09 at 17:16
  • Can u please translate that StringBuffer creation code into PreparedStatement version, to clearly mark the differences? And btw, what can go wrong with the StringBuffer forming the query, if the creator is paying enough attention to it? – hypercube Oct 09 '09 at 17:23
  • I dunno about the HashMap..i've google it and founded this : http://www.coderanch.com/t/202040/Performance/java/Hashtable-vs-HashMap. Slight performance increase vs. loosing thread-safe atribute doesnt pay off. – hypercube Oct 10 '09 at 13:01
  • I would use it just for the fact that it implements java.util.Collection and can be passed to any method taking Collections – Brian Agnew Oct 10 '09 at 13:27
  • I too would recommend the PreparedStatement, but since you are concerned with improving your techniques(and maybe even optimizing it to be more efficient)then there is also a possible downside to using PreparedStatement. PreparedStatement although more often then not has shown to be successful in optimizing and making it more efficient, it has also shown in some circumstances, to have a larger overhead then simply just putting it together how you have done it originally. I am not quite sure on the exact reason of the overhead, but if you are interested in PreparedStatement, consider this. – TheJediCowboy Oct 10 '09 at 22:32
  • Thanks, i will. I have made some tests using PreparedStatement, and a problem arised : i wanna use static objects like Connection, Statement, etc to refer to database. Using PreparedStatement, that sounds impossible, as a new object must be constructed every time. Further more, PMD code analizer suggests that the newly created PS may not close normally. Closing PS in the finally clause will throw an exception (OperationNotAllowed..resultset closed) to the caller method. I am a bit stuck here. Should i even care for SQL injection if this is NOT a web-based app? – hypercube Oct 11 '09 at 00:17
  • PreparedStatement will also handle quoting of strings. e.g. in your example code, what happens if you try to insert a string with a single quote ? Get a new connection each time, then get a new PreparedStatement etc. each time. – Brian Agnew Oct 11 '09 at 08:46
  • A new connection each time?? I found that hard to believe like the best thing to do. If your app has 50 or 100 users or more, what will happen if every one of them spawn a new connection witch each little thing they do?? This looks like the road to hell man :-(. A connection once initialised could be re-used and this single connection will be able to create as many new PeparedStatements as i need. I cannot agree with your suggestion here. – hypercube Oct 11 '09 at 10:19
  • You need to investigate connection pooling. When you get a new connection it will pull an existing connection from the pool, so it's not really new. – Brian Agnew Oct 11 '09 at 10:57
  • Yes, connection pooling is a more appropriate solution. However, perhaps a bit too much, to implement connection pooling just to be able to use a new PreparedStatement. Again, if i have a static Connection object called con and i call con.createPreparedStatement(someQuery) every time wouldn't suffice? I dont care much about SQL Injection here, as i wont create queries directly from parsing user input. – hypercube Oct 11 '09 at 13:25
6

You ask five questions

1. What ways do u suggest on improving this technique of returning some dogs, with some attribute?

Several, actually.

  • Your method is static - this isn't terrible, but leads to you using another static "executeQuery", which smells of Singleton to me...
  • The class "Dogs" violates an OO naming practice - plural nouns don't make good class names unless one instance of the class holds a collection of things - and it appears that Dogs is actually "Dog".
  • HashTable is all but deprecated. HashMap or ConcurrentHashMap give better performance.
  • I can't see a reason to create the first part of your query with multiple appends - it's not bad, but it's less readable than it might be, so sql.append ("SELECT * FROM dogs_table WHERE "); makes a more sensible beginning if you're just going to hardcode the selected columns (*) and table name (dogs_table) anyway.

2. rs.next() will return false for a null ResultSet, or will generate an exception

This doesn't seem to be a question, but yes, rs.next() returns false as soon as there's no longer any rows to process.

3. What if, while initializing a dog object from the current row of the ResultSet, something bad happens

If "something bad happens", what you do next is up to you and your design. There's the forgiving approach (return all the rows you can), and the unforgiving (throw an exception). I tend to lean towards the "unforgiving" approach, since with the "forgiving" approach, users won't know that you haven't returned all the rows that exist - just all the ones you'd gotten before the error. But there might be cases for the forgiving approach.

4. I think u will all agree on this one : nothing bad happens, there are no rows on the interrogation, a null value will be return.

This isn't something on which there's an obvious right answer. First, it's not what's happening in the method as written. It's going to return an empty HashTable (this is what was meant by a "null object"). Second, null isn't always the answer in the "no results found" case.

I've seen null, but I've also seen an empty result variable instead. I claim they're both correct approaches, but I prefer the empty result variable. However, it's always best to be consistent, so pick a method of returning "no results" and stick with it.

5. I've noticed people and groups of people saying that one should split the application into layers, or levels of some kind.

This is harder to answer than the others, without seeing the rest of your application.

CPerkins
  • 8,968
  • 3
  • 34
  • 47
  • Thank u for the contextual and concise answer. You were wright. 5 question (i was thinking of 4, but i've actually added another one :-) ). I am using a singletone manager for database access (is that bad?). If the suggested types performs better, i shall adopt them. It might sound stupid, but i wasnt aware of some things here..if i were, i wouldn't ask for info. Thanks again. I will use : HashMap, StringBuilder(where the case), PreparedStatement instead of executing strings, NullObjects instead of null value and Dog instead of Dogs. That's valuable info. – hypercube Oct 09 '09 at 20:33
  • Update.I've checked the objects in my project, related to db, and they are all named "Dog instead of Dogs". The truth is i was a bit unaware of the convention, but somehow it seemed more appropriate.Another light in the darkness :-). – hypercube Oct 09 '09 at 21:03
  • You're welcome. Yes, the Singleton pattern is ... well, I won't say *bad*, but it's a questionable practice. Anytime I see one in a design, it causes me to wonder if it's really needed. Google around for it, and you'll see what I mean. – CPerkins Oct 09 '09 at 23:04
  • I wonder why should i use HashMap an all cases. I've performed a search on the matter of HashMap vs. Hashtable, and i tend to stick with Hashtable. On some cases HashMap tends to be about 5% faster than Hashtable (sacrificing thread-safe prop of your collection), but syncronized HashMap IS slower than the "old" hashtable. So..why should i use HashMap again? – hypercube Oct 10 '09 at 13:03
  • Some people did some tests here : http://forums.sun.com/thread.jspa?trange=15&threadID=536477&forumID=24&tstart=0 – hypercube Oct 10 '09 at 13:06
  • @gnud: ouch. thanks. I'll edit for posterity's sake, but thanks for the edit. – CPerkins Oct 11 '09 at 15:31
  • @Hypercube - actually, HashTable doesn't provide thread-safety, at least not complete safety. There are many compound actions (like insert-if-absent) which would have to be externally synchronized. Also, iteration isn't thread-safe: HashTable's iterator isn't fail-fast, so if one thread holds an iterator, and another changes the map, you can get inconsistent results, while with a HashMap, you get a ConcurrentModificationException. But this has gotten pretty specific. There's some good threads on this topic in the SO archives. – CPerkins Oct 11 '09 at 15:36
  • Thank u for your patience with a stubborn donkey :-). I'll give it some more thought then. – hypercube Oct 11 '09 at 19:10
4

Null Object Pattern is a design pattern where you always return an object to avoid NPE:s and any null checks in your code. In your case this means that instead of returning null, return an empty Hashtable<Long, Dogs> instead.

The reasoning is that since it's a collection and your other code will access it as such, it won't break down if you return an empty collection; it won't be iterated through, it won't contain anything surprising, it wont cause NPE:s to be thrown and whatnot.

To be precise, a Null Object is a special implementation of a class/interface which does absolutely nothing and thus has no side effects of any kind. Because of its nature of not being null using it will make your code cleaner since when you know that you'll always get an object from your method calls no matter what happens inside the method you don't even have to check for nulls nor make code reacting to them! Because Null Object doesn't do anything, you can even have them as singletons just lying around and thus save memory by doing that.

Esko
  • 29,022
  • 11
  • 55
  • 82
  • Thank u for clearing that up. It looks like a special Class can be built with static initializers for Null Objects, like these : final static String[2] EMPTY_STRING_ARRAY = new String[]{"", ""}, right? – hypercube Oct 09 '09 at 17:21
  • Yep, that'd be a Null Object version of a String array with the length of 2. Now instead of doing things like if(arr[i] != null && arr[i].equals("val")){} you can simply do if(arr[i].equals("val")){} which is simpler and cleaner. – Esko Oct 09 '09 at 17:36
  • Roger that. What if my object X is hand-maded, has Y fields and Z methods? I must write a class named NullX with Y "default" or "null" values and Z empty methods, to substitute the original object? That also implies an interface to be created. Isn't that too much trouble? Using new X() with default values cannot do the same thing (except for the extra memory consumed with each instance?). Or perhaps i can make THIS object (new X - my null object) a static variable somewhere, to call in all cases? – hypercube Oct 09 '09 at 17:50
  • A bit confusing but as far as I understood, that'd be the Singleton. Considering Null Object Pattern in POJOs and beans, yes it means you have to implement Null Objects to fields and such too, but most commonly replacing null String with "" and numbers with -1 or 0 is enough. – Esko Oct 09 '09 at 18:15
  • I've readed about the possible implementations of the NullObjects : there are 2 main ones..1. Using an interface, 2.the Null Object will extend the RealObject, and therefore will have to implement his methods. There are SERIOUS concerns against this pattern here. I hope my comment link will work as intended... – hypercube Oct 09 '09 at 18:27
  • Using interface instead of extending another class is always cleaner so if you can choose, choose the interface. The concerns against null object pattern usually are related to the fact that you can't just drop it into existing code; you have to design rest of the code to work with NO:s too. – Esko Oct 09 '09 at 19:54
  • If i add a static method called getEmptyObject, who simply returns a new Object of my kind, wouldn't be enough? The default (null values) initializing will be done in the constructor. – hypercube Oct 11 '09 at 00:20
3

Do not build SQL queries by concatenating strings, like you're doing:

sql = new StringBuffer();
sql.append("SELECT * FROM ").append("dogs_table");
sql.append(" WHERE ").append(colName).append("='");
sql.append(colValue).append("'");

This makes your code vulnerable to a well-known security attack, SQL injection. Instead of doing this, use a PreparedStatement and set the parameters by calling the appropriate set...() methods on it. Note, you can only use this to set column values, you can't use this to dynamically construct a column name, as you're doing. Example:

PreparedStatement ps = connection.prepareStatement("SELECT * FROM dogs_table WHERE MYCOL=?");
ps.setString(1, colValue);

rs = ps.executeQuery();

If you use a PreparedStatement, the JDBC driver will automatically take care of escaping certain characters that might be present in colValue, so that SQL injection attacks don't work anymore.

Jesper
  • 202,709
  • 46
  • 318
  • 350
2

If an error occurs, thow an exception. If there's no data, return an empty collection, not a null. (Also, generally you should return the more generic 'Map', not the specific implementation),

Nerdfest
  • 1,675
  • 13
  • 20
0

You can significantly reduce the amount of boilerplate JDBC code by using Spring-JDBC instead of plain-old JDBC. Here's the same method rewritten using Spring-JDBC

public static Hashtable<Long, Dogs> getSomeDogs(String colName, String colValue) {

    StringBuffer sql = new StringBuffer();
    sql.append("SELECT * FROM ").append("dogs_table");
    sql.append(" WHERE ").append(colName).append("='");
    sql.append(colValue).append("'");

    Hashtable<Long, Dogs> result = new Hashtable<Long, Dogs>();

    RowMapper mapper = new RowMapper() {

        public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
            Dogs dog = new Dogs();
            //...initialize the dog from the current resultSet row
            result.put(new Long(dog.getId()), dog);
        }
    };
    (Hashtable<Long, Dogs>) jdbcTemplate.queryForObject(sql, mapper);
}

Spring takes care of:

  1. Iterating over the ResultSet
  2. Closing the ResultSet
  3. Handling exceptions consistently

As others have mentioned you really should use a PreparedStatement to construct the SQL instead of a String (or StringBuffer). If for some reason you can't do this, you could improve the readability of the query by constructing the SQL like this instead:

    String sql = 
        "SELECT * FROM dogs_table " +
        "WHERE " + "colName" + " = '" + colValue + "'";
Dónal
  • 185,044
  • 174
  • 569
  • 824
  • As this may seem more readable, if using the "manual" query builder, as in my example, i've founded that it is NOT. Using .append() method of the "deprecated" StringBuilder, separates in more distinctive tokens the query to build. All this make little sense now, comparing to people suggestions to ALWAYS change the current style, according to their own. Or the recommended one. However, one developper cannot adopt X styles, for X different answerers. For instance, the checked or unchecked use of exceptions question has NO answer so far..and so on... – hypercube Oct 09 '09 at 18:46
  • Some are even more disturbed of the format of the question, rather than the essence of that question. I did my best to make it look good, this is how it went into the wild. I've asked 4 distinct questions, i got some vague answers..i am sorry, but beside the use of new java types like StringBuilder or HashMap, i cannot extract anything of use from the answers so far :-(. Yes, perhaps SpringJDBC is a great framework, but i didn't ask for it. It's just another dependency to my project..and i am not sure i wanna commit to that yet. – hypercube Oct 09 '09 at 18:49
  • About the SpringJDBC advantages : 1. I am not annoyed by iterating the result set myself; 2. I have a static method for closing the resultSet safely; 3.I already use apache.commons.lang for dealing with exceptions, like getting the rootStackTrace() instead of some nasty-nested stacktraces, so..what advantages are left? – hypercube Oct 09 '09 at 18:53