The initial steps in the cleanup of the "must fix" items:
- Implement the interface java.lang.AutoClosable in the base class of all the DAO code.
- Provide a factory type instance into the DAO instances which acquire DB connections instead only when absolutely needed (which isn't 100% of time in these "DAO" classes).
- Replace the previous DAO connection close() logic (which was never in try/final blocks) with the Java 7 try-with-resource constructs everywhere.
Some other changes involved conversion from one cache solution to JBoss Infinispan 5.x and a different method of handling cache keys for the DAO data. I ended up with a somewhat more complex solution for this to help reduce the chance of keys being duplicated and used for different data items. The Java enum type is great for this and being able to implement interfaces on them provides a lot of flexibility.
The next problem was how to sort out data flow, mutability and nullness consistency. I try fairly hard to keep a certain consistency in what I return from DAO methods. I find that returning null tends to produce more errors through forgetting to check for it in all cases. This can be mitigated by not returning nulls whenever it makes sense.
I find this most useful when dealing with collections - return Collections.emptyList() (or use similar methods for sets and maps) instead of returning a null where there was no data to return. This works when you return something the client doesn't mutate. Other use cases might be handled by returning a new mutable empty list (but only if truly needed), requiring the client to make a copy of the object or possibly having the caller provide the collection. Wrappers and immutable collections are quite useful to prevent modification to the returned object when there is content in collection. Consistency is helpful to fellow developers who may not know the application or frameworks as well as the original developers.
One deficiency of Java is that it is impossible or nearly so using just the core language itself to fully define mutability constraints and expectations. Most of the time this must be done via Java doc with the hope that it is actually correct. The current project I am trying to sort out lacks usable Java doc or comments on what the best constraints should be. I am starting to work with an annotation framework to help document, report and enforce various constraints (mainly nullability and mutability).
http://types.cs.washington.edu/checker-framework/
I am trying to utilize it via an Eclipse plug-in with some success (but not complete). I think the nullability checks are working but I am running into problems with the mutability annotations. I know there are some limitations using Java 7 which should be removed or minimized with Java 8. It could also simply be related to the Eclipse plug-in or the use of Eclipse 4.2. The embedding of the annotations in Java comments has both good and bad aspects. I like not having to worry about the build server having the annotation jars, etc but I would like Eclipse to tell me when I make an obvious mistake well before I try to generate and analyze results.
If I am able to get this working correctly for my main cases, it has the potential to provide a much higher static gurantee that object instances are only being used in the way I intended them.
Other useful references are:
JSR 308: Annotations on Java Types
JSR 305: Annotations for Software Defect Detection
Java Modeling Language (JML)
Once I am able to get a more rigorous definition of how the DAO data is used in the current application, we can either further improve the plain JDBC based solution, move to an internal framework which uses Apache DBUtils or possibly move to Hibernate.
I am slightly leaning toward Hibernate because I think it will reduce our custom code, remove/reduce the need for the manual caching of data, possibly improve performance and generally reduce the number of sources of problems. I only say "leaning" until I gather enough info and prototype & evaluate some partial solutions for comparison.