1. 程式人生 > >ibatis遷移mybatis詳細方案(含指令碼和注意事項)

ibatis遷移mybatis詳細方案(含指令碼和注意事項)

小小的宣告:該文章已優先發在阿里內網部落格

ibatis已成過去式,官方早已不再維護,使用ibatis的老應用遷移到mybatis很有必要,且好用的服務層框架springboot集成了mybatis,支援維護良好,更加說明了遷移到mybatis的重要性,下面詳細說明整個遷移流程

1.使用 ibatis到mybatis的map檔案轉換工具

轉換工具:ibatis2mybatis
可以幫你將ibatis 2.x sqlmap檔案轉換為myBatis 3.x mapper檔案,該工具是使用了Ant構建任務進行XSTL轉換和一些語法文字替換
該工具下載下來使用非常簡單,把你要轉換的所有sqlmap檔案放到source資料夾,然後在當前目錄直接執行ant命令即可,轉換成功的mapper檔案放在了destination資料夾下。不過遺憾的是,ant命令並不總是執行成功,跟你的sqlmap檔案有關,比如我當時轉換時報錯如下:

BUILD FAILED
/Users/zhumin/Documents/ibatis2mybatis-ibatis2mybatis-1.0/build.xml:24: javax.xml.transform.TransformerException: javax.xml.transform.TransformerException: com.sun.org.apache.xml.internal.utils.WrappedRuntimeException: unknown protocol: classpath
at com.sun.org.apache.xalan.internal.xsltc
.trax.TransformerImpl.transform(TransformerImpl.java:749) at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:351) at org.apache.tools.ant.taskdefs.optional.TraXLiaison.transform(TraXLiaison.java:197) at org.apache.tools.ant.taskdefs.XSLTProcess.process(XSLTProcess.java
:844) at org.apache.tools.ant.taskdefs.XSLTProcess.execute(XSLTProcess.java:434) at org.apache.tools.ant.UnknownElement.execute(UnknownElement.java:293) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.apache.tools.ant.dispatch.DispatchUtils.execute(DispatchUtils.java:106) at org.apache.tools.ant.Task.perform(Task.java:348) at org.apache.tools.ant.Target.execute(Target.java:435) at org.apache.tools.ant.Target.performTasks(Target.java:456) at org.apache.tools.ant.Project.executeSortedTargets(Project.java:1405) at org.apache.tools.ant.Project.executeTarget(Project.java:1376) at org.apache.tools.ant.helper.DefaultExecutor.executeTargets(DefaultExecutor.java:41) at org.apache.tools.ant.Project.executeTargets(Project.java:1260) at org.apache.tools.ant.Main.runBuild(Main.java:854) at org.apache.tools.ant.Main.startAnt(Main.java:236) at org.apache.tools.ant.launch.Launcher.run(Launcher.java:285) at org.apache.tools.ant.launch.Launcher.main(Launcher.java:112) Caused by: javax.xml.transform.TransformerException: com.sun.org.apache.xml.internal.utils.WrappedRuntimeException: unknown protocol: classpath at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.getDOM(TransformerImpl.java:578) at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:739) ... 21 more Caused by: com.sun.org.apache.xml.internal.utils.WrappedRuntimeException: unknown protocol: classpath at com.sun.org.apache.xalan.internal.xsltc.dom.XSLTCDTMManager.getDTM(XSLTCDTMManager.java:427) at com.sun.org.apache.xalan.internal.xsltc.dom.XSLTCDTMManager.getDTM(XSLTCDTMManager.java:215) at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.getDOM(TransformerImpl.java:556) ... 22 more

看到unknown protocol: classpath錯誤時發現是我的sqlmap DTD檔案找不到,我的sqlmap一開始是這樣的:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "classpath:com/ibatis/sqlmap/engine/builder/xml/sql-map-2.dtd" >
<sqlMap namespace="funUser">
...
</sqlMap>

其中DTDs直接引用的jar包裡的檔案,工具無法識別就會報錯,只能將其去掉,然後就轉換成功了

2. 自己寫指令碼修復轉換後mapper檔案裡的錯誤

不要以為一個工具就完事了,它只是試圖在複雜的工作開始之前提供一個良好的起點,減少一點轉換成本
轉換後的mapper檔案可能仍有一堆無法轉換的部分或者轉換錯誤,以我的應用為例,當時轉換完成後有下面幾個問題:

無法識別ibatis中jdbcType的型別

很多sqlmap檔案寫了resultMap,而且還加了jdbcType,比如:

