Now that's pretty cool. The code feels a lot cleaner that way. I like
it muchly. :-)
...but...
I do have one question/suggestion - is there any way to clean up the
exception handling? For example, I tried an interface like this:
public interface GraphDao {
void insertGraph( Graph graph )
throws DuplicateEntityException;
}
I deliberately put a problem in the SQL, and got this exception:
java.lang.reflect.UndeclaredThrowableException
at $Proxy9.insertGraph(Unknown Source)
at
com.jenkster.moodswing.dao.GraphDaoImpl.insertGraph(GraphBoundaryImpl.java:39)
at
com.jenkster.moodswing.controller.SaveGraphController.saveGraph(SaveGraphController.java:64)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:324)
Fair enough - it's throwing an SQLException and my interface doesn't
support it. What can I do? It looks like I'd have to write another
interface like this:
public interface GraphDaoMapper {
void insertGraph( Graph graph )
throws SQLException;
}
Then I get the mapper from this SqlMap extension, and implement
GraphDao as a thin wrapper around GraphDaoMapper which can read the
SQLException and recognise if this is a valid DuplicateEntityException
or an unexpected RuntimeException. Not hard to write, but it would
leave me with two interface files I'd need to keep identical in all but
the 'throws' part.
What if there were some way to automatically refine SQLExceptions into
something more useful, based on the errorCode and message? Maybe an
element like:
<exception
class="com.jenkster.moodswing.exception.DuplicateEntityException"
errorCode="00001"
messagePattern="(?s).*ORA-00001: unique constraint
\\([A-Z]*[.]GRAPH_GRAPH_ID_UNIQUE\\) violated.*"
/>
If that could be somehow be associated on a global or
statement-specific basis, then I'd only need one the first interface,
life would get much simpler and I'd factor out a ton of code.
The mapper and exception handling together would, IMHO, be fantastic.
:-)
(Of course, maybe someone can tell me a better way to refine
SQLExceptions, in which case I'm all ears and all typed-out. :-})
Cheers,
Kris
Although quite simple, there are some tradeoffs with the typical
SqlMapClient methods like:
Document doc = (Document) sqlMap.queryForList("getDocument", new
Integer (1));
First of all, it is possible that you could spell getDocuments
incorrectly. Second, the parameter is not strongly typed.
So at code time, you could easily pass in an inappropriate
object. Also, the return type is cast, so it's even possible for
the statement to return an invalid object (i.e. result map returns a
Dog instead of a Document). Finally, if you're using anything
less than J2SE 5.0, you have to wrap primitives with their wrapper
types. DISCLAIMER: Yes, you should have unit tests to
verify this anyway! ;-)
But what else can we do about this? Well, what if we mapped the
"getDocument" mapped statement to an interface. For example, this
one:
public interface DocumentMapper {
Document getDocument (int id);
}
So basically we have a method that mirrors the queryForList signature,
except the method name matches the mapped statement name, instead of
passing it as a parameter. Furthermore, as soon as the
SqlMapClient is built, this method is validated against the mapped
statement to ensure that the proper parameter and result types are
defined. Finally, using the sucker is a whole lot easier:
Document doc = documentMapper.getDocument(1);
No more casting. No more wrapping. No more ambiguous types. No more
misspelling.
Sounds good, how do I create a Mapper? Well, we already
have. The interface above is all we need. A simple call to
the following SqlMapClient method, creates the instance that can be
used:
DocumentMapper documentMapper = (DocumentMapper)
sqlMap.getMapper(DocumentMapper.class);
The instance is thread safe, so you can keep this sucker around as a
field on your DAO or service class.
Best of all, unit testing becomes a snap, as you can mock a
DocumentMapper a heck of a lot easier than you could the SqlMapClient
interface.
Alrighty! So when will it be implemented? It already is. It's in SVN right
now for your perusal, here's the unit test:
http://svn.apache.org/repos/asf/incubator/ibatis/trunk/java/mapper/mapper2/test/com/ibatis/sqlmap/BindingTest.java
The JavaDoc is below.
The current implementation isn't optimized, nor does it perform full
validation. In it's current state,
it's mostly intended to be easily removed if you don't like the idea.
So let us know what you think!
Cheers,
Clinton
----------------------------------------
getMapper
public java.lang.Object getMapper(java.lang.Class iface)
- Returns a generated implementation of a cusom mapper class as
specified by the method parameter. The generated implementation will
run mapped statements by matching the method name to the statement
name. The mapped statement elements determine how the statement is run
as per the following:
- <insert> -- insert()
- <update> -- update()
- <delete> -- delete()
- <select> -- queryForObject, queryForList or
queryForMap, as determined by signature (see below)
- <procedure> -- determined by method name (see below)
How select statements are run is determined by the method signature, as
per the following:
- Object methodName (Object param) -- queryForObject
- List methodName (Object param [, int skip, int max | , int
pageSize]) -- queryForList
- Map methodName (Object param, String keyProp [,valueProp])
-- queryForMap
How stored procedures are run is determined by the method name, as per
the following:
- insertXxxxx -- insert()
- createXxxxx -- insert()
- updateXxxxx -- update()
- saveXxxxx -- update()
- deleteXxxxx -- delete()
- removeXxxxx -- delete()
- selectXxxxx -- queryForXxxxxx() determined by method
signature as above
- queryXxxxx -- queryForXxxxxx() determined by method
signature as above
- fetchXxxxx -- queryForXxxxxx() determined by method
signature as above
- getXxxxx -- queryForXxxxxx() determined by method
signature as above
-
- Parameters:
iface - The interface that contains methods
representing the mapped statements contained.
- Returns:
- An instance of iface that can be used to call mapped
statements directly in a typesafe manner.
--
Kris Jenkins
Email: [EMAIL PROTECTED]
Blog: http://cafe.jenkster.com/
Wiki: http://wiki.jenkster.com/
|