[MyBatis源碼分析 - 類(lèi)型模塊 - 組件一] TypeHandler

一筐咧、簡(jiǎn)介

??不管是參數(shù)綁定误褪,還是結(jié)果映射亭螟,mybatis 都要完成 Java 類(lèi)型和 Jdbc 類(lèi)型數(shù)據(jù)的相互轉(zhuǎn)換,在框架中為了統(tǒng)一類(lèi)型轉(zhuǎn)換處理器的行為榆俺,定義了 TypeHandler 接口售躁,并實(shí)現(xiàn)了多種不同的類(lèi)型處理器。

二谴仙、TypeHandler 接口

??TypeHandler 為所有類(lèi)型轉(zhuǎn)換器都要實(shí)現(xiàn)的接口迂求,定義了 #setParameter() 方法來(lái)為SQL綁定參數(shù),定義了重載的 #getResult() 方法來(lái)將結(jié)果映射為Java對(duì)象晃跺,接口定義如下:

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 就表示 Java 類(lèi)型揩局,各個(gè)特定的轉(zhuǎn)換器實(shí)現(xiàn)類(lèi)根據(jù)轉(zhuǎn)換的 Java 類(lèi)型指定其具體類(lèi)型。

??#setParameter() 方法完成 javaType 到 jdbcType 的轉(zhuǎn)換掀虎。PreparedStatement ps 表示一個(gè)預(yù)編譯的SQL語(yǔ)句對(duì)象凌盯,int i 指定了綁定該語(yǔ)句中的參數(shù)的位置,T parameter 為要被轉(zhuǎn)換的 Java 對(duì)象烹玉,JdbcType jdbcType 為目的轉(zhuǎn)化類(lèi)型驰怎,當(dāng)傳入的 Java 對(duì)象是空時(shí),參數(shù)綁定需要調(diào)用 PreparedStatement .setNull() 方法二打,該方法的第二個(gè)參數(shù)即為 Jdbc 類(lèi)型县忌。

??重載的三個(gè) #getResult() 方法分別根據(jù)數(shù)據(jù)列名、列索引來(lái)將結(jié)果集中的數(shù)據(jù)轉(zhuǎn)化為 Java 類(lèi)型继效,完成 jdbcType 到 javaType 的轉(zhuǎn)換症杏。

    public Role getRole(Long id) {
        Connection connection = getConnection();
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = connection.prepareStatement("select id, role_name, note from t_role where id = ?");
            ps.setLong(1, id);
            rs = ps.executeQuery();
            while (rs.next()) {
                Long roleId = rs.getLong("id");
                String roleName = rs.getString("role_name");
                String note = rs.getString("note");
                Role role = new Role();
                role.setId(id);
                role.setRoleName(roleName);
                role.setNote(note);
                return role;
            }
        } catch (Exception e) {
            Logger.getLogger(JdbcExample.class.getName()).log(Level.SEVERE, null, e);
        } finally {
            this.close(rs, ps, connection);
        }
        return null;
    }

??再次將 反射器模塊 開(kāi)頭的這段JDBC代碼拿出來(lái)回顧一下,前面的 ps.setLong(1, id) 就是將 javaType 轉(zhuǎn)換為 jdbcType瑞信,而后面的 rs.getString("role_name") 就是將jdbcType 轉(zhuǎn)換為 javaType厉颤,可以看出不管是 PreparedStatement 還是 ResultSet 的方法名都帶有類(lèi)型名,所以下面介紹的方法轉(zhuǎn)換器的實(shí)現(xiàn)中要根據(jù)處理的不同目標(biāo)類(lèi)型凡简,調(diào)用不同的 getType/setType 方法逼友。

【注意】如果將一張數(shù)據(jù)表中的一行數(shù)據(jù)映射成一個(gè)Java對(duì)象精肃,則Java對(duì)象中的每個(gè)成員屬性跟表中的一行的每個(gè)列映射,上述的類(lèi)型轉(zhuǎn)換器帜乞,實(shí)際上是完成一個(gè)屬性跟一個(gè)數(shù)據(jù)庫(kù)字段列的映射司抱,因?yàn)镴ava對(duì)象有多個(gè)成員,數(shù)據(jù)表有多個(gè)字段挖函,所以實(shí)際映射一行數(shù)據(jù)的時(shí)候状植,要有多個(gè)類(lèi)型轉(zhuǎn)換器參與處理。上述的Java對(duì)象怨喘,實(shí)際是指一個(gè)成員屬性對(duì)應(yīng)的對(duì)象津畸。

三、BaseTypeHandler