<resultMap id="resultMap" type="user">
            <result property="id" column="id" jdbcType="BIGINT"/>
            <result property="appName" column="app_name" jdbcType="VARCHAR"/>
            <result property="displayName" column="display_name" jdbcType="VARCHAR"/>
            <result property="avatar" column="avatar" jdbcType="VARCHAR"/>
            <result property="userId" column="user_id" jdbcType="BIGINT"/>
            <result property="gmtCreate" column="gmt_create" jdbcType="DATETIME"/>
            <result property="gmtModified" column="gmt_modified" jdbcType="DATETIME"/>
    </resultMap>

轉換工具保持原樣,而mybatis的jdbcType已經換了型別關鍵詞,不再是BIGINT啊這些,最好方法就是去掉,一個檔案就有這麼多的欄位要改,20個檔案豈不是要手動改幾百次,當然用指令碼了:

sed -i '' 's/ jdbcType.*\/\>/\/\>/g' ./*.xml

注意:mybatis有兩種欄位對映的方式:select別名和resultmap方式,詳細可看:mybatis – MyBatis 3 | Mapper XML 檔案

需要遷移\元素

轉換後的mapper檔案去掉了無法轉換的\元素,因為mybatis裡<typeAlias> 必須從<sqlMap> 元素移動到 <configuration><typeAliases>...</typeAliases></configuration>這個裡面,需要手動加個mybatis-config.xml檔案:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <typeAliases>
        <typeAlias alias="app" type="com.tmall.wireless.fun.core.app.AppDO" />
        ...
        <typeAlias alias="punish" type="com.tmall.wireless.fun.core.punish.PunishDO"/>
    </typeAliases>
</configuration>

個別動態標籤需要手動加上

雖然上面工具能轉換大部分動態sql比如<isNotNull><iterate>,但像<isNotEmpty>這樣的動態sql是不支援的,需要手動轉換

檢查一下具體的sql語句有沒有轉換錯誤

該工具還是比較強大的,可以將你的動態sql轉換成功,比如<isNotNull.*?property=\"(.*?)\"></isNotNull>改為:<if test="$1 != null"></if><iterate>標籤改為<foreach>標籤,但免不了可能存在連線符丟失的情況,比如我有一條sql轉換後少了一個and

3. sql DAO類的改動

ibatis使用spring封裝好的SqlMapClientTemplate進行DAO類的增刪改查,我們只需要繼承SpringFramework中提供的SqlMapClientDaoSupport類即可

/**
 * 把sql map client 注進來
 * 
 * @author 竹敏 2016.3.25
 */
@SuppressWarnings("deprecation")
public class AutowiringSqlMapClientDaoSupport extends SqlMapClientDaoSupport {

        @Resource
        public void injectSqlMapClient(SqlMapClient sqlMapClient) {
                super.setSqlMapClient(sqlMapClient);
    }
}

然後通過getSqlMapClientTemplate()即可進行select/insert/update/delete操作

public class AppDAO extends AutowiringSqlMapClientDaoSupport {

        public AppDO selectAppByName(String appName) {
             return (AppDO) getSqlMapClientTemplate().queryForObject(
             "funApp.selectByName", appName);
        }
...
}

而mybatis採用的是SqlSessionTemplate進行操作,上面的類就要改成:

public class AppDAO {

    @Resource
    private SqlSessionTemplate sqlSessionTemplate;

    public AppDO selectAppByName(String appName) {
        return (AppDO) sqlSessionTemplate.selectOne("funApp.selectByName", appName);
    }
...
}

想想如果有20個檔案(大工程遠不止),每個檔案都要如此改,和完全重寫工作量差不多,有兩種方案可改善:

採用介面卡模式

很簡單,就是寫個適配原來ibatis的DAO操作,介面如下:

/**
 * Created by jinshuan.li on 2016/8/15.
 * 適配ibatis的sqlmaptemplete
 */
public interface SqlMapClientAdapter {

    SqlSession getSqlSession();

    int insert(String statement, Object params);

    <T> T queryForObject(String statement, Object params);

    int update(String statement, Object params);

    <T> List<T> queryForList(String statement, Object params);

    <T> List<T> queryForList(String statement);

    void delete(String statement, Object params);

    <T> T queryForObject(String statement);
}

在介面卡的實現類中呼叫SqlSession的相關方法:

**
 * Created by jinshuan.li on 2016/8/15.
 */
@Repository("sqlSessionMapClient")
public class SqlSessionMapClient implements SqlMapClientAdapter {

    @Resource(name = "sqlSessionTemplate")
    private SqlSession sqlSession;

    @Override
    public SqlSession getSqlSession() {
        return sqlSession;
    }

    @Override
    public int insert(String statement, Object params) {
        return sqlSession.insert(statement, params);
    }

    @Override
    public <T> T queryForObject(String statement, Object params) {
        return sqlSession.selectOne(statement, params);
    }

    @Override
    public int update(String statement, Object params) {
        return sqlSession.update(statement, params);
    }

