A lightweight solution for mybatis to support database lightweight compatibility

Keywords: Mybatis xml Oracle MySQL

I love mybatis because of the freedom of sql mapping, sql in the hand, everything I have that feeling!
However, due to its incomplete ORM framework, we gain the freedom of SQL and lose the natural feature of ORM-compatible multilibraries.
This article tells you how to make mybatis lightweight support database compatibility?

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

Warm Tip: Because this method is too simple and rude, it comes out later. You can only use it if your mybatis version is higher than 3.1!

Implementation

First

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>

Configure a vendorProperties to store the productName and its corresponding alias, with productName as key and alias value, then configure a databaseIdProvider to associate vendorProperties with the configuration. Finally, add it in 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>

Just add a <databaseIdProvider type="DB_VENDOR">node

Friendly note, when using the mybatis-config.xml method, you must ensure that environments, their transactionManager and dataSource are defined in the configuration file, otherwise the databaseIdProvider will not work, that is, when using spring to manage mybatis, this method will not work!

Second

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>

After my own testing, mybatis's logic for finding statement is: first find the one with databaseId, if there is a corresponding one, [1] if there is one, then call it directly (Friendly Tip: don't force two identical databaseIds and the statement id is the same), [1] if there is no, then look for the statement without specifying databaseId, [2] if there is one, call it, [2] no more, report error!
That is to say, you can mainly use one kind of database, such as MySQL, Oracle is used by some modules, that is, when writing xml, you usually do not write databaseId directly. Only the Oracle module writes databaseId="oracle". This is the case for the main building company. Another case is that the whole project needs compatibility, so the amount of modification is relatively large, requiring two statemes.NT Indicates different databaseIds.

A Simple Analysis of the Principle

After I took the time to check the source code: SqlSessionFactoryBean, the corresponding databaseIdProvider code, I finally found such a piece of code:

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);
    }
}

This code passes the dataSource to the databaseIdProvider and lets the databaseIdProvider get the databaseId and set it to mybatis!

Here's how the databaseIdProvider gets the 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;
    }

Originally, the connection.getMetaData.getDatabaseProductName() method resulted in an East East, which is the key of the properties in the configuration. Then look at the following code:

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;
        }
    }

Get the actual productName of the current data source from the connection, such as MySQL, Oracle. Then use the configured properties to determine if it contains this key, and return if it does! Otherwise return productName directly!

The above source code is how to get it when analyzing the databaseId, how to set it in mybatis. It's not shy how mybatis judges the corresponding statement based on the databaseId, but I'm a little white, I haven't seen the mybatis source code, and I'm not familiar with it. It's too time-consuming to find it, and I'm not fussy in society.Free!

Posted by kurtis on Sun, 30 Jun 2019 17:41:08 -0700