前言 相信大家用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類型@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {ps.setInt(i, StringUtils.equals(parameter.toString(),"男")?1:2);}//以下三個參數都是將查詢的結果轉換@Overridepublic Object getNullableResult(ResultSet rs, String columnName) throws SQLException {return rs.getInt(columnName)==1?"男":"女";}@Overridepublic Object getNullableResult(ResultSet rs, int columnIndex) throws SQLException {return rs.getInt(columnIndex)==1?"男":"女";}@Overridepublic 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注冊到mybatisGenderTypeHandler 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=1and user_id in<foreach collection="userIds" item="item" open="(" separator="," close=")" >#{item}</foreach></select>
源碼中如何執行TypeHandler? 既然會使用TypeHandler了,那么肯定要知道其中的執行原理了,在Mybatis中類型處理器是如何在JDBC類型和Java類型進行轉換的,下面的將從源碼角度詳細介紹。 入參如何轉換? 這個肯定是發生在設置參數的過程中,詳細的代碼在PreparedStatementHandler中的parameterize()方法中,這個方法就是設置參數的方法。源碼如下: @Overridepublic void parameterize(Statement statement) throws SQLException {//實際調用的是DefaultParameterHandlerparameterHandler.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 paramsvalue = 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(); //JdbcTypeJdbcType 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;//執行SQLps.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的简单查询用语句吗_面试官:Mybatis中的TypeHandler你用过吗? 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。