Simple answer to the exception question.... yes. :-)
For this simple implementation, I didn't even unwrap the UndeclaredThrowable (as I usually do).
It's imporant to note that these Mappers are not DAOs. At this level we're still working with the SQL Mapper layer. DAOs are more coarse grained.
However, with such a clean interface, we may not need DAOs. ...hmmm...
Clinton
On 5/20/05, Kris Jenkins <[EMAIL PROTECTED]> wrote:
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:
How select statements are run is determined by the method signature, 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 stored procedures are run is determined by the method name, 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
- 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/