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:
And with Spring/iBATIS, it requires a similar amount of Java code:
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>
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.