面試官問:Mybatis中的TypeHandler你用過嗎?


持續原創輸出,點擊上方藍字關注我吧

目錄

  • 前言
  • 環境配置
  • 什么是TypeHandler?
    • 如何自定義?
    • 如何將其添加到Mybatis中?
    • XML文件中如何指定TypeHandler?
  • 源碼中如何執行TypeHandler?
    • 入參如何轉換?
    • 結果如何轉換?
    • 總結
  • 總結

前言

  • 相信大家用Mybatis這個框架至少一年以上了吧,有沒有思考過這樣一個問題:數據庫有自己的數據類型,Java有自己的數據類型,那么Mybatis是如何把數據庫中的類型和Java的數據類型對應的呢?

  • 本篇文章就來講講Mybatis中的黑匣子TypeHandler(類型處理器),說它是黑匣子一點都不為過,總是在默默的奉獻着,但是不為人知。

環境配置

  • 本篇文章講的一切內容都是基於 Mybatis3.5SpringBoot-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文件中使用呢?
  • 使用其實很簡單,分為兩種,一種是 更新,一種 查詢,下面將會一一介紹。
  • 更新:刪除自不必說了,這里講的是 updateinsert兩種,只需要在 #{}中指定的屬性 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);  }  } 

總結

總結

  • 本文詳細的介紹了TypeHandler在Mybatis中的應用、自定義使用以及從源碼角度分析了類型處理器的執行流程,如果覺得作者寫的不錯,有所收獲的話,不妨點點關注,分享一波。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM