Mybatis之typeHandler類型轉(zhuǎn)換器

在JDBC中,需要通過PreparedStatement對(duì)象中設(shè)置那些已經(jīng)預(yù)編譯過的SQL語句的參數(shù),執(zhí)行SQL后,會(huì)通過ResultSet對(duì)象獲取得到數(shù)據(jù)庫的數(shù)據(jù)不跟,而這些MyBatis是根據(jù)數(shù)據(jù)的類型通過typeHandler來實(shí)現(xiàn)的,在typeHandler中米碰,分為jdbcType和javaType躬拢,其中jdbcType用于定義數(shù)據(jù)庫類型,而javaType用于定義java類型见间,那么typeHandler的作用就是承擔(dān)jdbcType和javaType之間的相互轉(zhuǎn)換聊闯,
在很多中情況下,我們并不需要去配置typeHandler米诉,jdbctype,javatype菱蔬。因?yàn)镸yBatis會(huì)探測(cè)應(yīng)該使用什么類型的typeHandler進(jìn)行配置,但是有些場(chǎng)景就無法探測(cè)到史侣,對(duì)于那些需要使用自定義的枚舉的場(chǎng)景拴泌,或者數(shù)據(jù)庫使用特殊數(shù)據(jù)類型的場(chǎng)景,可以使用自定義的typeHandler去處理類型之間的轉(zhuǎn)換問題惊橱。

源碼分析

在MyBatis中typeHandler都要實(shí)現(xiàn)接口org.apache.ibatis.type.TypeHandler,首先讓我們先看看這個(gè)接口的定義

/**
 * @author Clinton Begin
 */
public interface TypeHandler<T> {
 
  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
 
  T getResult(ResultSet rs, String columnName) throws SQLException;
 
  T getResult(ResultSet rs, int columnIndex) throws SQLException;
 
  T getResult(CallableStatement cs, int columnIndex) throws SQLException;
 
}

這里我們稍微說明一下他們的定義

  • 其中T是泛型蚪腐,專指javaType,比如我們需要String的時(shí)候税朴,那么實(shí)現(xiàn)類可以寫為implements TypeHandler<String>
  • setParameter方法回季,是使用typeHandler通過PreparedStatement對(duì)象設(shè)置SQL參數(shù)的時(shí)候使用的具體方法家制,其中i是參數(shù)在SQL的下標(biāo),parameter是參數(shù)泡一,jdbcType是數(shù)據(jù)庫類型
  • 其中有三個(gè)getResult的方法颤殴,它的作用是從JDBC結(jié)果集中獲取數(shù)據(jù)進(jìn)行轉(zhuǎn)換,要么使用列名鼻忠,(columnname)要么使用下標(biāo)(columnIndex)獲取數(shù)據(jù)庫的數(shù)據(jù)涵但,其中最后一個(gè)getResult方法是存儲(chǔ)過程使用的。
    TypeHandler源碼實(shí)現(xiàn)
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
 
  protected Configuration configuration;
 
  public void setConfiguration(Configuration c) {
    this.configuration = c;
  }
 
  @Override
  public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
    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 e) {
        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: " + e, e);
      }
    } else {
      try {
        setNonNullParameter(ps, i, parameter, jdbcType);
      } catch (Exception e) {
        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: " + e, e);
      }
    }
  }
 
  @Override
  public T getResult(ResultSet rs, String columnName) throws SQLException {
    T result;
    try {
      result = getNullableResult(rs, columnName);
    } catch (Exception e) {
      throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set.  Cause: " + e, e);
    }
    if (rs.wasNull()) {
      return null;
    } else {
      return result;
    }
  }
 
  @Override
  public T getResult(ResultSet rs, int columnIndex) throws SQLException {
    T result;
    try {
      result = getNullableResult(rs, columnIndex);
    } catch (Exception e) {
      throw new ResultMapException("Error attempting to get column #" + columnIndex+ " from result set.  Cause: " + e, e);
    }
    if (rs.wasNull()) {
      return null;
    } else {
      return result;
    }
  }
 
  @Override
  public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
    T result;
    try {
      result = getNullableResult(cs, columnIndex);
    } catch (Exception e) {
      throw new ResultMapException("Error attempting to get column #" + columnIndex+ " from callable statement.  Cause: " + e, e);
    }
    if (cs.wasNull()) {
      return null;
    } else {
      return result;
    }
  }
 
  public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
 
  public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;
 
  public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;
 
  public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;
 
}

