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的方式:
- 指定了與自定義typeHandler一緻的jdbcType和javaType
- 直接使用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);
}
}