1. 程式人生 > >深入理解MyBatis的原理(三):配置文件用法(續)

深入理解MyBatis的原理(三):配置文件用法(續)

pac amt 單個 gis obb rri tab obj 用戶

前言:前文講解了 MyBatis 的配置文件一部分用法,本文將繼續講解 MyBatis 的配置文件的用法。

目錄

1、typeHandler 類型處理器

2、ObjectFactory

3、插件

4、environments 配置環境

5、databaseIdProvider 數據庫廠商標識

6、引入映射器的方法

1、typeHandler 類型處理器

  MyBatis 在預處理語句(PreparedStatement)中設置一個參數時,或者從結果集(ResultSet)中取出一個值時,都會用註冊了的 typeHandle 進行處理。

  由於數據庫廠商不同,字段類型就不同,所以從 java 傳遞參數到數據庫,或者從數據庫中讀取數據到 java,我們都需要進行字段的處理。typeHandle 的作用,就是將參數從 javaType 轉化為 jdbcType,或者從數據庫中取出結果時把 jdbcType 轉化為 javaType。

  typeHandle 和別名一樣,也分為 MyBatis 系統定義和用戶自定義兩種。  

1.1 dtd 規則

  常用的配置:java 類型(javaType)、JDBC 類型(jdbcType)。

技術分享圖片

1.2 系統定義的 typeHandle

  MyBatis 系統內部就定義了一系列的 typeHandle,源碼在 org.apache.ibatis.type.TypeHandlerRegistry,如下:

