1. 程式人生 > >【MyBatis學習筆記】5:認識使用typeHandlers配置型別處理器

【MyBatis學習筆記】5:認識使用typeHandlers配置型別處理器

簡述

註冊了的型別處理器會用於處理下面兩種情形:

  • 為PreparedStatement設定一個引數,將引數從Java型別轉為JDBC型別。
  • 從ResultSet中取出一個值,將結果從JDBC型別轉為Java型別。

型別處理器可分為以下兩類:

  • MyBatis系統定義的型別處理器
  • 使用者自定義的型別處理器

認識系統定義的型別處理器

org.apache.ibatis.type.TypeHandlerRegistry類中的構造器可以看到系統註冊的大量的型別處理器,如其中一段:

    register(Reader.class, new ClobReaderTypeHandler());
    register
(String.class, new StringTypeHandler()); register(String.class, JdbcType.CHAR, new StringTypeHandler()); register(String.class, JdbcType.CLOB, new ClobTypeHandler()); register(String.class, JdbcType.VARCHAR, new StringTypeHandler()); register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler()); register
(String.class, JdbcType.NVARCHAR, new NStringTypeHandler()); register(String.class, JdbcType.NCHAR, new NStringTypeHandler()); register(String.class, JdbcType.NCLOB, new NClobTypeHandler()); register(JdbcType.CHAR, new StringTypeHandler()); register(JdbcType.VARCHAR, new StringTypeHandler()); register
(JdbcType.CLOB, new ClobTypeHandler()); register(JdbcType.LONGVARCHAR, new ClobTypeHandler()); register(JdbcType.NVARCHAR, new NStringTypeHandler()); register(JdbcType.NCHAR, new NStringTypeHandler()); register(JdbcType.NCLOB, new NClobTypeHandler());

點開其中的org.apache.ibatis.type.StringTypeHandler類檢視一下具體實現:

//繼承BaseTypeHandler抽象介面,間接實現了必要的TypeHandler介面
public class StringTypeHandler extends BaseTypeHandler<String> {

    //設定非空引數(要設定引數的預處理語句,設定引數的下標,使用的Java型別字串,對應資料庫的JDBC型別)
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
            throws SQLException {
        ps.setString(i, parameter);
    }

    //從結果集中按列名獲取(結果集,列名)
    @Override
    public String getNullableResult(ResultSet rs, String columnName)
            throws SQLException {
        return rs.getString(columnName);
    }

    //從結果集中按列下標獲取(結果集,列下標)
    @Override
    public String getNullableResult(ResultSet rs, int columnIndex)
            throws SQLException {
        return rs.getString(columnIndex);
    }

    //從儲存過程中按列下標獲取(儲存過程,列下標)
    @Override
    public String getNullableResult(CallableStatement cs, int columnIndex)
            throws SQLException {
        return cs.getString(columnIndex);
    }
}

自定義的型別處理器

按照書上的例子,先嚐試自定義覆蓋掉一個系統定義的型別處理器,實現StringVARCHAR的轉換。

配置檔案中

<!--註冊自定義的型別處理器-->
<typeHandlers>
    <typeHandler jdbcType="VARCHAR" javaType="string" handler="test.MyStringTypeHandler"/>
</typeHandlers>

該型別處理器類

一定要實現TypeHandler介面,可以像前面系統實現的那樣通過繼承BaseTypeHandler類來實現,也可以直接實現。

對於處理器類,使用@MappedTypes註解Java型別;使用@MappedJdbcTypes註解JDBC型別,即org.apache.ibatis.type.JdbcType列舉類所列的列舉型別。

package test;

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 java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

//Java型別為String
@MappedTypes({String.class})
//JDBC型別為VARCHAR
@MappedJdbcTypes(JdbcType.VARCHAR)
//直接實現TypeHandler介面,泛型<T>影響了後三個方法的返回值<T>
public class MyStringTypeHandler implements TypeHandler<String> {

