Making iBatis Type-Safe(r)
iBatis recently came up as a near perfect solution to one of my problems. Its level of data abstraction is exactly right for what I’m doing and it provides out of the box caching. I’d previously tried Hibernate in this context but because my objects are long lived, I’d inevitably need to reconnect the objects to the active session anytime I wanted to do something interesting, like lazy loading.
But, and its a biggie, the interface when using iBatis is not type-safe; meaning I found myself with lots of error prone code resembling:
List<Product> products = (List<Product>)sqlMapClient.queryForList("getAllProducts");
When what I really wanted to do was write code like this:
List<Product> products = productDAO.getAllProducts();
Now, there are existing iBatis DAO options, but it seems to me they don’t solve this specific problem. Their approach to DAOs is a coding convention more than a framework, so you end up encapsulating the type-safety problems, but you still write the code.
With that in mind, I’ve written a utility library that very thinly wraps the iBatis library in a (mostly) type-safe way that doesn’t require me to implement all the access code myself.
I’ll start by providing some example code that makes use of it, and then drilling down into the code that makes it possible.
First off I define my model classes:
public class Product { private int id; private String name; public void setId(int id) { this.id = id; } public int getId() { return id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
And the DAO’s interface:
public interface ProductDAO { public List<Product> getAllProducts(); public Product getById(int id); }
And the actual usage would be something like:
DAOFactory daoFactory = new DAOFactory("path/to/ibatis/config.xml"); ProductDAO productDAO = (ProductDAO)daoFactory.buildDAO(ProductDAO.class); Product product = productDAO.getById(1); List<Product> products = productDAO.getAllProducts();
You’ll notice that line 2 does involve a Cast, but this is the only one required to work with the DAO, as opposed to normal iBatis usage which requires you to cast on practically every call.
And the magical class that makes it all possible:
public class DAOFactory { private SqlMapClient sqlMap; public DAOFactory(String configPath) throws IOException { Reader reader = Resources.getResourceAsReader(configPath); sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader); } public Object buildDAO(Class<?> daoInterface) { assert (daoInterface.isInterface()) : "buildDAO only works with interfaces"; String namespace = extractNameSpaceFromDaoClass(daoInterface); InvocationHandler handler = new SqlMapInvocationBridge(sqlMap, namespace); Object dao = Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] { daoInterface }, handler); return dao; } private String extractNameSpaceFromDaoClass(Class<?> daoInterface) { String[] classNameParts = daoInterface.getName().split("\\."); String className = classNameParts[classNameParts.length - 1]; assert (className.endsWith("DAO")) : "Invalid DAO Interface Provided"; return className.substring(0, className.length() - 3); } private final class SqlMapInvocationBridge implements InvocationHandler { private String namespace; private SqlMapClient sqlMap; public SqlMapInvocationBridge(SqlMapClient sqlMap, String namespace) { this.sqlMap = sqlMap; this.namespace = namespace; } public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { if (method.getReturnType().isAssignableFrom(List.class)) { return sqlMap.queryForList(namespace + "." + method.getName(), args[0]); } else if (method.getReturnType().isAssignableFrom(Map.class)) { return sqlMap.queryForList(namespace + "." + method.getName(), args[0]); } else { return sqlMap.queryForObject(namespace + "." + method.getName(), args[0]); } } } }
Since, at this point it’s only implemented as a thought experiment, it only supports SELECT, but with very little work, it could also allow calls to UPDATE, DELETE, and INSERT.
Suggestions/criticisms are welcome.
Piccolo is a Java and .NET library written to allow the construction of arbitrary 2D GUIs; most notably it provides great support for rotation and scaling.