??BaseTypeHandler 是一個(gè)承上啟下的抽象類(lèi)必怜,它實(shí)現(xiàn)了 TypeHandler 接口肉拓,又被所有具體的轉(zhuǎn)換器類(lèi)所繼承,因?yàn)镾QL綁定參數(shù)時(shí)可能傳入一個(gè)空的參數(shù)梳庆,結(jié)果映射時(shí)也可能獲取到一個(gè)空的數(shù)據(jù)暖途,這里需要一點(diǎn)特別的處理,這些都放在 BaseTypeHandler 中統(tǒng)一實(shí)現(xiàn)膏执,簡(jiǎn)化定義類(lèi)型轉(zhuǎn)換器的工作驻售。

??BaseTypeHandler 封裝了 Configuration 對(duì)象作為成員,該對(duì)象與 mybatis 全局配置中的配置信息映射更米,因?yàn)樵?mybatis-config.xml 用戶(hù)可以自定義類(lèi)型轉(zhuǎn)換器處理一些特殊的類(lèi)型欺栗,比如枚舉。

public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {

  protected Configuration configuration;

  public void setConfiguration(Configuration c) {
    this.configuration = c;
  }

  // other code
}

??BaseTypeHandler 還繼承了一個(gè)帶泛型的父類(lèi) TypeReference征峦,該類(lèi)主要用來(lái)解析泛型的具體類(lèi)型迟几,后面會(huì)介紹。

1栏笆、void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType)

【功能】使用 Java 類(lèi)型對(duì)象為 SQL 綁定參數(shù)类腮。
【源碼與注解】

  @Override
  public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
    // (1)當(dāng)Java對(duì)象為空時(shí),調(diào)用 PreparedStatement.setNull() 綁定參數(shù)
    if (parameter == null) {
      // 上述方法需要確定JdbcType蛉加,所以如果jdbcType也為空蚜枢,該方法無(wú)法執(zhí)行,拋出異常
      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) {
        // 傳入的JdbcType類(lèi)型不合適针饥,拋出異常
        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 {
        // (2)調(diào)用類(lèi)中定義的抽象方法祟偷,該方法在繼承了BaseTypeHandler的子類(lèi)中有具體且不同的實(shí)現(xiàn)
        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);
      }
    }
  }

  public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

【解析】

  • (1)當(dāng)Java對(duì)象為空時(shí),調(diào)用 PreparedStatement.setNull() 綁定參數(shù)打厘,如果傳入的 jdbcType 也為空則拋出異常,如果方法執(zhí)行拋出異常贺辰,證明傳入的 jdbcType 不合適户盯。
  • (2)調(diào)用類(lèi)中定義的抽象方法嵌施,該方法在繼承了BaseTypeHandler的子類(lèi)中有具體且不同的實(shí)現(xiàn)。

2莽鸭、T getResult(ResultSet rs, String columnName)

【功能】根據(jù)列名對(duì) SQL 執(zhí)行結(jié)果 ResultSet 對(duì)象進(jìn)行映射成 Java 對(duì)象吗伤。
【源碼與注解】

  @Override
  public T getResult(ResultSet rs, String columnName) throws SQLException {
    T result;
    try {
      // 調(diào)用子類(lèi)中實(shí)現(xiàn)的抽象方法獲取結(jié)果值
      result = getNullableResult(rs, columnName);
    } catch (Exception e) {
      throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set.  Cause: " + e, e);
    }
    // 判斷映射的結(jié)果集是否為空
    if (rs.wasNull()) {
      return null;
    } else {
      return result;
    }
  }

  public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;

【解析】
??這里代碼第一眼看有點(diǎn)費(fèi)解,為什么先嘗試從結(jié)果集中獲取結(jié)果硫眨,再來(lái)判斷結(jié)果集是否空呢足淆?從 ResetSet.wasNull() 的注釋中可以看出,需要調(diào)用 get 方法礁阁,才能使用該方法巧号,所以這里要先嘗試先調(diào)用 #getNullableResult() 獲取結(jié)果,該方法的具體實(shí)現(xiàn)由各具體的轉(zhuǎn)換器子類(lèi)實(shí)現(xiàn)姥闭。


??其他兩個(gè)重名的重載方法跟上述方法類(lèi)似丹鸿,不再贅述。

四棚品、各種類(lèi)型轉(zhuǎn)換器

??TypeHandler 有很多類(lèi)型轉(zhuǎn)化器靠欢,下面分類(lèi)說(shuō)明。

1铜跑、基本數(shù)據(jù)類(lèi)型對(duì)應(yīng)的包裝類(lèi)型轉(zhuǎn)換處理器

