面試官問:Mybatis中的TypeHandler你用過嗎?
持續原創輸出,點擊上方藍字關注我吧

目錄
-
前言 -
環境配置 -
什麼是TypeHandler? -
如何自定義? -
如何將其添加到Mybatis中? -
XML文件中如何指定TypeHandler?
-
-
源碼中如何執行TypeHandler? -
入參如何轉換? -
結果如何轉換? -
總結
-
-
總結
前言
-
相信大家用Mybatis這個框架至少一年以上了吧,有沒有思考過這樣一個問題:資料庫有自己的數據類型,Java有自己的數據類型,那麼Mybatis是如何把資料庫中的類型和Java的數據類型對應的呢?
-
本篇文章就來講講Mybatis中的
黑匣子TypeHandler(類型處理器)
,說它是黑匣子一點都不為過,總是在默默的奉獻著,但是不為人知。
環境配置
-
本篇文章講的一切內容都是基於 Mybatis3.5
和SpringBoot-2.3.3.RELEASE
。
什麼是TypeHandler?
-
顧名思義,類型處理器,將入參和結果轉換為所需要的類型,Mybatis中對於內置了許多類型處理器,實際開發中已經足夠使用了,如下圖:
-
類型處理器這個介面其實很簡單,總共四個方法,一個方法將入參的Java類型的數據轉換為JDBC類型,三個方法將返回結果轉換為Java類型。源碼如下:
public interface TypeHandler<T> {
//設置參數,java類型轉換為jdbc類型
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
//將查詢的結果轉換為java類型
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
如何自定義並使用TypeHandler?
-
實際應用開發中的難免會有一些需求要自定義一個 TypeHandler
,比如這樣一個需求:前端傳來的年齡是男
,女
,但是資料庫定義的欄位卻是int
類型(1男2女
)。此時可以自定義一個年齡的類型處理器,進行轉換。
如何自定義?
-
自定義的方式有兩種,一種是實現 TypeHandler
這個介面,另一個就是繼承BaseTypeHandler
這個便捷的抽象類。 -
下面直接繼承 BaseTypeHandler
這個抽象類,定義一個年齡的類型處理器,如下:
@MappedJdbcTypes(JdbcType.INTEGER)
@MappedTypes(String.class)
public class GenderTypeHandler extends BaseTypeHandler {
//設置參數,這裡將Java的String類型轉換為JDBC的Integer類型
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
ps.setInt(i, StringUtils.equals(parameter.toString(),"男")?1:2);
}
//以下三個參數都是將查詢的結果轉換
@Override
public Object getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getInt(columnName)==1?"男":"女";
}
@Override
public Object getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return rs.getInt(columnIndex)==1?"男":"女";
}
@Override
public Object getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return cs.getInt(columnIndex)==1?"男":"女";
}
}
-
這裡涉及到兩個註解,如下: -
@MappedTypes
:指定與其關聯的Java
類型列表。 如果在javaType
屬性中也同時指定,則註解上的配置將被忽略。 -
@MappedJdbcTypes
:指定與其關聯的JDBC
類型列表。 如果在jdbcType
屬性中也同時指定,則註解上的配置將被忽略。
-
如何將其添加到Mybatis中?
-
Mybatis在與SpringBoot整合之後一切都變得很簡單了,其實這裡有兩種配置方式,下面將會一一介紹。 -
「第一種」:只需要在配置文件 application.properties
中添加一行配置即可,如下:
## 設置自定義的Typehandler所在的包,啟動的時候會自動掃描配置到Mybatis中
mybatis.type-handlers-package=cn.cb.demo.typehandler
-
「第二種」:其實任何框架與Springboot整合之後,只要配置文件中能夠配置的,在配置類中都可以配置(「除非有特殊訂製,否則不要輕易覆蓋自動配置」)。如下:
@Bean("sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATOIN));
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
// 自動將資料庫中的下劃線轉換為駝峰格式
configuration.setMapUnderscoreToCamelCase(true);
configuration.setDefaultFetchSize(100);
configuration.setDefaultStatementTimeout(30);
sqlSessionFactoryBean.setConfiguration(configuration);
//將typehandler註冊到mybatis
GenderTypeHandler genderTypeHandler = new GenderTypeHandler();
TypeHandler[] typeHandlers=new TypeHandler[]{genderTypeHandler};
sqlSessionFactoryBean.setTypeHandlers(typeHandlers);
return sqlSessionFactoryBean.getObject();
}
-
第二種方式的思想其實就是重寫自動配置類 MybatisAutoConfiguration
中的方法。「注意:除非自己有特殊訂製,否則不要輕易重寫自動配置類中的方法」。
XML文件中如何指定TypeHandler?
-
上面的兩個步驟分別是自定義和注入到Mybatis中,那麼如何在 XML
文件中使用呢? -
使用其實很簡單,分為兩種,一種是 更新
,一種查詢
,下面將會一一介紹。 -
「更新」:刪除自不必說了,這裡講的是 update
和insert
兩種,只需要在#{}
中指定的屬性typeHandler
為自定義的全類名
即可,程式碼如下:
<insert id="insertUser">
insert into user_info(user_id,his_id,name,gender,password,create_time)
values(#{userId,jdbcType=VARCHAR},#{hisId,jdbcType=VARCHAR},#{name,jdbcType=VARCHAR},
#{gender,jdbcType=INTEGER,typeHandler=cn.cb.demo.typehandler.GenderTypeHandler},#{password,jdbcType=VARCHAR},now())
</insert>
-
「查詢」:查詢的時候類型處理會將JDBC類型的轉化為Java類型,因此也是需要指定 typeHandler
,需要在resultMap
中指定typeHandler
這個屬性,值為全類名
,如下:
<resultMap id="userResultMap" type="cn.cb.demo.domain.UserInfo">
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="his_id" property="hisId"/>
<!-- 指定typeHandler屬性為全類名-->
<result column="gender" property="gender" typeHandler="cn.cb.demo.typehandler.GenderTypeHandler"/>
<result column="name" property="name"/>
<result column="password" property="password"/>
</resultMap>
<select id="selectList" resultMap="userResultMap">
select * from user_info where status=1
and user_id in
<foreach collection="userIds" item="item" open="(" separator="," close=")" >
#{item}
</foreach>
</select>
源碼中如何執行TypeHandler?
-
既然會使用 TypeHandler
了,那麼肯定要知道其中的執行原理了,在Mybatis中類型處理器是如何在JDBC
類型和Java
類型進行轉換的,下面的將從源碼角度詳細介紹。
入參如何轉換?
-
這個肯定是發生在設置參數的過程中,詳細的程式碼在 PreparedStatementHandler
中的parameterize()
方法中,這個方法就是設置參數的方法。源碼如下:
@Override
public void parameterize(Statement statement) throws SQLException {
//實際調用的是DefaultParameterHandler
parameterHandler.setParameters((PreparedStatement) statement);
}
-
實際執行的是 DefaultParameterHandler
中的setParameters
方法,如下:
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
//獲取參數映射
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
//遍歷參數映射,一一設置
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
//獲取類型處理器,如果不存在,使用默認的
TypeHandler typeHandler = parameterMapping.getTypeHandler();
//JdbcType
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
//調用類型處理器中的方法設置參數,將Java類型轉換為JDBC類型
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
-
從上面的源碼中可以知道這行程式碼 typeHandler.setParameter(ps, i + 1, value, jdbcType);
就是調用類型處理器中的設置參數的方法,將Java
類型轉換為JDBC
類型。
結果如何轉換?
-
這一過程肯定是發生在執行查詢語句的過程中,之前也是介紹過Mybatis的六大劍客,其中的 ResultSetHandler
這個組件就是對查詢的結果進行處理的,那麼肯定是發生在這一組件中的某個方法。 -
在 PreparedStatementHandler
執行查詢結束之後,調用的是ResultSetHandler
中的handleResultSets()
方法,對結果進行處理,如下:
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
//執行SQL
ps.execute();
//處理結果
return resultSetHandler.handleResultSets(ps);
}
-
最終的在 DefaultResultHandler
中的getPropertyMappingValue()
方法中調用了TypeHandler
中的getResult()
方法,如下:
private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
if (propertyMapping.getNestedQueryId() != null) {
return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
} else if (propertyMapping.getResultSet() != null) {
addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?
return DEFERRED;
} else {
final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
//執行typeHandler中的方法獲取結果並且轉換為對應的Java類型
return typeHandler.getResult(rs, column);
}
}
總結
-
上述只是簡要的介紹了類型處理器如何在Mybatis中執行的,可能其中有些概念東西如果不清楚的,可以看一下作者前面的文章,如下:
總結
-
本文詳細的介紹了TypeHandler在Mybatis中的應用、自定義使用以及從源碼角度分析了類型處理器的執行流程,如果覺得作者寫的不錯,有所收穫的話,不妨點點關注,分享一波。