Wednesday, September 9, 2009

Test Driving IBATIS 3 (and Spring3)

As a big fan and heavy user of iBATIS SqlMaps, i have wanted to give iBATIS version 3 a look for some time, but due to lack of time and limited documentation, I had not gotten around to it. But the beta version is now here, and with it a very detailed and well written document, and I finally had some time between projects, so I figured i could delay no longer. I immediately headed to check the latest version of Spring (3.0M4 as of this post) but i see that there is no built in support for it yet, though there is some JIRA activity on the iBATIS and Spring side trying to make this happen.

The goal of my first encounter with iBATIS 3 was to get a small project up and running, where the iBATIS session would be provided to me by the Spring container. This would give me the datasource and transactions power of Spring even though i would not have the full DataAccessException hierarchy and Runtime Exception changes.

As the iBATIS folks recommended, i am using maven to create the project (a tool i have a lot of reservations about), and am followiing their folder structure. In order to do this with Spring 3 M4, i needed to add the following repository to my pom.xml

   1: <repository>
   2:     <id>spring-releases</id>
   3:     <name>Spring Maven RELEASE Repository</name>
   4:     <url>http://s3.amazonaws.com/maven.springframework.org/release</url>
   5: </repository>

The end result is that my project looks like this:

As recommended in the documentation, I added a new folder within my package structure called “data” where all my configuration files will be stored. These files, are my Spring Application Context (applicationContext.xml), the SqlMapConfig file for Ibatis configuration (minus datasource information), and the Mapper XML file. There is one new file there, an interface with the same name as the SqlMap file. This is a new feature of iBATIS3 that enables type safety. Each mapping will have a function defined in this interface. I may also choose to implement this interface for my dao functions, so the interface could be useful.

Let’s start with the application Context. It seems to me that Spring has come a long way to solve the length of our application context, by enabling the use of annotation in our java code, as well as the shorthand notation in our XML file. So, here is my very simple application Context that contains 2 beans, the datasource and a call to a static method used by iBATIS to read the SqlMapConfig.xml. This file still contains the mapping files as well as global settings, but obviously no datasource settings. Of course, since i will be using JavaConfig, i need to configure the annotation configuration as well. So, here it is

   1: <?xml version="1.0" encoding="UTF-8"?>
   2: <beans xmlns="http://www.springframework.org/schema/beans"
   3:        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   4:        xmlns:context="http://www.springframework.org/schema/context"
   5:        xmlns:aop="http://www.springframework.org/schema/aop"
   6:         xmlns:p="http://www.springframework.org/schema/p"
   7:        xsi:schemaLocation="
   8:     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
   9:     http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
  10:  
  11:  
  12:     <context:property-placeholder location="classpath:properties/config.properties"/>
  13:     <context:annotation-config/>
  14:     <context:component-scan base-package="com.dr.ibatis3app"/>
  15:  
  16:  
  17:     <bean id="dataSource"
  18:           class="org.springframework.jdbc.datasource.DriverManagerDataSource"
  19:                 p:driverClassName="${jdbc.driver}"
  20:                 p:url="${jdbc.url}"
  21:                 p:username="${jdbc.username}"
  22:                 p:password="${jdbc.password}"/>
  23:  
  24:  
  25:     <bean id="ibatisResourceReader" class="org.apache.ibatis.io.Resources"
  26:           factory-method="getResourceAsReader">
  27:             <constructor-arg value="com/dr/ibatis3app/data/SqlMapConfig.xml"/>
  28:     </bean>
  29:  
  30: </beans>

The rest of the beans needed for my project are defined below in the JavaConfig file. The reason is obvious when you see the code:

   1: @Configuration
   2: public class AppConfig {
   3:     @Resource
   4:     DataSource dataSource;
   5:  
   6:     @Resource(name="ibatisResourceReader")
   7:     Reader sqlMapConfigReader;
   8:     
   9:     @Bean
  10:     public SqlSessionFactory sqlSessionFactoryBuilder() throws IOException {
  11:         System.out.println("Calling SqlSessionFactoryBuilder build method");
  12:         SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
  13:         return builder.build(sqlMapConfigReader);
  14:     }
  15:  
  16:     @Bean
  17:     @Scope("prototype")
  18:     public SqlSession sqlSession() throws DataAccessException {
  19:         try {
  20:             return sqlSessionFactoryBuilder().openSession(dataSource.getConnection());
  21:         } catch (SQLException e) {
  22:             throw new DataSourceLookupFailureException("Unable to open session");
  23:         } catch (IOException e) {
  24:             throw new DataSourceLookupFailureException("Error opening sqlconfig");
  25:         }
  26:     }
  27: }


Rather than using messy method calls in the XML file, i can have a few simple lines of Java that handle the instantiation of the SqlSession, yet the resource file name is injected from the XML file so this is easily changed. The SqlSession is defined as scope prototype as it is not thread safe.

The mapper file is straightforward, with the main change being the required namespace to match the exact location of the interface and XML files.

   1: package com.dr.ibatis3app.data;
   2:  
   3: public interface CardMapper {
   4:     public String getPrimAcctNum(int acctId);
   5: }
   6:  
   7: //XML file is below
   8: <?xml version="1.0" encoding="UTF-8" ?>
   9: <!DOCTYPE mapper
  10:         PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
  11:         "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd">
  12: <mapper namespace="com.dr.ibatis3app.data.CardMapper">
  13:     <select id="getPrimAcctNum" parameterType="int" resultType="String">
  14:         SELECT prim_acct_num FROM acct WHERE acct_id = #{id}
  15:     </select>
  16: </mapper>


And now we use the code:

   1: public static void main(String[] args) {
   2:     
   3:     ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("com/dr/ibatis3app/data/applicationContext.xml");
   4:     SqlSession session = ctx.getBean("sqlSession", SqlSession.class);
   5:     try {
   6:         CardMapper mapper = session.getMapper(CardMapper.class);
   7:         String  cardNum = mapper.getPrimAcctNum(4185576);
   8:         System.out.println("CARDNUM IS " + cardNum);
   9:     } finally {
  10:         session.close();
  11:     }
  12: }

And that’s it. We have a working application, and can start to kick the tires a bit.

3 comments:

  1. Anyone have some better idea for inserting code? I am using windows live writer and after i create it, there are loads of blank lines i need to manually delete, since they are not visible in the windows live writer, only in the google editor

    ReplyDelete
  2. Plz how can i create this type of project in Maven??

    ReplyDelete
  3. Great walk through! Thanks for posting this!

    ReplyDelete