iBATISからMyBatis3に移行した

Spring + iBATISで構築された古いシステムをSpring + MyBatis3にやっと移行しました。今までも何度か移行を試みましたがSQLの定義ファイルが1000を超えており、MyBatis用の定義ファイルへの移行を手作業で行うには手間がかかりすぎるのと、[公式？](https://github.com/mybatis/ibatis2mybatis)のツールではうまく移行が出来なかったので断念していました。しかし、MyBatisへの移行ができないとSpringのバージョンアップもできないため流石にまずいと思い、定義ファイルの移行ツールを作成し無事移行を完了させました。あまり需要はない気はしますが、どこかで困っている人に少しでも役に立てばと思い公開することにしました。

## 公式ツールではうまく移行できなかったこと

GitHubにある[mybatis/ibatis2mybatis](https://github.com/mybatis/ibatis2mybatis)が公式のツールと思われますが、このツールでは以下の場合にうまく移行できなかったため移行ツールを作ることにしました。（他にもあった気がしますが忘れました。。。）

* `<dynamic>`, `<isPropertyAvailable>`, `<isNotPropertyAvailable>`タグを利用
* `<isEqual>`,`<isNotEqual>`, `<iterate>`などのタグで`property`属性が未定義の場合

## 移行作業

### ライブラリの変更

* Gradleの設定を変更し、MyBatisのライブラリを使うようにします

#### 変更前
```
compile group: 'org.apache.ibatis', name: 'ibatis-sqlmap', version: '2.3.4.726'
```

#### 変更後
```
compile group: 'org.mybatis', name: 'mybatis', version: '3.4.5'
compile group: 'org.mybatis', name: 'mybatis-spring', version: '1.3.1'
```

### 設定の変更

* `SqlMapClientFactoryBean`から`SqlSessionFactoryBean`を使うように変更します。
* iBATISではSELECT文で取得した結果をMapオブジェクトにマッピングする場合に、nullの場合にもマッピングされていました。しかし、MyBatisではデフォルトではnullの場合はマッピングしないようになっているので、同じ動きにするために設定(`callSettersOnNulls`)を変更します。
* iBATISでは全ての結果がnullの行もマッピングされ空のインスタンスが返されるようになっていました。しかし、MyBatisではnullが返るようになっているので、同じ動きにするために設定(`returnInstanceForEmptyRow`)を変更します。ただし、この変更によりネストされた結果をマッピングする際にも空のインスタンスが返るようになります。この場合、iBATISではnullが返ってきていたので同じ動きにするため、後述する移行ツールでは`notNullColumn`を設定するようにしています。

#### 変更前
```xml
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean" scope="singleton">
  <property name="configLocations">
    <list>
       <value>classpath:/ibatis.xml</value>
    </list>
  </property>
  <property name="dataSource" ref="dataSource" />
</bean>
```

#### 変更後
```xml
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="mapperLocations" value="classpath*:/mybatis/**/*.xml" />
  <property name="configuration">
    <bean class="org.apache.ibatis.session.Configuration">
      <property name="callSettersOnNulls" value="true"/>
      <property name="returnInstanceForEmptyRow" value="true"/>
    </bean>
  </property>
</bean>
```

### コードの変更

* 継承元を`SqlMapClientDaoSupport`から`SqlSessionDaoSupport`に変更します
* `getSqlMapClient()`を`getSqlSession()`に変更します
* `getSqlMapClient().queryForObject()`を`getSqlSession().selectOne()`に変更します
* `getSqlMapClient().queryForList()`を`getSqlSession().selectList()`に変更します

#### 変更前
```java
public class Dao extends SqlMapClientDaoSupport {
    public Object find(String id) throws SQLException {
        return getSqlMapClient().queryForObject(id);
    }
    public List findList(String id) throws SQLException {
        return getSqlMapClient().queryForList(id);
    }
}
```

#### 変更後
```java
public class Dao extends SqlSessionDaoSupport {
    public Object find(String id) throws SQLException {
        return getSqlSession().selectOne(id);
    }
    public List findList(String id) throws SQLException {
        return getSqlSession().selectList(id);
    }
}
```

### sqlMapファイルをmapperファイルに移行

iBATISのsqlMapファイルをMyBatisのmapperファイルに移行するためのツールを作成しましたのでそれを利用します。このツールの実行にはJDKが必要になりますので、あらかじめインストールしておいてください。

[GitHub - ogasada/ibatisToMyBatis3](https://github.com/ogasada/ibatisToMyBatis3)

1. GitHubからプロジェクトを取得します  
`$ git clone https://github.com/ogasada/ibatisToMyBatis3.git`
2. 設定ファイルを変更し、移行元のsqlMapファイルが格納されているディレクトリを指定します
    * `gradle.properties`ファイル内の`systemProp.targetDir`に格納ディレクトリのフルパスを指定します
3. プログラムを実行しmapperファイルに移行します  
`$ ./gradlew convert`
    * 移行プログラムではsqlMapファイルをmapperファイルに上書きします。そのため、プログラム実行時に自動的にバックアップを作成します。バックアップディレクトリは設定ファイルで指定したディレクトリと同じ場所に作成します。


移行ツールは筆者が必要な機能に限定しているため不十分な場合もあるかもしれません。プログラム自体は大した内容ではないので適当に変えてもらえれば機能追加できると思います。