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

在JDBC中,需要通過(guò)PreparedStatement對(duì)象中設(shè)置那些已經(jīng)預(yù)編譯過(guò)的SQL語(yǔ)句的參數(shù),執(zhí)行SQL后,會(huì)通過(guò)ResultSet對(duì)象獲取得到數(shù)據(jù)庫(kù)的數(shù)據(jù),而這些MyBatis是根據(jù)數(shù)據(jù)的類型通過(guò)typeHandler來(lái)實(shí)現(xiàn)的,在typeHandler中,分為jdbcType和javaType,其中jdbcType用于定義數(shù)據(jù)庫(kù)類型,而javaType用于定義java類型,那么typeHandler的作用就是承擔(dān)jdbcType和javaType之間的相互轉(zhuǎn)換,
在很多中情況下,我們并不需要去配置typeHandler,jdbctype,javatype。因?yàn)镸yBatis會(huì)探測(cè)應(yīng)該使用什么類型的typeHandler進(jìn)行配置,但是有些場(chǎng)景就無(wú)法探測(cè)到,對(duì)于那些需要使用自定義的枚舉的場(chǎng)景,或者數(shù)據(jù)庫(kù)使用特殊數(shù)據(jù)類型的場(chǎng)景,可以使用自定義的typeHandler去處理類型之間的轉(zhuǎn)換問(wè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;
 
}

這里我們稍微說(shuō)明一下他們的定義

  • 其中T是泛型,專指javaType,比如我們需要String的時(shí)候,那么實(shí)現(xiàn)類可以寫(xiě)為implements TypeHandler<String>
  • setParameter方法,是使用typeHandler通過(guò)PreparedStatement對(duì)象設(shè)置SQL參數(shù)的時(shí)候使用的具體方法,其中i是參數(shù)在SQL的下標(biāo),parameter是參數(shù),jdbcType是數(shù)據(jù)庫(kù)類型
  • 其中有三個(gè)getResult的方法,它的作用是從JDBC結(jié)果集中獲取數(shù)據(jù)進(jìn)行轉(zhuǎn)換,要么使用列名,(columnname)要么使用下標(biāo)(columnIndex)獲取數(shù)據(jù)庫(kù)的數(shù)據(jù),其中最后一個(gè)getResult方法是存儲(chǔ)過(guò)程使用的。
    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;
 
}

簡(jiǎn)單分析一下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é)果集是通過(guò)getNullableResult方法去獲取的,該方法需要子類去實(shí)現(xiàn),同樣是針對(duì)下標(biāo)、列名以及存儲(chǔ)過(guò)程三種的實(shí)現(xiàn)
  • getNullableParameter方法用于存儲(chǔ)過(guò)程

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è)抽象方法,代碼也非常簡(jiǎn)單。
在這里,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è),而是通過(guò)配置或者掃描。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ù)庫(kù)的數(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上下文就可以通過(guò)它找到對(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ù)庫(kù)返回為空導(dǎo)致無(wú)法斷定采用哪個(gè)typeHandler來(lái)處理,而又沒(méi)有注冊(cè)對(duì)應(yīng)的javaType的typeHandler時(shí),MyBatis無(wú)法知道使用哪個(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="表的主鍵字段,或者可以為查詢語(yǔ)句中的別名字段" 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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容