技術分享圖片
public TypeHandlerRegistry() {
        this.register((Class)Boolean.class, (TypeHandler)(new
BooleanTypeHandler())); this.register((Class)Boolean.TYPE, (TypeHandler)(new BooleanTypeHandler())); this.register((JdbcType)JdbcType.BOOLEAN, (TypeHandler)(new BooleanTypeHandler())); this.register((JdbcType)JdbcType.BIT, (TypeHandler)(new BooleanTypeHandler())); this
.register((Class)Byte.class, (TypeHandler)(new ByteTypeHandler())); this.register((Class)Byte.TYPE, (TypeHandler)(new ByteTypeHandler())); this.register((JdbcType)JdbcType.TINYINT, (TypeHandler)(new ByteTypeHandler())); this.register((Class)Short.class, (TypeHandler)(new ShortTypeHandler())); this.register((Class)Short.TYPE, (TypeHandler)(new ShortTypeHandler())); this.register((JdbcType)JdbcType.SMALLINT, (TypeHandler)(new ShortTypeHandler())); this.register((Class)Integer.class, (TypeHandler)(new IntegerTypeHandler())); this.register((Class)Integer.TYPE, (TypeHandler)(new IntegerTypeHandler())); this.register((JdbcType)JdbcType.INTEGER, (TypeHandler)(new IntegerTypeHandler())); this.register((Class)Long.class, (TypeHandler)(new LongTypeHandler())); this.register((Class)Long.TYPE, (TypeHandler)(new LongTypeHandler())); this.register((Class)Float.class, (TypeHandler)(new FloatTypeHandler())); this.register((Class)Float.TYPE, (TypeHandler)(new FloatTypeHandler())); this.register((JdbcType)JdbcType.FLOAT, (TypeHandler)(new FloatTypeHandler())); this.register((Class)Double.class, (TypeHandler)(new DoubleTypeHandler())); this.register((Class)Double.TYPE, (TypeHandler)(new DoubleTypeHandler())); this.register((JdbcType)JdbcType.DOUBLE, (TypeHandler)(new DoubleTypeHandler())); this.register((Class)Reader.class, (TypeHandler)(new ClobReaderTypeHandler())); this.register((Class)String.class, (TypeHandler)(new StringTypeHandler())); this.register((Class)String.class, JdbcType.CHAR, (TypeHandler)(new StringTypeHandler())); this.register((Class)String.class, JdbcType.CLOB, (TypeHandler)(new ClobTypeHandler())); this.register((Class)String.class, JdbcType.VARCHAR, (TypeHandler)(new StringTypeHandler())); this.register((Class)String.class, JdbcType.LONGVARCHAR, (TypeHandler)(new ClobTypeHandler())); this.register((Class)String.class, JdbcType.NVARCHAR, (TypeHandler)(new NStringTypeHandler())); this.register((Class)String.class, JdbcType.NCHAR, (TypeHandler)(new NStringTypeHandler())); this.register((Class)String.class, JdbcType.NCLOB, (TypeHandler)(new NClobTypeHandler())); this.register((JdbcType)JdbcType.CHAR, (TypeHandler)(new StringTypeHandler())); this.register((JdbcType)JdbcType.VARCHAR, (TypeHandler)(new StringTypeHandler())); this.register((JdbcType)JdbcType.CLOB, (TypeHandler)(new ClobTypeHandler())); this.register((JdbcType)JdbcType.LONGVARCHAR, (TypeHandler)(new ClobTypeHandler())); this.register((JdbcType)JdbcType.NVARCHAR, (TypeHandler)(new NStringTypeHandler())); this.register((JdbcType)JdbcType.NCHAR, (TypeHandler)(new NStringTypeHandler())); this.register((JdbcType)JdbcType.NCLOB, (TypeHandler)(new NClobTypeHandler())); this.register((Class)Object.class, JdbcType.ARRAY, (TypeHandler)(new ArrayTypeHandler())); this.register((JdbcType)JdbcType.ARRAY, (TypeHandler)(new ArrayTypeHandler())); this.register((Class)BigInteger.class, (TypeHandler)(new BigIntegerTypeHandler())); this.register((JdbcType)JdbcType.BIGINT, (TypeHandler)(new LongTypeHandler())); this.register((Class)BigDecimal.class, (TypeHandler)(new BigDecimalTypeHandler())); this.register((JdbcType)JdbcType.REAL, (TypeHandler)(new BigDecimalTypeHandler())); this.register((JdbcType)JdbcType.DECIMAL, (TypeHandler)(new BigDecimalTypeHandler())); this.register((JdbcType)JdbcType.NUMERIC, (TypeHandler)(new BigDecimalTypeHandler())); this.register((Class)InputStream.class, (TypeHandler)(new BlobInputStreamTypeHandler())); this.register((Class)Byte[].class, (TypeHandler)(new ByteObjectArrayTypeHandler())); this.register((Class)Byte[].class, JdbcType.BLOB, (TypeHandler)(new BlobByteObjectArrayTypeHandler())); this.register((Class)Byte[].class, JdbcType.LONGVARBINARY, (TypeHandler)(new BlobByteObjectArrayTypeHandler())); this.register((Class)byte[].class, (TypeHandler)(new ByteArrayTypeHandler())); this.register((Class)byte[].class, JdbcType.BLOB, (TypeHandler)(new BlobTypeHandler())); this.register((Class)byte[].class, JdbcType.LONGVARBINARY, (TypeHandler)(new BlobTypeHandler())); this.register((JdbcType)JdbcType.LONGVARBINARY, (TypeHandler)(new BlobTypeHandler())); this.register((JdbcType)JdbcType.BLOB, (TypeHandler)(new BlobTypeHandler())); this.register(Object.class, this.UNKNOWN_TYPE_HANDLER); this.register(Object.class, JdbcType.OTHER, this.UNKNOWN_TYPE_HANDLER); this.register(JdbcType.OTHER, this.UNKNOWN_TYPE_HANDLER); this.register((Class)Date.class, (TypeHandler)(new DateTypeHandler())); this.register((Class)Date.class, JdbcType.DATE, (TypeHandler)(new DateOnlyTypeHandler())); this.register((Class)Date.class, JdbcType.TIME, (TypeHandler)(new TimeOnlyTypeHandler())); this.register((JdbcType)JdbcType.TIMESTAMP, (TypeHandler)(new DateTypeHandler())); this.register((JdbcType)JdbcType.DATE, (TypeHandler)(new DateOnlyTypeHandler())); this.register((JdbcType)JdbcType.TIME, (TypeHandler)(new TimeOnlyTypeHandler())); this.register((Class)java.sql.Date.class, (TypeHandler)(new SqlDateTypeHandler())); this.register((Class)Time.class, (TypeHandler)(new SqlTimeTypeHandler())); this.register((Class)Timestamp.class, (TypeHandler)(new SqlTimestampTypeHandler())); if(Jdk.dateAndTimeApiExists) { Java8TypeHandlersRegistrar.registerDateAndTimeHandlers(this); } this.register((Class)Character.class, (TypeHandler)(new CharacterTypeHandler())); this.register((Class)Character.TYPE, (TypeHandler)(new CharacterTypeHandler())); }
View Code

  其中 StringTypeHandle,源碼如下