    //為PreparedStatement設定引數
    @Override
    public void setParameter(PreparedStatement preparedStatement, int i, String s, JdbcType jdbcType) throws SQLException {
        System.out.println("呼叫了自定的setParameter");
        preparedStatement.setString(i, s);
    }

    //從結果集中按列名獲取
    @Override
    public String getResult(ResultSet resultSet, String s) throws SQLException {
        System.out.println("呼叫了自定的getResult(1)");
        return resultSet.getString(s);
    }

    //從結果集中按列號獲取
    @Override
    public String getResult(ResultSet resultSet, int i) throws SQLException {
        System.out.println("呼叫了自定的getResult(2)");
        return resultSet.getString(i);
    }

    //從儲存過程中按列號獲取
    @Override
    public String getResult(CallableStatement callableStatement, int i) throws SQLException {
        System.out.println("呼叫了自定的getResult(3)");
        return callableStatement.getString(i);
    }
}

不改變其行為,只是在呼叫其內方法時多輸出一句話,以驗證是否配置上了這個自定義的型別處理器。

對映檔案中

  • 為對映檔案新增<resultMap>標籤(構建ResultSet型別處理器)
  • 為查詢的標籤新增resultMap屬性指向這個剛構建的型別處理器(增刪改不會返回ResultSet所以不用)
  • 為SQL語句中使用到引數的位置新增上javaTypejdbcTypetypeHandler屬性於#{}內(引數使用型別處理器)。

前兩件事共同配置了ResultSet使用型別處理器,後面一件事是在配置PreparedStatement的引數傳入時使用型別處理器。

<?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="mapper.StudentMapper">
    <!--設定關於ResultSet使用型別處理器-->
    <!--id用來給後面使用,type就使用PO類或其別名表示結果集對應的是這個類的物件-->
    <resultMap id="stuMap" type="stu">
        <!--id子元素用來標識主鍵,column標識在資料庫中的列或屬性,property標識PO類的屬性,後兩個屬性不用說了-->
        <id column="id" property="id" javaType="int" jdbcType="INTEGER"/>
        <!--result子元素標識非主屬性,元素內的四個屬性都和前面id的一樣-->
        <!--可以直接使用typeHandler屬性,就不必指出javaType和jdbcType了,或只給出javaType和jdbcType也夠用-->
        <result column="name" property="stuName" typeHandler="test.MyStringTypeHandler"/>
    </resultMap>

    <!--查詢語句需要給出resultMap屬性,指向前面配置的resultMap的id,表示對該語句的結果集的處理使用前面resultMap的配置-->
    <select id="getStudent" parameterType="int" resultType="stu" resultMap="stuMap">
        SELECT
            id,
            name
        FROM student
        WHERE id = #{id}
    </select>

    <insert id="addStudent" parameterType="stu">
        INSERT INTO student (name) VALUES (#{stuName})
    </insert>

    <delete id="deleteStudent" parameterType="int">
        DELETE FROM student
        WHERE id = #{id}
    </delete>

    <!--這裡面的name設定了引數使用這個自定的型別處理器-->
    <update id="updateStudent" parameterType="stu">
        UPDATE student
        SET name = #{stuName,javaType=string,jdbcType=VARCHAR,typeHandler=test.MyStringTypeHandler}
        WHERE id = #{id}
    </update>
</mapper>

執行結果

這裡寫圖片描述
可以看到輸出了自定義的型別處理器中的這兩句話,說明成功配置了自定義的型別處理器。

未解決的疑問

在前面對映檔案中,可以看到除了新的和型別處理器相關的配置之外,還有一個和之前不一樣的地方:

SELECT
    id,
    name
FROM student
WHERE id = #{id}

這裡之前是:

SELECT
    id,
    name as stuName
FROM student
WHERE id = #{id}

如果採用這種帶有AS的寫法,讀出來的name始終是null,而且不會輸出第一句呼叫(即沒有呼叫自定義的型別處理器中的getResult()方法)。

我猜測是因為新增ResultMap已經給出了這種對映關係,即從columnproperty的對映關係。