簡單分析一下BaseTypeHandler的源碼

  • BaseTypeHandler是一個(gè)抽象類帖蔓,它實(shí)現(xiàn)了TypeHandler的所有接口矮瘟,需要子類去實(shí)現(xiàn)它自定義的4個(gè)抽象方法
  • setParameter方法中,當(dāng)parameter和jdbcType都為空時(shí)將拋出異常塑娇,如果能明確jdbcType澈侠,將會(huì)進(jìn)行空設(shè)置(PreparedStatement的setNull方法);如果paramter不為空钝吮,將使用setNonNullParameter去設(shè)置參數(shù)(該方法需要子類去實(shí)現(xiàn))
  • getResult方法埋涧,非空結(jié)果集是通過getNullableResult方法去獲取的板辽,該方法需要子類去實(shí)現(xiàn)奇瘦,同樣是針對(duì)下標(biāo)、列名以及存儲(chǔ)過程三種的實(shí)現(xiàn)
  • getNullableParameter方法用于存儲(chǔ)過程

Mybatis使用最多的是typeHandler之一是——StringTypeHandler.它用于字符串轉(zhuǎn)換劲弦、

/**
 * @author Clinton Begin
 */
public class StringTypeHandler extends BaseTypeHandler<String> {
 
  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
      throws SQLException {
    ps.setString(i, parameter);
  }
 
  @Override
  public String getNullableResult(ResultSet rs, String columnName)
      throws SQLException {
    return rs.getString(columnName);
  }
 
  @Override
  public String getNullableResult(ResultSet rs, int columnIndex)
      throws SQLException {
    return rs.getString(columnIndex);
  }
 
  @Override
  public String getNullableResult(CallableStatement cs, int columnIndex)
      throws SQLException {
    return cs.getString(columnIndex);
  }
}

顯然它實(shí)現(xiàn)了BaseTypeHandler的4個(gè)抽象方法耳标,代碼也非常簡單。
在這里邑跪,MyBatis把javaType和jdbcType相互轉(zhuǎn)換次坡,那么他們是如何進(jìn)行注冊(cè)的呢?在MyBatis中采用org.apache.ibatis.type.TypeHandlerRegistry類對(duì)象的register方法進(jìn)行注冊(cè)

public TypeHandlerRegistry() {
    register(Boolean.class, new BooleanTypeHandler());
    register(boolean.class, new BooleanTypeHandler());
    register(JdbcType.BOOLEAN, new BooleanTypeHandler());
    register(JdbcType.BIT, new BooleanTypeHandler());
}

這樣就實(shí)現(xiàn)用代碼的形式注冊(cè)typeHandler画畅,注意砸琅,自定義的typeHandler一般不會(huì)使用代碼注冊(cè),而是通過配置或者掃描轴踱。0

自定義typeHandler

從系統(tǒng)定義的typeHandler可以知道症脂,要實(shí)現(xiàn)typeHandler就需要去實(shí)現(xiàn)接口typeHandler,或者繼承BaseTypeHandler(實(shí)際上BassseTypeHandler實(shí)現(xiàn)了typehandler的接口)

package com.learn.ssm.chapter4.typehandler;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
import org.apache.log4j.Logger;

public class MyTypeHandler implements TypeHandler<String> {

    Logger logger=Logger.getLogger(MyTypeHandler.class);
//  這個(gè)就是一個(gè)日志的對(duì)象淫僻,記錄和這個(gè)類對(duì)象進(jìn)行的一切操作诱篷,記錄用戶的操作
    
    @Override
    public String getResult(ResultSet rs, String columnName) throws SQLException {
        // TODO Auto-generated method stub
        String result=rs.getString(columnName);
        logger.info("讀取string參數(shù)1【"+result+"】");
        return result;
    }

    @Override
    public String getResult(ResultSet rs, int columnIndex) throws SQLException {
        // TODO Auto-generated method stub
        String result=rs.getString(columnIndex);
        logger.info("讀取string參數(shù)2【"+result+"】");
        return result;
    }

