天天看點

MyBatis配置typeHandler類型轉換器 (自定義類型轉換器)初始typeHandler系統定義的typeHandler自定義typeHandler

MyBatis配置のtypeHandler類型轉換器 - 簡書

https://www.jianshu.com/p/8e0a2d06892c

初始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的自定義之後,我們還需要配置掃描 (古月: 新版mybatis好像沒有這個将配置了, 建議使用下面的包掃描配置)

<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"/> <!-- 在要使用的資料庫字段上使用自定義的轉換器, 不加時還是會使用mybatis預設的轉換器 -->
    </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的方法

這裡展示兩種使用自定義typeHandler的方式:

  1. 指定了與自定義typeHandler一緻的jdbcType和javaType
  2. 直接使用typeHandler指定具體的實作類

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

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

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

@MappedJdbcTypes(JdbcType.VARCHAR)   // 表示把資料庫中的varchar類型轉成java的String類型時使用該轉換器
@MappedTypes(String.class)
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);
    }
}
           

繼續閱讀