public class StringTypeHandler extends BaseTypeHandler<String> {
    public StringTypeHandler() {
    }

    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, parameter);
    }

    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return rs.getString(columnName);
    }

    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return rs.getString(columnIndex);
    }

    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return cs.getString(columnIndex);
    }
}

  StringTypeHandle 繼承了 BaseTypeHandle。而 BaseTypeHandle 實現了接口 typeHandle,並且自己定義了 4 個抽象方法。

  setParameter 是 PreparedStatement 對象設置參數,它允許我們自己填寫變換規則。

  getResult 則分為 ResultSet 用列名(columnName)或者使用列下標(columnIndex)來獲取結果數據。其中還包括了 CallableStatement(存儲過程)獲取結果及數據的方法。

1.3 自定義 typeHandler

  從上面系統定義的 typeHandler 中來看,其實已經能夠應付大部分的場景了,但是有一些特殊情況還是需要自定義 typeHandler。

  這裏實現一個字符串參數的 typeHandler。首先配置 XML 文件,確定我們需要處理上面類型的參數和結果。

    <!--自定義類型處理器-->
    <typeHandlers>
        <typeHandler handler="com.yule.mybatis.typehandle.MyStringTypeHandler" javaType="string" jdbcType="VARCHAR"/>
    </typeHandlers>

  或者采取包掃描的方式

    <!--自定義類型處理器-->
    <typeHandlers>
        <package name="com.yule.mybatis.typehandler"/>
    </typeHandlers>

  源碼裏的 StringTypeHandler 使用的是繼承 BaseTypeHandler,而這裏采取實現 TypeHandler 接口來實現。

  自定義的 typeHandler 中配置的 jdbcType,javaType,需要使用註解在 handle 類中綁定。

package com.yule.mybatis.typehandler;

import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import org.apache.ibatis.type.TypeHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * @author yule
 * @date 2018/8/12 17:52
 */
@MappedTypes({String.class})
@MappedJdbcTypes(JdbcType.VARCHAR)
public class MyStringTypeHandler implements TypeHandler<String> {
    private Logger logger = LoggerFactory.getLogger(MyStringTypeHandler.class);

    @Override
    public void setParameter(PreparedStatement preparedStatement, int i, String s, JdbcType jdbcType) throws SQLException {
        logger.info("使用我的 typeHandler");
        preparedStatement.setString(i, s);
    }

    @Override
    public String getResult(ResultSet resultSet, String s) throws SQLException {
        logger.info("使用我的 typeHandler,ResultSet 列名獲取字符串");
        return resultSet.getString(s);
    }

    @Override
    public String getResult(ResultSet resultSet, int i) throws SQLException {
        logger.info("使用我的 typeHandler,ResultSet 下標獲取字符串");
        return resultSet.getString(i);
    }

    @Override
    public String getResult(CallableStatement callableStatement, int i) throws SQLException {
        logger.info("使用我的 typeHandler,CallableStatement 下標獲取字符串");
        return callableStatement.getString(i);
    }
}

  註解 @MappedTypes 定義的是 JavaType 類型,可以指定哪些 Java 類型被攔截。

  註解 @MappedJdbcTypes 定義的是 JdbcType 類型,它需要滿足枚舉類 org.apache.ibatis.type.JdbcType 所列的枚舉類型。

  到這裏,我們還需要去 sql 裏標註哪些參數或結果類型去用我們自定義的 TypeHandler 進行轉換,因為在沒有任何標註的情況,MyBatis 是不會啟用自定義的 TypeHandler。可以通過配置 jdbcType 和 javaType,或者直接使用 typeHandler 屬性指定。

    <select id="queryUserByName" resultType="user">
        select t.id, t.name, t.age from t_user t
        where t.name = #{name,javaType=String,jdbcType=VARCHAR,typeHandler=com.yule.mybatis.typehandler.MyStringTypeHandler}
    </select>

  這時運行 sql,就可以了。

1.4 枚舉類型 typeHandler

2、ObjectFactory

  當 MyBatis 在構建一個結果返回的時候,會使用 ObjectFactory(對象工廠)去創建 POJO,在 MyBatis 中可以定制自己的對象工廠。

  一般情況下,我們使用默認的對象工廠即可,MyBatis 中默認的對象工廠是由 org.apache.ibatis.reflection.factory.DefaultObjectFactory 來提供服務的。

  如果特殊情況下,需要定制特定的工廠,xml 中配置如下:

    <!--自定義對象工廠-->
    <objectFactory type="com.yule.mybatis.objectfactory.MyObjectFactory">
        <property name="name" value="MyObjectFactory"/>
    </objectFactory>

  MyObjectFactory 可以參考 DefaultObjectFactory 的寫法來,這裏為了測試方便,直接繼承 DefaultObjectFactory 來簡化編程。