    @Override
    public String getResult(CallableStatement cs, int columnIndex)
            throws SQLException {
        // TODO Auto-generated method stub
        String result=cs.getString(columnIndex);
        logger.info("讀取string參數(shù)3【"+result+"】");
        return result;
    }

    @Override
    public void setParameter(PreparedStatement ps, int i, String parameter,
            JdbcType jdbcType) throws SQLException {
        // TODO Auto-generated method stub
        logger.info("設(shè)置string參數(shù)["+parameter+"]");
        ps.setString(i, parameter);
    }

}

定義的typeHandler泛型為String,顯然我們要把數(shù)據(jù)庫的數(shù)據(jù)類型轉(zhuǎn)換為String型雳灵,然后實(shí)現(xiàn)設(shè)置參數(shù)和獲取結(jié)果集方法
配置typeHandler,配置文件在mybatis-config.xml

<!-- 配置typehandler -->
<typeHandlers>
         <typeHandler jdbcType="VARCHAR" javaType="string" handler="com.learn.ssm.chapter4.typehandler.MyTypeHandler" 
            /> 
<!--        <package name="com.learn.ssm.chapter4.typehandler" /> -->

    </typeHandlers>

配置完成后系統(tǒng)才會(huì)讀取它棕所,這樣注冊(cè)后,當(dāng)jdbcType和javaType能與MyTypeHandler對(duì)應(yīng)的時(shí)候悯辙,它就會(huì)啟動(dòng)MyTypeHandler.有時(shí)候還可以顯示啟用typeHandler琳省,一般而言啟用這個(gè)typeHandler有 兩種方式迎吵。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.learn.ssm.chapter3.mapper.RoleMapper">
<!-- namespace所對(duì)應(yīng)的是一個(gè)接口的全限定名,于是MyBatis上下文就可以通過它找到對(duì)應(yīng)的接口  -->
    <resultMap id="roleMapper" type="role">
        <result property="id" column="id" />
        <result property="roleName" column="role_name" jdbcType="VARCHAR"
            javaType="string" />
        <result property="note" column="note"
            typeHandler="com.learn.ssm.chapter4.typehandler.MyTypeHandler" />
    </resultMap>

    <select id="getRole" parameterType="long" resultMap="roleMapper">
        select id, role_name, note from t_role where id = #{id}
    </select>

    <select id="findRoles" parameterType="string" resultMap="roleMapper">
        select id, role_name, note from t_role
        where role_name like concat('%', #{roleName, jdbcType=VARCHAR,
        javaType=string}, '%')
    </select>
<!--第一種:用配置中一樣的jdbcType和javaType-->
    <select id="findRoles2" parameterType="string" resultMap="roleMapper">
        select id, role_name, note from t_role
        where note like concat('%', #{note,
        typeHandler=com.learn.ssm.chapter4.typehandler.MyTypeHandler}, '%')
    </select>
    <!--第二種:直接用具體的實(shí)現(xiàn)類-->
</mapper>

注意要么指定了與自定義typeHandler一致的jdbcType和javaType岛啸,要么直接使用typeHandler指定具體的實(shí)現(xiàn)類钓觉,在一些因?yàn)閿?shù)據(jù)庫返回為空導(dǎo)致無法斷定采用哪個(gè)typeHandler來處理,而又沒有注冊(cè)對(duì)應(yīng)的javaType的typeHandler時(shí)坚踩,MyBatis無法知道使用哪個(gè)typeHandler

  • 補(bǔ)充 resultMap是Mybatis最強(qiáng)大的元素荡灾,它可以將查詢到的復(fù)雜數(shù)據(jù)(比如查詢到幾個(gè)表中數(shù)據(jù))映射到一個(gè)結(jié)果集當(dāng)中