??Java 中八種基本數(shù)據(jù)類(lèi)型分別是 short门怪、int、long锅纺、float掷空、double、boolean伞广、byte拣帽、char,對(duì)應(yīng)的包裝類(lèi)型分別為 Short嚼锄、Integer减拭、Long、Float区丑、Double拧粪、Boolean、Byte沧侥、Character可霎,對(duì)應(yīng)的類(lèi)型轉(zhuǎn)換器別分為 ShortTypeHandler、IntegerTypeHandler宴杀、LongTypeHandler癣朗、FloatTypeHandler、DoubleTypeHandler旺罢、BooleanTypeHandler旷余、ByteTypeHandler绢记、CharacterTypeHandler,這幾個(gè)轉(zhuǎn)換器的實(shí)現(xiàn)都是類(lèi)似的正卧,只是調(diào)用 PreparedStatement/ResultSet/CallableStatement 對(duì)應(yīng)的 set/get 方法有差異蠢熄,以 IntegerTypeHandler 為例,源碼如下:

public class IntegerTypeHandler extends BaseTypeHandler<Integer> {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
      throws SQLException {
    ps.setInt(i, parameter);
  }

  @Override
  public Integer getNullableResult(ResultSet rs, String columnName)
      throws SQLException {
    return rs.getInt(columnName);
  }

  @Override
  public Integer getNullableResult(ResultSet rs, int columnIndex)
      throws SQLException {
    return rs.getInt(columnIndex);
  }

  @Override
  public Integer getNullableResult(CallableStatement cs, int columnIndex)
      throws SQLException {
    return cs.getInt(columnIndex);
  }
}

??其他的 TypeHandler 比如 ShortTypeHandler 就是將 #getInt 改成 #getShort炉旷,將 #setInt 改成 #setShort签孔,其他類(lèi)型轉(zhuǎn)換器以此類(lèi)推。

2窘行、字節(jié)饥追、字符串及相關(guān)數(shù)組對(duì)應(yīng)的類(lèi)型轉(zhuǎn)換處理器

??在介紹這些轉(zhuǎn)換處理器之前,有必要先了解關(guān)于字符抽高、字符串相關(guān)的通用 SQL 類(lèi)型判耕,列表如下:

類(lèi)型 用途
char(n)/character(n) 字符/字符串。固定長(zhǎng)度n
varchar(n)/character varying(n) 字符/字符串翘骂”谙ǎ可變長(zhǎng)度,最大長(zhǎng)度n
blob 二進(jìn)制形式的長(zhǎng)文本數(shù)據(jù)
clob/text 以字節(jié)為單位保存文本大對(duì)象
nclob 以 unicode 字符為單位保存文本大對(duì)象

?? 不同數(shù)據(jù)庫(kù)數(shù)據(jù)類(lèi)型的定義略有不同碳竟,不過(guò)大同小異草丧,一般都有定長(zhǎng)和變長(zhǎng)字符類(lèi)型可選,兩者的區(qū)別在于莹桅,定長(zhǎng)類(lèi)型的數(shù)據(jù)存儲(chǔ)前數(shù)據(jù)庫(kù)就分配一塊固定的存儲(chǔ)空間了昌执,而變長(zhǎng)是實(shí)際用到多少分配多少。

??mysql 和 oracle 都有 blob 的數(shù)據(jù)類(lèi)型诈泼,用來(lái)存儲(chǔ)二進(jìn)制形式的長(zhǎng)文本數(shù)據(jù)懂拾,最大存儲(chǔ)空間不同數(shù)據(jù)庫(kù)設(shè)計(jì)略有差異,為了滿(mǎn)足不同大小的數(shù)據(jù)庫(kù)存儲(chǔ)的需求铐达,mysql 還有 tinyblob岖赋、mediumbloblongblob 的數(shù)據(jù)類(lèi)型瓮孙,oracle 還有 bfile 滿(mǎn)足超大數(shù)據(jù)塊的存儲(chǔ)需求唐断。

??對(duì)于存儲(chǔ)文本形式的大數(shù)據(jù)塊,mysql 對(duì)應(yīng)的數(shù)據(jù)類(lèi)型為 tinytext杭抠、text脸甘、mediumtextlongtext偏灿,而 oracle 則設(shè)計(jì)了 clob丹诀、nclobclob 使用數(shù)據(jù)庫(kù)編碼的字符的定長(zhǎng)字節(jié)存儲(chǔ)文本,nclob 使用 unicode 編碼的字符的定長(zhǎng)字節(jié)存儲(chǔ)文本忿墅。

(1)與 blob 相關(guān)的類(lèi)型轉(zhuǎn)換處理器

