1. 程式人生 > >MyBatis配置のtypeHandler型別轉換器

MyBatis配置のtypeHandler型別轉換器

初始typeHandler

JDBC中,需要在PreparedStatement物件中設定那些已經預編譯過的SQL語句 引數。 執行SQL後,會通過ResultSet物件獲取得到資料庫的資料,而這些MyBatis是根據資料的型別通過typeHandler來實現的。

在typeHandler中,分為jdbcType和javaType,其中jdbcType用於定義資料庫型別,javaType用於定義Java型別,那麼typeHandler的作用就是承擔jdbcType和javaType之間的相互轉換。

在MyBatis中存在系統定義的typeHandler和自定義的typeHandler。MyBatis會根據javaType和資料庫的jdbcType決定採用哪個typeHandler處於這些轉換規則。

系統提供的typeHandler能覆蓋大部分場景的要求,但是有些情況下是不夠的,比如我們有特殊的轉換規則,列舉類就是這樣。

系統定義的typeHandler

MyBatis內部定義了許多有用的typeHandler,這裡列舉一些

型別處理器 Java型別 JDBC型別
IntegerTypeHandler java.lang.Integer,int 資料庫相容的NUMERIC或SHORT INTEGER
StringTypeHandler java.lang.String CHAR、VARCHAR

在MyBatis中typeHandler都要實現介面org.apache.ibatis.type.TypeHandler。

// T是泛型,專指javaType
public interface TypeHandler<T> {
    // 通過PreparedStatement 物件進行設定SQL引數 
    // i是引數在SQL的下標
    // parameter是引數
    // jdbcType是資料庫型別
    void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

    // 從JDBC結果集中獲取資料進行轉換,要麼使用列名(columnName)要麼使用下標(columnIndex)獲取資料庫的資料
T getResult(ResultSet var1, String var2) throws SQLException; T getResult(ResultSet var1, int var2) throws SQLException; // 儲存過程專用的 T getResult(CallableStatement var1, int var2) throws SQLException; }

我們這裡以StringTypeHandler為例,研究一下MyBatis系統的typeHandler是如何實現的。
檢視StringTypeHandler的原始碼,可以發現它繼承了BaseTypeHandler

// BaseTypeHandler本身是一個抽象類,需要子類去實現其定義的4個抽象方法,而它本身實現了typeHandler介面的4個方法
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {

    public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
        // 當parameter和jdbcType同時為空,MyBatis將丟擲異常
        if (parameter == null) {
            if (jdbcType == null) {
                throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
            }

            try {
                ps.setNull(i, jdbcType.TYPE_CODE);
            } catch (SQLException var7) {
                throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . " + "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. " + "Cause: " + var7, var7);
            }
        } else {            
            try {
                // 設定引數
                this.setNonNullParameter(ps, i, parameter, jdbcType);
            } catch (Exception var6) {
                throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . " + "Try setting a different JdbcType for this parameter or a different configuration property. " + "Cause: " + var6, var6);
            }
        }

    }

    public T getResult(ResultSet rs, String columnName) throws SQLException {
        Object result;
        try {
            // 返回非空結果集
            result = this.getNullableResult(rs, columnName);
        } catch (Exception var5) {
            throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set.  Cause: " + var5, var5);
        }

        return rs.wasNull() ? null : result;
    }

    public T getResult(ResultSet rs, int columnIndex) throws SQLException {
        Object result;
        try {
            result = this.getNullableResult(rs, columnIndex);
        } catch (Exception var5) {
            throw new ResultMapException("Error attempting to get column #" + columnIndex + " from result set.  Cause: " + var5, var5);
        }

        return rs.wasNull() ? null : result;
    }

    public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
        Object result;
        try {
            result = this.getNullableResult(cs, columnIndex);
        } catch (Exception var5) {
            throw new ResultMapException("Error attempting to get column #" + columnIndex + " from callable statement.  Cause: " + var5, var5);
        }

        return cs.wasNull() ? null : result;
    }

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

接下來再看

public class StringTypeHandler extends BaseTypeHandler<String> {

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

在這裡MyBatis就把javaType和jdbcType進行了相互轉換。

那麼它們是如何註冊的?
是通過org.apache.ibatis.type.TypeHandlerRegistry類物件的register方法進行註冊

public TypeHandlerRegistry() {
    this.register((Class)Boolean.class, (TypeHandler)(new BooleanTypeHandler()));
    this.register((Class)Boolean.TYPE, (TypeHandler)(new BooleanTypeHandler()));
    ...
}

這樣就實現了自帶的typeHanlder註冊。
自定義的typeHandler一般不會使用程式碼註冊,而是通過配置或掃描

自定義typeHandler

這裡我們仿造一個StringTypeHandler

/**
 * 自定義的typeHandler 實現StringTypeHandler的功能
 * @author Why
 *
 */
public class MyTypeHandler implements TypeHandler<String>{

    Logger logger = Logger.getLogger(MyTypeHandler.class);

    @Override
    public String getResult(ResultSet rs, String columnName) throws SQLException {
        String result = rs.getString(columnName);
        logger.info("讀取string引數1【" + result +"】");
        return result;
    }

    @Override
    public String getResult(ResultSet rs, int columnIndex) throws SQLException {
        String result = rs.getString(columnIndex);
        logger.info("讀取string引數2【" + result +"】");
        return result;
    }

    @Override
    public String getResult(CallableStatement cs, int columnIndex) throws SQLException {
        String result = cs.getString(columnIndex);
        logger.info("讀取string引數3【" + result +"】");
        return result;
    }

    @Override
    public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
        logger.info("設定string引數【" + parameter + "】");
        ps.setString(i, parameter);
    }
}

在完成了typeHandler的自定義之後,我們還需要配置掃描

<typeHandlers>
    <typeHandler handler="com.whyalwaysmea.typeHandler.MyTypeHandler" jdbcType="VARCHAR" javaType="string"/>
</typeHandlers>

最後就是自定義typeHandler的使用了

<mapper namespace="com.whyalwaysmea.mapper.RoleMapperWithTypeHandler">  

    <resultMap type="role" id="roleMapper">
        <result property="id" column="id"/>
        <result property="roleName" column="role_name" jdbcType="VARCHAR" javaType="string"/>
        <result property="note" column="note" typeHandler="com.whyalwaysmea.typeHandler.MyTypeHandler"/>
    </resultMap>

    <select id="findRoles1" parameterType="string" resultType="role">
        select id, role_name, note from t_role where role_name like concat('%', #{roleName, jdbcType=VARCHAR, javaType=string}, '%')
    </select>

    <select id="findRoles2" parameterType="string" resultType="role">
        select id, role_name, note from t_role where role_name like concat('%', #{roleName, typeHandler=com.whyalwaysmea.typeHandler.MyTypeHandler}, '%')
    </select>
</mapper>

這裡展示兩種使用自定義typeHandler的方式:
1. 指定了與自定義typeHandler一致的jdbcType和javaType
2. 直接使用typeHandler指定具體的實現類

有時候配置的typeHandler太多,也可以使用包掃描的方式

<typeHandlers>      
    <package name="com.whyalwaysmea.typeHandler"/>
</typeHandlers>

這樣還需要處理一下自定義的typeHandler類,指定jdbcType和javaType

@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes(String.class)
public class MyTypeHandler implements TypeHandler<String>{
    ...
}