1. 程式人生 > >MyBatis之TypeHandler解析

MyBatis之TypeHandler解析

MyBatis作為一個ORM框架,在實現物件到關係資料庫對映的過程中,一個無法避免的問題就是Java型別和JDBC型別之間的相互轉換,而TypeHandler的作用就在於此,其作用是實現Java型別向JDBC型別之間的轉換。

從上面的描述上來看,TypeHandler的作用物件是一個物件屬性,從Java型別向JDBC型別之間的轉換這樣的說法或許過於抽象,舉個例子來說,比如我們有個物件的屬性是String陣列,我想在插入資料庫的時候將這個屬性在插入資料庫的時候是以一個varchar的屬性記錄,陣列中每個元素以逗號相隔,這是從Java型別轉換到JDBC;而從資料庫獲取這個物件的屬性的時候又能夠分割開逗號返回物件String陣列,這是從JDBC轉換到Java型別。
從實現層次上來說TypeHandler的功能,就是在我們使用MyBatis插入一個物件資料的時候,物件的每條屬性是在經過如何處理後放入JDBC的SQL語句中,在取出一條資料庫記錄的時候,每一列的資料又是經過如何的處理放入物件的屬性中的,這兩個方面我們完全可以從MyBatis提供TypeHandler

類中得以體現。

org.apache.ibatis.type.TypeHandler是所有TypeHandler的基類,裡面有四個方法

    void setParameter(PreparedStatement var1, int var2, T var3, JdbcType var4) throws SQLException;

    T getResult(ResultSet var1, String var2) throws SQLException;

    T getResult(ResultSet var1, int var2) throws SQLException;

    T getResult(CallableStatement var1, int
var2) throws SQLException;

其中setParameter是將一個物件的屬性經過轉換後插入到PreparedStatement,後面三個方法是將從ResultSet中取出的值處理後返回給物件的屬性(分別是通過列名,列索引來獲取值,最後一個適用於儲存過程)。而org.apache.ibatis.type.BaseTypeHandler則實現了TypeHandler,做了一些null值上的處理,一般情況下我們自定義的型別處理器直接繼承自BaseTypeHandler,並覆蓋一下四個方法

    public abstract void setNonNullParameter
(PreparedStatement var1, int var2, T var3, JdbcType var4) throws SQLException; public abstract T getNullableResult(ResultSet var1, String var2) throws SQLException; public abstract T getNullableResult(ResultSet var1, int var2) throws SQLException; public abstract T getNullableResult(CallableStatement var1, int var2) throws SQLException;

對於一下簡單的TypeHandler MyBatis已經預設提供了,對於一些比較難處理的,比如陣列,容器,自定義簡單物件需要我們自己自定義TypeHandler進行型別處理。

自定義StringArrayTypeHandler


上圖顯示了在構建MyBatis程式的一般過程,其中橙色的線是我們在使用TypeHandler的過程中的一些重要步驟,我在圖中做了數字標註。

編寫StringArrayTypeHandler

首先第一步是編寫屬性的型別轉換器,我們這裡用編寫的String陣列的型別轉換器作為例子。假設JavaBean是Student有如下屬性

public class Student
{
    private int id;
    private String name;
    private int age;
    private SexEnum sex;
    private String[] interests;
    //getter and setter
}

其中interests屬性是一個數組,我們第一步編寫StringArrayTypeHandler,它繼承自BaseTypeHandler

    @Override
    /**
     * 將java型別轉換為JDBC型別
     */
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, String[] strings, JdbcType jdbcType) throws SQLException {
        StringBuilder builder=new StringBuilder();
        for(String item: strings){
            builder.append(item+",");
        }
        builder.deleteCharAt(builder.length()-1);
        System.out.println(builder.toString());
        preparedStatement.setString(i,builder.toString());

    }

    @Override
    /**
     *通過列名獲取Java型別的值
     */
    public String[] getNullableResult(ResultSet resultSet, String s) throws SQLException {
        String result=resultSet.getString(s);
        if(result!=null){
            return result.split(",");
        }else{
            return null;
        }
    }

編寫Mapper介面

第二步是先寫業務需求的介面,我們這裡定義了插入和查詢兩個介面

public interface StudentMapper {
    int insertStudent(Student student);
    Student findStudentById(int id);
}

配置Mapper檔案

定義完業務介面就可以配置Mapper檔案,這個是我們使用TypeHandler的地方,有兩處使用到了我們定義的StringArrayTypeHandler,第一處是在我們插入值的引數處,使用了typeHandler引數。

 <insert id="insertStudent" parameterType="TypeHandler.Student" useGeneratedKeys="true" keyProperty="id">
        insert into student(stu_name,stu_age,stu_interests,stu_sex) values(#{name},#{age},
        #{interests,typeHandler=TypeHandler.StringArrayTypeHandler},
        #{sex,typeHandler=org.apache.ibatis.type.EnumOrdinalTypeHandler})
    </insert>

另一處是在select的resultMap中

<resultMap id="stuResult" type="TypeHandler.Student">
<id column="stu_id" property="id"/>
    <result column="stu_name" property="name"/>
    <result column="stu_age" property="age"/>
    <result column="stu_sex" property="sex" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>
    <result column="stu_interests" property="interests" typeHandler="TypeHandler.StringArrayTypeHandler"/>
</resultMap>

這兩個方法Mapper方法的呼叫會引起我們自定義的StringArrayTypeHandler的setNonNullParameter和getNullableResult方法的呼叫。
Mapper配置檔案和Mapper介面直接的管理是Mapper配置檔案來維護的,Mapper配置檔案namespace指定了Mapper介面的全稱限名,並且對應的select,insert子標籤的id對應於Mapper介面的方法名。

基礎配置檔案載入Mapper檔案

第四步就是在MyBatis的基礎配置檔案中載入Mapper配置檔案

<mapper resource="TypeHandler/StudentMapper.xml"/>

如果是非顯示使用TypeHandler還要在基礎配置檔案中註冊TypeHandler,顯示使用則不用。顯示使用是指直接指定TypeHandler的全稱限名,非顯示使用只指定type和jdbcType,讓框架去尋找合適的TypeHandler

使用Mapper介面進行測試

這一階段需要經過載入基礎配置檔案,獲取SqlSessionFactory,開啟SqlSession,getMapper執行sql語句的過程,至此整個過程結束,程式順利執行

遇到的問題

  • 沒有宣告別名的類一定要寫成全稱限名
  • 主鍵回填不僅要設定useGeneratedKeys=true,還要設定keyProperty

EnumOrdinalTypeHandler和EnumTypeHandler

這兩個都是MyBatis內建的處理Enum型別屬性的型別轉換器,其區別在於在存入資料庫的時候EnumOrdinalTypeHandler是呼叫ordinal()方法獲取索引儲存,而EnumTypeHandler是呼叫name()方法獲取名稱進行儲存,取回的時候也是同樣策略的逆過程,比如我們上面使用了EnumOrdinalTypeHandler,其對應的列舉型別如下,而資料庫儲存的stu_sex為對應的索引0或1

public enum SexEnum {
    FEMALE("女"),MALE("男");
    private String sexName;
    SexEnum(String sexName){
        this.sexName=sexName;
    }

    public String getSexName() {
        return sexName;
    }

    public void setSexName(String sexName) {
        this.sexName = sexName;
    }
}

關於原始碼

你可以在這裡找到本文的全部原始碼,你同樣可以在我的部落格看到這篇文章。