    @Override
    public <T> List<T> queryForList(String statement, Object params) {
        return sqlSession.selectList(statement, params);
    }

    @Override
    public <T> List<T> queryForList(String statement) {
        return sqlSession.selectList(statement);
    }

    @Override
    public void delete(String statement, Object params) {
        sqlSession.delete(statement, params);
    }

    @Override
    public <T> T queryForObject(String statement) {
        return sqlSession.selectOne(statement);
    }

}

直接指令碼批量改

介面卡方式在大多數情況下都是優選方案,值得推薦,但免不了仍到處到是ibatis的影子,強迫症患者可能不想忍,要改就改徹底了,絕不留下ibatis的影子,那怎麼辦尼?只能手動寫指令碼批量改了,熟悉指令碼的人很快就能寫好,每個人都需要根據自己程式碼情況進行定製指令碼,比如我的如下:

#### ibatis2mybatis_dao.sh
# 把getSqlMapClientTemplate()改成sqlSessionTemplate
find . -name "*DAO\.java" | xargs perl -pi -e 's|getSqlMapClientTemplate\(\)|sqlSessionTemplate|g'

# 方法名稱一一對應改掉,queryForObject——>selectOne,queryForList——>selectList
find . -name "*DAO\.java" | xargs perl -pi -e 's|queryForObject|selectOne|g' 

find . -name "*DAO\.java" | xargs perl -pi -e 's|queryForList|selectList|g'

find . -name "*DAO\.java" | xargs perl -pi -e 's|return[\s]*sqlSessionTemplate [\s]*\.selectList|return sqlSessionTemplate\.selectList|g'

# 去掉不再使用的AutowiringSqlMapClientDaoSupport輔助類
find . -name "*DAO\.java" | xargs perl -pi -e 's|import com\.tmall\.wireless\.fun\.core\.utils\.AutowiringSqlMapClientDaoSupport;||g'

find . -name "*DAO\.java" | xargs perl -pi -e 's| extends AutowiringSqlMapClientDaoSupport||g'

find . -name "*DAO\.java" | xargs sed -i '' '/@SuppressWarnings/d'

# 新增新的SqlSessionTemplate注入
find . -name "*DAO\.java" | xargs sed -i '' '/public class/a\
@Resource' 

find . -name "*DAO\.java" | xargs sed -i '' '/@Resource/G'

find . -name "*DAO\.java" | xargs sed -i '' '/@Resource/a\
private SqlSessionTemplate sqlSessionTemplate;' 

find . -name "*DAO\.java" | xargs sed -i '' '/public class/G' 
find . -name "*DAO\.java" | xargs sed -i '' 's/^@Resource/    @Resource/' 
find . -name "*DAO\.java" | xargs sed -i '' 's/^private/    private^/'

還有一些微調的部分,不適合用指令碼,比如mybatis的insert方法已經不再返回新的id了,而是需要我們呼叫成功後直接從物件引數裡獲取,這裡可能需要手動調整下

4. 資料來源配置變更

上面說了ibatis使用的是SqlMapClientTemplate物件,dataSource配置如下:

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
           http://www.springframework.org/schema/tx  http://www.springframework.org/schema/tx/spring-tx-3.2.xsd  
       "
    default-autowire="byName">
    <tx:annotation-driven transaction-manager="transactionManager"/>
    <bean id="dataSource" class="com.taobao.tddl.jdbc.group.TGroupDataSource" init-method="init">
        <property name="appName" value="TMALL_FUN_APP" />
        <property name="dbGroupKey" value="TMALL_FUN_GROUP" />
    </bean>

    <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocations">
            <list>
                <value>classpath*:dao/sqlMapConfig.xml</value>
            </list>
        </property>
    </bean>

    <bean id="sqlMapClientTemplate" class="org.springframework.orm.ibatis.SqlMapClientTemplate">
        <property name="sqlMapClient" ref="sqlMapClient" />
    </bean>

    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
   </bean>
</beans>

而mybatis使用的是SqlSessionTemplate,資料來源需要改動配置:

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
           http://www.springframework.org/schema/tx  http://www.springframework.org/schema/tx/spring-tx-3.2.xsd  
       "
default-autowire="byName">
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="dataSource" class="com.taobao.tddl.jdbc.group.TGroupDataSource" init-method="init">
<property name="appName" value="TMALL_FUN_APP" />
<property name="dbGroupKey" value="TMALL_FUN_GROUP" />
</bean>
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:dao/mybatis-config.xml"/>
        <property name="mapperLocations" value="classpath:dao/mapper/*.xml"/>
    </bean>

    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg index="0" ref="sqlSessionFactoryBean"/>
    </bean>

    <bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>

</beans>

參考文件