package com.yule.mybatis.objectfactory;

import org.apache.ibatis.reflection.factory.DefaultObjectFactory;

import java.util.List;
import java.util.Properties;

/**
 * @author yule
 * @date 2018/8/12 19:34
 */
public class MyObjectFactory extends DefaultObjectFactory {

    private static final long serialVersionUID = -8855120656740914948L;
    
    @Override
    public void setProperties(Properties properties) {
        System.out.println("使用我自己的對象工廠 定制屬性");
        super.setProperties(properties);
    }

    @Override
    public <T> T create(Class<T> type) {
        System.out.println("使用我自己的對象工廠 構建單個對象");
        return super.create(type);
    }

    @Override
    public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
        System.out.println("使用我自己的對象工廠 構建對象列表");
        return super.create(type, constructorArgTypes, constructorArgs);
    }

    @Override
    public <T> boolean isCollection(Class<T> aClass) {
        return false;
    }
}

  這樣我們配置的對象工廠就已經生效。

  大部分情況下,我們使用系統默認的對象工廠即可。

3、插件

4、environments 配置環境

4.1 概述

  配置環境可以註冊多個數據源(dataSource),每一個數據源分為兩大部分:數據源的配置和數據庫事務(transactionManager)的配置。

    <!--配置環境-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC">
                <property name="autoCommit" value="false"/>
            </transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${datasourceurl}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>

  environments 中的屬性 default,標明在缺省的情況下,我們將啟用哪個數據源配置。

  environment 元素是配置一個數據源,屬性 id 是設置這個數據源的唯一標識,以便 MyBatis 上下文使用它。

  transactionManager 配置的是數據庫事務,其中 type 屬性有 3 中配置方式:

  (1)JDBC,采用 JDBC 方式管理事務,在獨立編碼中我們常常使用。

  (2)MANAGED,采用容器方式管理事務,在 JNDI 數據源中常用。

  (3)自定義,由使用者自定義數據庫事務管理方式,適用於特殊應用。

  property 元素則是可以配置數據源的各類屬性,我們這個配置了 autoCommit = false,則是要求數據源不自動提交。

  dataSource 標簽,是配置數據源連接的信息,type 屬性是提供我們隊數據庫連接方式的配置,同樣 MyBatis 提供幾種配置方式:

  (1)UNPOOLED,非連接池數據庫(UnpooledDataSource)。

  (2)POOLED,連接池數據庫(PooledDataSource)。

  (3)自定義數據源。

  其中,配置的 property 元素,就是定義數據庫的各類參數。

4.2 數據源

  MyBatis 內部提供了 3 中數據源的實現方式:

  (1)UNPOOLED,非連接池,使用org.apache.ibatis.datasource.unpooled.UnpooledDataSource 實現。

  (2)POOLED,連接池,使用 org.apache.ibatis.datasource.pooled.PooledDataSource 實現。

  (3)JNDI,使用 org.apache.ibatis.datasource.jndi.JndiDataSourceFactory 來獲取數據源。

  這 3 中方式實現比較簡單,只需要在 dataSource 標簽的 type定義為 UNPOOLED,POOLED,JNDI 即可。

  如果還需要其他的數據源,可以自定義一個類 MyDataSourceFactory,實現 org.apache.ibatis.datasource.DataSourceFactory 接口,可以參考 JndiDataSourceFactory,然後在 dataSource 標簽中配置 type="xxx.xxx.MyDataSourceFactory",即可。

5、databaseIdProvider 數據庫廠商標識

  如果 MyBatis 需要運行在不同廠商的數據庫中,會需要使用到數據庫廠商標識,它為此提供一個數據庫標識,並提供自定義,作用在於指定 SQL 到對應的數據庫廠商提供的數據庫中運行。

  用的很少,所以就不先不在這裏講解。

6、引入映射器的方法

  引入映射器的幾種方式:

    <!-- 引入映射器 的幾種方式 -->
    <mappers>
        <!--使用類註冊引入-->
        <!--<mapper class="com.yule.user.dao.UserDao"/>-->
        <!--使用文件路徑引入-->
        <mapper resource="com/yule/user/dao/UserDao.xml"/>
        <!--使用包名引入映射器-->
        <!--<package name="com.yule.user.dao"/>-->
        <!--使用 userDao.xml 引入-->
        <!--<mapper url="file:F:\IDEAworkspace\sdemo\src\main\java\com\yule\user\dao\UserDao.xml"/>-->
    </mappers>

深入理解MyBatis的原理(三):配置文件用法(續)