BlobTypeHandler / BlobByteObjectArrayTypeHandler / BlobInputStreamTypeHandler

public class BlobTypeHandler extends BaseTypeHandler<byte[]> {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, byte[] parameter, JdbcType jdbcType)
      throws SQLException {
    // 將字節(jié)數(shù)組轉(zhuǎn)化為流對(duì)象
    ByteArrayInputStream bis = new ByteArrayInputStream(parameter);
    ps.setBinaryStream(i, bis, parameter.length);
  }

  @Override
  public byte[] getNullableResult(ResultSet rs, String columnName)
      throws SQLException {
    Blob blob = rs.getBlob(columnName);
    byte[] returnValue = null;
    if (null != blob) {
      returnValue = blob.getBytes(1, (int) blob.length());
    }
    return returnValue;
  }
  // 另外兩個(gè)方法類(lèi)似

【解析】
??參數(shù)綁定時(shí)扁藕,先將字節(jié)數(shù)組轉(zhuǎn)化為流對(duì)象,再綁定參數(shù)疚脐,這樣數(shù)據(jù)庫(kù)在接收數(shù)據(jù)時(shí)是從流對(duì)象中讀取,直到到達(dá)文件末尾邢疙,性能更佳棍弄;結(jié)果映射都是從結(jié)果集中獲取到一個(gè) Blob 對(duì)象,再?gòu)脑搶?duì)象中讀取字節(jié)數(shù)組疟游。

??BlobByteObjectArrayTypeHandler 跟 BlobTypeHandler 的處理基本一樣呼畸,只不過(guò)處理的java對(duì)象類(lèi)型為 Byte[],所以處理之前將 Byte[] 轉(zhuǎn)換為 byte[]颁虐,結(jié)果映射時(shí)反過(guò)來(lái)將 byte[] 轉(zhuǎn)換為 Byte[]蛮原。

??BlobInputStreamTypeHandler 處理的java數(shù)據(jù)類(lèi)型是 InputStream,不過(guò)用的不是 #setBinaryStream()另绩,而是 #setBlob()儒陨,這里有點(diǎn)疑惑,看 Javadoc 的解釋是 #setBlob() 方法會(huì)通知驅(qū)動(dòng)發(fā)送 Blob 形式的參數(shù)值給服務(wù)端笋籽;而 #setBinaryStream() 方法驅(qū)動(dòng)處理時(shí)會(huì)有額外的工作決定發(fā)送 LONGVARBINARY 還是 Blob 形式的參數(shù)值給服務(wù)端蹦漠。至于 LONGVARBINARY 和 Blob 有什么區(qū)別?這些類(lèi)型在 Java 端可能就都是字節(jié)數(shù)組车海,但是在數(shù)據(jù)庫(kù)驅(qū)動(dòng)中應(yīng)該進(jìn)行了封裝處理轉(zhuǎn)化為不同的數(shù)據(jù)類(lèi)型笛园,但是我們無(wú)法得知是怎么處理的,因?yàn)轵?qū)動(dòng)一般只對(duì)外提供接口侍芝。

(2)與 clob 相關(guān)的類(lèi)型轉(zhuǎn)換處理器

ClobTypeHandler / NClobTypeHandler / ClobReaderTypeHandler

public class ClobTypeHandler extends BaseTypeHandler<String> {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
      throws SQLException {
    StringReader reader = new StringReader(parameter);
    ps.setCharacterStream(i, reader, parameter.length());
  }

  @Override
  public String getNullableResult(ResultSet rs, String columnName)
      throws SQLException {
    String value = "";
    Clob clob = rs.getClob(columnName);
    if (clob != null) {
      int size = (int) clob.length();
      value = clob.getSubString(1, size);
    }
    return value;
  }
  // 另外兩個(gè)方法類(lèi)似

【解析】
??參數(shù)綁定時(shí)研铆,先將字符串轉(zhuǎn)化為 Reader 對(duì)象,再綁定參數(shù)州叠,該對(duì)象為給定的字符數(shù)棵红。 當(dāng)將非常大的 UNICODE 值輸入到 LONGVARCHAR 參數(shù)時(shí),通過(guò) java.io.Reader 對(duì)象發(fā)送它可能更實(shí)際留量。 數(shù)據(jù)將根據(jù)需要從流中讀取窄赋,直到到達(dá)文件末尾。 JDBC 驅(qū)動(dòng)程序?qū)?zhí)行從 UNICODE 到數(shù)據(jù)庫(kù)char格式的所有必要轉(zhuǎn)換楼熄。

