This is a continuing series on what I'm doing to make AppFuse a better application in Winter/Spring 2004. Previous titles include: Changing the Directory Structure, Spring Integration and Remember Me refactorings.
- - - -
On my last project, we ported an existing JSP/Servlet/JDBC app to use JSP/Struts/iBATIS. In the process, I got to learn a lot about iBATIS and grew to love the framework (although I prefer to spell it iBatis). It was super easy to port the existing JDBC-based application because all of the SQL was already written (in PreparedStatements). Don't get me wrong, I think Hibernate is the better O/R Framework of the two, but iBATIS works great for existing databases. The best part is that iBATIS is just as easy to code as Hibernate is. For example, here's how to retrieve an object with Spring/Hibernate:
List users =
getHibernateTemplate().find("from User u where u.username=?", username);
|
And with Spring/iBATIS, it requires a similar amount of Java code:
List users = getSqlMapTemplate().executeQueryForList("getUser", user);
|
The main difference between the two is that iBATIS uses SQL and Hibernate uses a mapping file. Here's the "getUser" mapped statement for iBATIS:
<mapped-statement name="getUser" result-class="org.appfuse.model.User">
SELECT * FROM app_user WHERE username=#username#;
</mapped-statement>
Spring makes it super easy to configure your DAOs to use either Hibernate or iBATIS. For Hibernate DAOs, you
can simply extend HibernateDaoSupport and for iBATIS DAOs you can extend SqlMapDaoSupport.
Now to the point of this post: How I replaced Hibernate with iBATIS. The first thing I had to do was write the XML/SQL mapping files for iBATIS. This was actually the hardest part - once I got the SQL statements right, everything worked. One major difference between iBATIS and Hibernate was I had to manually fetch children and manually create primary keys. For primary key generation, I took a very simple approach: doing a max(id) on the table's id and then adding 1. I suppose I could also use the RandomGUID generator - but I prefer Longs for primary keys. Hibernate is pretty slick because it allows easy mapping to children and built-in generation of primary keys. The ability to generate the mapping file with XDoclet is also a huge plus.
As far as integrating iBATIS into AppFuse, I created an installer in contrib/ibatis. If you navigate to this directory (from the command line), you can execute any of the following targets with Ant. It might not be the most robust installer (it'll create duplicates if run twice), but it seems to work good enough.
install: installs iBatis into AppFuse
uninstall: uninstalls iBatis from AppFuse
uninstall-hibernate: uninstalls Hibernate from AppFuse
help: Print this help text.
All of these targets simply parse lib.properties, build.xml and properties.xml to add/delete iBATIS stuff or delete Hibernate stuff. They also install/remove JARs and source .java and .sql files. If you're going to run this installer, I recommend running "ant install uninstall-hibernate". Of course, you can also simply "install" it and then change the dao.type in properties.xml. This will allow you to use both Hibernate and iBATIS DAOs side-by-side. To use both Hibernate and iBATIS in an application, you could create an applicationContext-hibatis.xml file
in src/dao/org/appfuse/persistence and change the dao.type to be hibatis (like that nickname ;-). In this file, you'd have to then define your transactionManager and sqlMap/sessionFactory. I tested this and it works pretty slick. Click here to see my applicationContext-hibatis.xml file.
<!-- Hibernate SessionFactory -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
<property name="dataSource"><ref local="dataSource"/></property>
<property name="mappingResources">
<list>
<value>org/appfuse/model/Role.hbm.xml</value>
<value>org/appfuse/model/User.hbm.xml</value>
<value>org/appfuse/model/UserCookie.hbm.xml</value>
<value>org/appfuse/model/UserRole.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">@HIBERNATE-DIALECT@</prop>
</props>
</property>
</bean>
<!-- SqlMap setup for iBATIS Database Layer -->
<bean id="sqlMap" class="org.springframework.orm.ibatis.SqlMapFactoryBean">
<property name="configLocation">
<value>classpath:/org/appfuse/persistence/ibatis/sql-map-config.xml</value>
</property>
</bean>
<!-- Transaction manager for a single JDBC DataSource -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource"><ref local="dataSource"/></property>
</bean>
<!-- LookupDAO: iBatis implementation -->
<bean id="lookupDAO" class="org.appfuse.persistence.ibatis.LookupDAOiBatis">
<property name="dataSource"><ref local="dataSource"/></property>
<property name="sqlMap"><ref local="sqlMap"/></property>
</bean>
<!-- UserDAO: Hibernate implementation -->
<bean id="userDAO" class="org.appfuse.persistence.hibernate.UserDAOHibernate">
<property name="sessionFactory"><ref local="sessionFactory"/></property>
</bean>
Some things I noticed in the process of developing this:
- Running "ant clean test-dao" with iBATIS (28 seconds) is a bit faster than Hibernate (33 seconds). I'm sure if I optimized Hibernate, I could make these numbers equal.
- The iBATIS install is about 500K, whereas Hibernate's JARs are around 2 MB. So using iBATIS will get you a slightly faster and smaller AppFuse application, but it's a bit harder to manipulate the database on the fly. There's no way of generating the tables/columns with iBATIS. Instead it uses a table creation script - so if you add new persistent objects, you'll have to manually edit the table creation SQL.
Hibernate is still the right decision for me, but it's cool that iBATIS is an option. Even cooler is the fact that you can mix and match Hibernate and iBATIS DAOs.
Download or read the (rather long) set of release notes. The good thing is that all tests pass with AppFuse - so that seems like a good release to me! You can also read a condensed version of the release notes.
Changes since 1.0 M4 include:
- restructured core, beans and util packages
- reworked PropertyPlaceholderConfigurer, now also able to resolve system properties
- new ReloadableResourceBundleMessageSource, supporting hot reloading of message definition files
- support for transaction suspension, via new propagation behaviors REQUIRES_NEW and NOT_SUPPORTED
- javax.transaction.TransactionManager support in JtaTransactionManager, for transaction suspension
- support for local JOTM and XAPool instances
- support for CLOB handling in DefaultLobCreator and OracleLobCreator
- new "evict", "saveOrUpdateCopy" and overloaded "delete" and "update" methods in HibernateTemplate
- revised Hibernate-JTA synchronization, respecting a registered Hibernate TransactionManagerLookup
- support for Hessian and Burlap 3.x
- removed deprecated "beanName" property from BaseCommandController
- various refinements and minor renamings in the web support
- revised distribution JAR files, now one full spring.jar and various fine-grained module-specific JARs
- refined framework build process to work standalone
- new sample application "imagedb", illustrating BLOB/CLOB support, multipart file upload, Velocity view