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>{
...
}