1. 程式人生 > >僅改2處配置, 令mybatis相容多種資料庫(非專業方案, 源自官方支援)

僅改2處配置, 令mybatis相容多種資料庫(非專業方案, 源自官方支援)

愛上mybatis是因為自由自在的sql對映, SQL在手, 一切我有的那種感覺!
然而由於其非完整ORM框架原因, 導致我們掌握了SQL的自由, 卻失去了ORM相容多庫的天然特性
本文章告訴你, 如何使 mybatis 輕量支援資料庫相容?

Beacuse of some reason , I write this line , just like because of some reason , you read this line !

溫馨提示: 由於此方法過於簡單粗暴, 所以面世較晚 , 當你的mybatis版本高於3.1時,才可以用哦!

實現方式

第一處

applicationContext.xml or mybatis-config.xml

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"
> <context:property-placeholder location="classpath*:jdbc.properties" ignore-unresolvable="true"/> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="databaseIdProvider" ref="databaseIdProvider" /> <property name="configLocation" value="classpath:SqlMapConfig.xml" /> <property name="mapperLocations" value="classpath*:sqlmap/*.xml" /> </bean> <bean id="vendorProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean"> <property name="properties"> <props> <prop key="Oracle">oracle</prop> <prop key="MySQL">mysql</prop> <prop key="DB2">db2</prop> <prop key="Adaptive Server Enterprise">sybase</prop> <prop key="SQL Server">sqlserver</prop> </props> </property> </bean> <bean id="databaseIdProvider" class="org.apache.ibatis.mapping.VendorDatabaseIdProvider"> <property name="properties" ref="vendorProperties" /> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.xxx" /> <property name="markerInterface" value="com.xxx.SqlMapper"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> </bean> <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <tx:annotation-driven transaction-manager="transactionManager"/> </beans>

先配置一個vendorProperties, 儲存productName和其對應的別名, productName為key, 別名為value , 然後配置一個databaseIdProvider, 關聯vendorProperties, 使其拿到配置. 最後再sqlSessionFactory中新增
<property name="databaseIdProvider" ref="databaseIdProvider" />即可

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD SQL MAP Config 3.1//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="callSettersOnNulls" value="true"/>
        <setting name="jdbcTypeForNull" value="NULL"/>
    </settings>

    <databaseIdProvider type="DB_VENDOR">
        <property name="MySQL" value="mysql"/>
        <property name="Oracle" value="oracle" />
        <property name="SQL Server" value="sqlserver"/>
        <property name="DB2" value="DB2" />
    </databaseIdProvider>

</configuration>

直接加個<databaseIdProvider type="DB_VENDOR"> 節點就行了

友情提示, 使用mybatis-config.xml方式時, 必須確保改配置檔案中定義了environments及其transactionManager和dataSource,否則databaseIdProvider將不生效, 也就是說, 使用spring管理mybatis時, 此方式失效!

第二處

UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xxx.UserMapper">

    <resultMap id="result" type="com.xxx.UserModel"/>

    <select id="findAll" resultMap="result" databaseId="mysql">
        SELECT *
        FROM users
        ORDER BY id ASC
    </select>

    <select id="findAll" resultMap="result" databaseId="oracle">
        SELECT *
        FROM users
        ORDER BY id ASC
    </select>

</mapper>

經過本人實測, mybatis查詢 statement 的邏輯是 : 先找有databaseId的, 是否有對應的, [1] 若有, 則直接呼叫(友情提示:彆強行寫2個相同的databaseId且statement id也相同), [1] 若無, 則查詢是否有 未指定databaseId的 statement , [2] 若有, 則呼叫, [2] 再沒有, 就報錯了 !
也就是說, 你完全可以以一種資料庫為主, 如MySQL為主, Oracle則是某些模組使用, 即寫xml時, 一般都直接不寫databaseId, 唯有Oracle那個模組才寫上 databaseId=”oracle” .比如樓主公司, 就是這種情況! 而另一種情況則是, 整個專案都需要相容, 那修改量就相對大一些, 需要2個statement分別標明不同的databaseId.

原理簡單剖析

經過本人不辭辛苦的檢視原始碼: SqlSessionFactoryBean, 對應的databaseIdProvider程式碼, 最終發現這麼一段程式碼:

Environment var29 = new Environment(this.environment, this.transactionFactory, this.dataSource);
configuration.setEnvironment(var29);
if(this.databaseIdProvider != null) {
    try {
        configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
    } catch (SQLException var22) {
        throw new NestedIOException("Failed getting a databaseId", var22);
    }
}

這段程式碼是將dataSource傳給databaseIdProvider,讓databaseIdProvider獲取databaseId,並設定到mybatis中!

下面看databaseIdProvider如何獲取databaseId

private String getDatabaseProductName(DataSource dataSource) throws SQLException {
        Connection con = null;

        String var4;
        try {
            con = dataSource.getConnection();
            DatabaseMetaData metaData = con.getMetaData();
            var4 = metaData.getDatabaseProductName();
        } finally {
            if(con != null) {
                try {
                    con.close();
                } catch (SQLException var11) {
                    ;
                }
            }

        }

        return var4;
    }

原來最終時通過connection.getMetaData.getDatabaseProductName()方法得到一個東東,就是配置中properties的 key , 然後看下面這段程式碼:

private String getDatabaseName(DataSource dataSource) throws SQLException {
        String productName = this.getDatabaseProductName(dataSource);
        if(this.properties != null) {
            Iterator i$ = this.properties.entrySet().iterator();

            Entry property;
            do {
                if(!i$.hasNext()) {
                    return null;
                }

                property = (Entry)i$.next();
            } while(!productName.contains((String)property.getKey()));

            return (String)property.getValue();
        } else {
            return productName;
        }
    }

根據connection獲得了當前資料來源真實的productName, 如MySQL, Oracle .而後通過配置進來的properties判斷是否包含這個 key , 若有進行返回! 無則直接返回productName !

以上的原始碼都是分析databaseId時如何獲取, 如何被設定到mybatis中的, 至於mybatis如何根據databaseId判斷對應的statement, 那還不是小意思, 不過本人小白一個, 沒有看過mybatis原始碼, 對其不熟悉, 要找這塊原始碼太費時間, 浮躁的社會我也不能倖免 !