??NClobTypeHandler 的實(shí)現(xiàn)跟 ClobTypeHandler 完全一樣忆绰,不知道為什么要定義這樣一個(gè)多余的類(lèi)型轉(zhuǎn)換處理器?

??ClobReaderTypeHandler 處理的java數(shù)據(jù)類(lèi)型是 Reader可岂,不過(guò)用的不是 #setCharacterStream()错敢,而是 #setClob(),原理跟上面一樣。

(3)與字節(jié)相關(guān)的類(lèi)型轉(zhuǎn)換處理器

ByteTypeHandler / ByteArrayTypeHandler / ByteObjectArrayTypeHandler

??代碼跟上面類(lèi)似稚茅,不贅述纸淮,這里會(huì)用到一個(gè)用具類(lèi),用于 byte[]Byte[] 之間的互相轉(zhuǎn)換亚享,源碼如下:

class ByteArrayUtils {

  private ByteArrayUtils() {
    // Prevent Instantiation
  }

  static byte[] convertToPrimitiveArray(Byte[] objects) {
    final byte[] bytes = new byte[objects.length];
    for (int i = 0; i < objects.length; i++) {
      bytes[i] = objects[i];
    }
    return bytes;
  }

  static Byte[] convertToObjectArray(byte[] bytes) {
    final Byte[] objects = new Byte[bytes.length];
    for (int i = 0; i < bytes.length; i++) {
      objects[i] = bytes[i];
    }
    return objects;
  }
}


(4)與字符串相關(guān)的類(lèi)型轉(zhuǎn)換處理器

StringTypeHandler / NStringTypeHandler

??兩個(gè)類(lèi)的實(shí)現(xiàn)完全一樣咽块,java類(lèi)型是 String,調(diào)用 PreparedStatement 的 setString欺税、getString 方法處理侈沪。

3、日期時(shí)間相關(guān)的類(lèi)型轉(zhuǎn)換處理器

??先了解一下日期時(shí)間相關(guān)通用 SQL 數(shù)據(jù)類(lèi)型晚凿,列表如下:

類(lèi)型 格式 用途
DATE YYYY-MM-DD 日期
TIME HH:MM:SS 時(shí)間值
YEAR YYYY 年份值
DATETIME YYYY-MM-DD HH:MM:SS 混合日期和時(shí)間值
TIMESTAMP YYYY-MM-DD HH:MM:SS 混合日期和時(shí)間值亭罪,時(shí)間戳

(1)日期相關(guān)類(lèi)型轉(zhuǎn)換處理器

DateTypeHandler / DateOnlyTypeHandler / SqlDateTypeHandler

public class DateTypeHandler extends BaseTypeHandler<Date> {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType)
      throws SQLException {
    // 將 java.util.Date 轉(zhuǎn)化為 java.sql.Timestamp,再設(shè)置到 ps 中
    ps.setTimestamp(i, new Timestamp((parameter).getTime()));
  }

  @Override
  public Date getNullableResult(ResultSet rs, String columnName)
      throws SQLException {
    // 從結(jié)果集中獲得 Timestamp 的值
    Timestamp sqlTimestamp = rs.getTimestamp(columnName);
    // 將 Timestamp 轉(zhuǎn)化為 Date 類(lèi)型
    if (sqlTimestamp != null) {
      return new Date(sqlTimestamp.getTime());
    }
    return null;
  }

  // 另外兩個(gè)方法類(lèi)似

【解析】
??DateTypeHandlerjava.util.Date 的 JavaType 轉(zhuǎn)化為 timestamp 的 JdbcType歼秽,綁定參數(shù)時(shí)先轉(zhuǎn)化為 java.sql.Timestamp应役,再設(shè)置到 ps 中,結(jié)果映射反過(guò)來(lái)燥筷。
??DateOnlyTypeHandlerjava.util.Datejava.sql.Date 之間的轉(zhuǎn)換箩祥,處理類(lèi)似。
??SqlDateTypeHandler 更簡(jiǎn)單了荆责,傳入返回的參數(shù)類(lèi)型就是 java.sql.Date 無(wú)需做額外的類(lèi)型轉(zhuǎn)換滥比。

(2)時(shí)間相關(guān)類(lèi)型轉(zhuǎn)換處理器

TimeOnlyTypeHandler / SqlTimeTypeHandler / SqlTimestampTypeHandler

  • TimeOnlyTypeHandler:javaType 是 java.util.Date,jdbcType 是 time做院,中間通過(guò) java.sql.Time 轉(zhuǎn)換盲泛。
  • SqlTimeTypeHandler:javaType 是 java.sql.Time,jdbcType 是 time键耕。
  • SqlTimestampTypeHandler:javaType 是 java.sql.Timestamp寺滚,jdbcType 是 timestamp