<!--column不做限制,可以為任意表的字段瞬铸,而property須為type 定義的pojo屬性-->
<resultMap id="唯一的標(biāo)識(shí)" type="映射的pojo對(duì)象">
  <id column="表的主鍵字段批幌,或者可以為查詢語句中的別名字段" jdbcType="字段類型" property="映射pojo對(duì)象的主鍵屬性" />
  <result column="表的一個(gè)字段(可以為任意表的一個(gè)字段)" jdbcType="字段類型" property="映射到pojo對(duì)象的一個(gè)屬性(須為type定義的pojo對(duì)象中的一個(gè)屬性)"/>
  <association property="pojo的一個(gè)對(duì)象屬性" javaType="pojo關(guān)聯(lián)的pojo對(duì)象">
    <id column="關(guān)聯(lián)pojo對(duì)象對(duì)應(yīng)表的主鍵字段" jdbcType="字段類型" property="關(guān)聯(lián)pojo對(duì)象的主席屬性"/>
    <result  column="任意表的字段" jdbcType="字段類型" property="關(guān)聯(lián)pojo對(duì)象的屬性"/>
  </association>
  <!-- 集合中的property須為oftype定義的pojo對(duì)象的屬性-->
  <collection property="pojo的集合屬性" ofType="集合中的pojo對(duì)象">
    <id column="集合中pojo對(duì)象對(duì)應(yīng)的表的主鍵字段" jdbcType="字段類型" property="集合中pojo對(duì)象的主鍵屬性" />
    <result column="可以為任意表的字段" jdbcType="字段類型" property="集合中的pojo對(duì)象的屬性" />  
  </collection>
</resultMap>

運(yùn)行

package com.learn.ssm.chapter4.main;

import org.apache.ibatis.session.SqlSession;
import org.apache.log4j.Logger;

import com.learn.ssm.chapter3.mapper.RoleMapper;

import com.learn.ssm.chapter3.pojo.Role;

import com.learn.ssm.chapter3.utils.SqlSessionFactoryUtils;

public class chapter4Main {

    public static void main(String[] args) {
        testRoleMapper();
//      testTypeHandler();
    }

    private static void testRoleMapper() {
        Logger log = Logger.getLogger(chapter4Main.class);
        SqlSession sqlSession = null;
        try {
            sqlSession = SqlSessionFactoryUtils.openSqlSession();
            RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
            Role role = roleMapper.getRole(1L);
    
            log.info(role.getRoleName());
    
        } finally {
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市嗓节,隨后出現(xiàn)的幾起案子荧缘,更是在濱河造成了極大的恐慌,老刑警劉巖拦宣,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件截粗,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡鸵隧,警方通過查閱死者的電腦和手機(jī)绸罗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來豆瘫,“玉大人珊蟀,你說我怎么就攤上這事⊥馇” “怎么了育灸?”我有些...
    開封第一講書人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長昵宇。 經(jīng)常有香客問我磅崭,道長,這世上最難降的妖魔是什么瓦哎? 我笑而不...
    開封第一講書人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任砸喻,我火速辦了婚禮,結(jié)果婚禮上杭煎,老公的妹妹穿的比我還像新娘恩够。我一直安慰自己,他們只是感情好羡铲,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開白布蜂桶。 她就那樣靜靜地躺著,像睡著了一般也切。 火紅的嫁衣襯著肌膚如雪扑媚。 梳的紋絲不亂的頭發(fā)上腰湾,一...
    開封第一講書人閱讀 52,262評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音疆股,去河邊找鬼费坊。 笑死,一個(gè)胖子當(dāng)著我的面吹牛旬痹,可吹牛的內(nèi)容都是我干的附井。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼两残,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼永毅!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起人弓,我...
    開封第一講書人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤沼死,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后崔赌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體意蛀,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年健芭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了县钥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡吟榴,死狀恐怖魁蒜,靈堂內(nèi)的尸體忽然破棺而出囊扳,到底是詐尸還是另有隱情吩翻,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布锥咸,位于F島的核電站狭瞎,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏搏予。R本人自食惡果不足惜熊锭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望雪侥。 院中可真熱鬧碗殷,春花似錦、人聲如沸速缨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽旬牲。三九已至仿粹,卻和暖如春搁吓,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背吭历。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來泰國打工堕仔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人晌区。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓摩骨,卻偏偏與公主長得像,于是被迫代替她去往敵國和親朗若。 傳聞我的和親對(duì)象是個(gè)殘疾皇子仿吞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容