4屈雄、枚舉相關(guān)的類(lèi)型轉(zhuǎn)換處理器

??MyBatis 內(nèi)置了兩種枚舉類(lèi)型轉(zhuǎn)換處理器 EnumTypeHandler村视、EnumOrdinalTypeHandler

EnumTypeHandler

【功能】將枚舉轉(zhuǎn)化為對(duì)應(yīng)的枚舉類(lèi)型字符串酒奶,再綁定參數(shù)蚁孔;結(jié)果映射時(shí)拿到代表枚舉的字符串,再轉(zhuǎn)化為枚舉類(lèi)型惋嚎。
【源碼與注解】

public class EnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {

  private Class<E> type;

  public EnumTypeHandler(Class<E> type) {
    if (type == null) {
      throw new IllegalArgumentException("Type argument cannot be null");
    }
    this.type = type;
  }

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
    // 將 Enum 轉(zhuǎn)換成 String 類(lèi)型
    if (jdbcType == null) {
      ps.setString(i, parameter.name());
    } else {
      ps.setObject(i, parameter.name(), jdbcType.TYPE_CODE); // see r3589
    }
  }

  @Override
  public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
    // 獲得 String 的值
    String s = rs.getString(columnName);
    // 將 String 轉(zhuǎn)換成 Enum 類(lèi)型
    return s == null ? null : Enum.valueOf(type, s);
  }

  // 另外兩個(gè)重載方法處理類(lèi)似
}

【解析】
??假設(shè)有一個(gè)這樣的枚舉類(lèi)杠氢,定義如下:

public enum SexEnum {
    FEMALE(1, "女"),
    MALE(0, "男");

    private int code;
    private String name;
    
    
    
    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

??上面 parameter.name() 得到的就是一個(gè) "FEMALE""MALE" 的字符串,存入數(shù)據(jù)表列中也是存儲(chǔ)這么一個(gè)值另伍,結(jié)果映射時(shí)也是用這么一個(gè)字符串調(diào)用 Enum.valueOf() 轉(zhuǎn)化為對(duì)應(yīng)的枚舉類(lèi)型返回鼻百。

EnumOrdinalTypeHandler

【功能】根據(jù)枚舉類(lèi)型獲取在枚舉數(shù)組中的索引位置,再將其作為一個(gè)整數(shù)存儲(chǔ)在數(shù)據(jù)庫(kù)中。
【源碼與注解】

public class EnumOrdinalTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {

  private Class<E> type;
  private final E[] enums;

  public EnumOrdinalTypeHandler(Class<E> type) {
    if (type == null) {
      throw new IllegalArgumentException("Type argument cannot be null");
    }
    this.type = type;
    this.enums = type.getEnumConstants();
    if (this.enums == null) {
      throw new IllegalArgumentException(type.getSimpleName() + " does not represent an enum type.");
    }
  }

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
    // 將 Enum 轉(zhuǎn)換成 int 類(lèi)型
    ps.setInt(i, parameter.ordinal());
  }

  @Override
  public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
    // 獲得 int 的值
    int i = rs.getInt(columnName);
    if (rs.wasNull()) {
      return null;
    } else {
      try {
        // 將 int 轉(zhuǎn)換成 Enum 類(lèi)型
        return enums[i];
      } catch (Exception ex) {
        throw new IllegalArgumentException("Cannot convert " + i + " to " + type.getSimpleName() + " by ordinal value.", ex);
      }
    }
  }

【解析】
??還是上面的枚舉類(lèi)為例子温艇,SexEnum.FEMALE.ordinal() 得到的是 0因悲,保存到數(shù)據(jù)庫(kù)中的就是一個(gè)整數(shù),結(jié)果映射反過(guò)來(lái)勺爱。

5晃琳、對(duì)象類(lèi)型轉(zhuǎn)換處理器

ObjectTypeHandler

【功能】數(shù)據(jù)庫(kù)驅(qū)動(dòng)自動(dòng)根據(jù)傳入的Java對(duì)象類(lèi)型和數(shù)據(jù)表字段類(lèi)型,自動(dòng)轉(zhuǎn)換琐鲁。
【源碼】

public class ObjectTypeHandler extends BaseTypeHandler<Object> {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType)
      throws SQLException {
    ps.setObject(i, parameter);
  }

  @Override
  public Object getNullableResult(ResultSet rs, String columnName)
      throws SQLException {
    return rs.getObject(columnName);
  }

  @Override
  public Object getNullableResult(ResultSet rs, int columnIndex)
      throws SQLException {
    return rs.getObject(columnIndex);
  }

  @Override
  public Object getNullableResult(CallableStatement cs, int columnIndex)
      throws SQLException {
    return cs.getObject(columnIndex);
  }
}

【解析】
??如果數(shù)據(jù)類(lèi)型不明確蝎土,可以調(diào)用 setObject、getObject 方法處理绣否,如果知道數(shù)據(jù)類(lèi)型,優(yōu)先使用指明處理類(lèi)型的方法挡毅。

6蒜撮、UnknownTypeHandler

??如果轉(zhuǎn)換時(shí),參數(shù)綁定傳入的參數(shù)類(lèi)型或者結(jié)果集數(shù)據(jù)類(lèi)型不清晰跪呈,則調(diào)用 UnknownTypeHandler 先處理解析出具體的類(lèi)型段磨,再調(diào)用對(duì)應(yīng)的 TypeHandler 處理。
【源碼分析】

public class UnknownTypeHandler extends BaseTypeHandler<Object> {

  // 靜態(tài)變量耗绿,單例共享
  private static final ObjectTypeHandler OBJECT_TYPE_HANDLER = new ObjectTypeHandler();
  // TypeHandler 注冊(cè)表
  private TypeHandlerRegistry typeHandlerRegistry;

  public UnknownTypeHandler(TypeHandlerRegistry typeHandlerRegistry) {
    this.typeHandlerRegistry = typeHandlerRegistry;
  }

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType)
      throws SQLException {
    // 根據(jù)傳入的參數(shù)和jdbcType獲得對(duì)應(yīng) TypeHandler
    TypeHandler handler = resolveTypeHandler(parameter, jdbcType);
    // 使用處理器綁定參數(shù)
    handler.setParameter(ps, i, parameter, jdbcType);
  }

  @Override
  public Object getNullableResult(ResultSet rs, String columnName)
      throws SQLException {
    // 根據(jù)結(jié)果集和列名獲得對(duì)應(yīng)的處理器
    TypeHandler<?> handler = resolveTypeHandler(rs, columnName);
    // 使用處理器處理結(jié)果映射
    return handler.getResult(rs, columnName);
  }

  @Override
  public Object getNullableResult(ResultSet rs, int columnIndex)
      throws SQLException {
    // 根據(jù)結(jié)果集和列名獲得對(duì)應(yīng)的處理器
    TypeHandler<?> handler = resolveTypeHandler(rs.getMetaData(), columnIndex);
    // 從注冊(cè)表中可能找不到合適的處理器
    if (handler == null || handler instanceof UnknownTypeHandler) {
      handler = OBJECT_TYPE_HANDLER;
    }
    // 使用處理器處理結(jié)果映射
    return handler.getResult(rs, columnIndex);
  }

  @Override
  public Object getNullableResult(CallableStatement cs, int columnIndex)
      throws SQLException {
    return cs.getObject(columnIndex);
  }

  private TypeHandler<? extends Object> resolveTypeHandler(Object parameter, JdbcType jdbcType) {
    TypeHandler<? extends Object> handler;
    // 若參數(shù)值為空苹支,則使用默認(rèn)的 ObjectTypeHandler
    if (parameter == null) {
      handler = OBJECT_TYPE_HANDLER;
    // 若參數(shù)非空,根據(jù)參數(shù)類(lèi)型從注冊(cè)表中獲取處理器
    } else {
      handler = typeHandlerRegistry.getTypeHandler(parameter.getClass(), jdbcType);
      // check if handler is null (issue #270)
      // 若沒(méi)有獲得明確類(lèi)型的處理器误阻,則使用默認(rèn)的 ObjectTypeHandler
      if (handler == null || handler instanceof UnknownTypeHandler) {
        handler = OBJECT_TYPE_HANDLER;
      }
    }
    return handler;
  }

  private TypeHandler<?> resolveTypeHandler(ResultSet rs, String column) {
    try {
      // 獲得結(jié)果集中的元數(shù)據(jù)信息债蜜,并將所有的數(shù)據(jù)列名和位置索引的映射關(guān)系保存在 columnIndexLookup 中
      Map<String,Integer> columnIndexLookup;
      columnIndexLookup = new HashMap<String,Integer>();
      ResultSetMetaData rsmd = rs.getMetaData();
      int count = rsmd.getColumnCount();
      for (int i=1; i <= count; i++) {
        String name = rsmd.getColumnName(i);
        columnIndexLookup.put(name,i);
      }
      // 從 columnIndexLookup 中找到列名對(duì)應(yīng)的位置索引
      Integer columnIndex = columnIndexLookup.get(column);
      TypeHandler<?> handler = null;
      if (columnIndex != null) {
        handler = resolveTypeHandler(rsmd, columnIndex);
      }
      // 若沒(méi)有找到列名對(duì)應(yīng)的索引,則使用默認(rèn)的 ObjectTypeHandler
      if (handler == null || handler instanceof UnknownTypeHandler) {
        handler = OBJECT_TYPE_HANDLER;
      }
      return handler;
    } catch (SQLException e) {
      throw new TypeException("Error determining JDBC type for column " + column + ".  Cause: " + e, e);
    }
  }

  private TypeHandler<?> resolveTypeHandler(ResultSetMetaData rsmd, Integer columnIndex) throws SQLException {
    TypeHandler<?> handler = null;
    JdbcType jdbcType = safeGetJdbcTypeForColumn(rsmd, columnIndex);
    Class<?> javaType = safeGetClassForColumn(rsmd, columnIndex);
    // 根據(jù) JdbcType 和 JavaType 從注冊(cè)表中調(diào)用對(duì)應(yīng)的方法獲得處理器
    if (javaType != null && jdbcType != null) {
      handler = typeHandlerRegistry.getTypeHandler(javaType, jdbcType);
    } else if (javaType != null) {
      handler = typeHandlerRegistry.getTypeHandler(javaType);
    } else if (jdbcType != null) {
      handler = typeHandlerRegistry.getTypeHandler(jdbcType);
    }
    return handler;
  }

  private JdbcType safeGetJdbcTypeForColumn(ResultSetMetaData rsmd, Integer columnIndex) {
    try {
      // 從 ResultSetMetaData 中究反,獲得字段類(lèi)型的 JdbcType
      return JdbcType.forCode(rsmd.getColumnType(columnIndex));
    } catch (Exception e) {
      return null;
    }
  }

  private Class<?> safeGetClassForColumn(ResultSetMetaData rsmd, Integer columnIndex) {
    try {
      // 從 ResultSetMetaData 中寻定,獲得字段類(lèi)型的 JavaType
      return Resources.classForName(rsmd.getColumnClassName(columnIndex));
    } catch (Exception e) {
      return null;
    }
  }
}


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市精耐,隨后出現(xiàn)的幾起案子狼速,更是在濱河造成了極大的恐慌,老刑警劉巖卦停,帶你破解...
    沈念sama閱讀 221,888評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件向胡,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡惊完,警方通過(guò)查閱死者的電腦和手機(jī)僵芹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)专执,“玉大人淮捆,你說(shuō)我怎么就攤上這事。” “怎么了攀痊?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,386評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵桐腌,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我苟径,道長(zhǎng)案站,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,726評(píng)論 1 297
  • 正文 為了忘掉前任棘街,我火速辦了婚禮蟆盐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘遭殉。我一直安慰自己石挂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布险污。 她就那樣靜靜地躺著痹愚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蛔糯。 梳的紋絲不亂的頭發(fā)上拯腮,一...
    開(kāi)封第一講書(shū)人閱讀 52,337評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音蚁飒,去河邊找鬼动壤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛淮逻,可吹牛的內(nèi)容都是我干的琼懊。 我是一名探鬼主播,決...
    沈念sama閱讀 40,902評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼弦蹂,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼肩碟!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起凸椿,我...
    開(kāi)封第一講書(shū)人閱讀 39,807評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤削祈,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后脑漫,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體髓抑,經(jīng)...
    沈念sama閱讀 46,349評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評(píng)論 3 340
  • 正文 我和宋清朗相戀三年优幸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了吨拍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,567評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡网杆,死狀恐怖羹饰,靈堂內(nèi)的尸體忽然破棺而出伊滋,到底是詐尸還是另有隱情,我是刑警寧澤队秩,帶...
    沈念sama閱讀 36,242評(píng)論 5 350
  • 正文 年R本政府宣布笑旺,位于F島的核電站,受9級(jí)特大地震影響馍资,放射性物質(zhì)發(fā)生泄漏筒主。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評(píng)論 3 334
  • 文/蒙蒙 一鸟蟹、第九天 我趴在偏房一處隱蔽的房頂上張望乌妙。 院中可真熱鬧,春花似錦建钥、人聲如沸藤韵。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,420評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)荠察。三九已至,卻和暖如春奈搜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背盯荤。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,531評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工馋吗, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人秋秤。 一個(gè)月前我還...
    沈念sama閱讀 48,995評(píng)論 3 377
  • 正文 我出身青樓宏粤,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親灼卢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子绍哎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評(píng)論 2 359