Mybatis源碼之美:2.14.解析Mybatis的typeHandlers元素,配置Mybatis的類型轉(zhuǎn)換器

解析Mybatis的typeHandlers元素,配置Mybatis的類型轉(zhuǎn)換器

在學(xué)習(xí)本章內(nèi)容之前,可以通過類型轉(zhuǎn)換器(typeHandlers)來了解關(guān)于TypeHandler的用法。

示例

為了更好的理解mybatisTypeHandler對象,我們在測試包org.apache.learning下,新建一個(gè)typehandler包,該包下的所有數(shù)據(jù),均用于演示TypeHandler對象的用法。

首先我們在包下新建一個(gè)CreateDB.sql的腳本文件:

drop table users if exists;
create table users
(
    id     varchar(36),
    name   varchar(20),
    gender varchar(6)

);

insert into users (id, name,gender) values ('38400000-8cf0-11bd-b23e-10b96e4ef00d', 'Panda', '男');

該腳本用于創(chuàng)建一個(gè)users表,并插入一條數(shù)據(jù)以供使用。

之后我們新建一個(gè)mybatis-config.xml文件,該文件用于為mybatis提供一個(gè)基本的數(shù)據(jù)源環(huán)境配置:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC">
                <property name="" value=""/>
            </transactionManager>
            <!-- 使用 hsql 內(nèi)存數(shù)據(jù)庫 -->
            <dataSource type="UNPOOLED">
                <property name="driver" value="org.hsqldb.jdbcDriver"/>
                <property name="url" value="jdbc:hsqldb:mem:type_handler_test"/>
                <property name="username" value="sa"/>
            </dataSource>
        </environment>
    </environments>

</configuration>

然后創(chuàng)建一個(gè)名為EUserGender的枚舉對象:

/**
 * 用戶性別
 *
 * @author HanQi [Jpanda@aliyun.com]
 * @version 1.0
 * @since 2020/3/14 10:47
 */
@Getter
@AllArgsConstructor
public enum EUserGender {
    MALE("男"), FEMALE("女");
    private String name;
}

并為EUserGender創(chuàng)建一個(gè)專屬的TypeHandler——EUserGenderTypeHandler:

public class EUserGenderTypeHandler extends BaseTypeHandler<EUserGender> {

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

    @Override
    public EUserGender getNullableResult(ResultSet rs, String columnName) throws SQLException {
        String columnValue = rs.getString(columnName);

        return findEUserGender(columnValue);
    }

    @Override
    public EUserGender getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        String columnValue = rs.getString(columnIndex);
        return findEUserGender(columnValue);
    }

    @Override
    public EUserGender getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        String columnValue = cs.getString(columnIndex);
        return findEUserGender(columnValue);
    }

    private EUserGender findEUserGender(String dbName) {
        return Stream.of(EUserGender.values())
                .filter(g -> g.getName().equals(dbName))
                .findFirst()
                .orElse(null);
    }
}

再新建一個(gè)名為User的實(shí)體對象,對象中的屬性定義和創(chuàng)建的users表中的列保持一致:

@Getter
@Setter
@NoArgsConstructor
public class User {
    private String id;
    private String name;
    private EUserGender gender;
}

緊接著新建一個(gè)Mapper接口,該接口定義了操作User對象的兩個(gè)方法:

public interface Mapper {
    List<User> selectByGender(EUserGender gender);

    List<User> selectByGenderWithoutSpecify(EUserGender gender);
}

以及Mapper對應(yīng)的xml文件——Mapper.xml

<?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="org.apache.learning.typehandler.Mapper">

    <resultMap type="org.apache.learning.typehandler.User"
               id="userResult">
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <!-- 指定屬性對應(yīng)的TypeHandler-->
        <result property="gender" column="gender"
                typeHandler="org.apache.learning.typehandler.EUserGenderTypeHandler"/>
    </resultMap>

    <!-- 指定屬性對應(yīng)的TypeHandler-->
    <select id="selectByGender" resultMap="userResult">
        select *
        from users
        where gender = #{gender, typeHandler=org.apache.learning.typehandler.EUserGenderTypeHandler}
    </select>

    <!-- 未指定屬性對應(yīng)的TypeHandler-->
    <select id="selectByGenderWithoutSpecify" resultType="org.apache.learning.typehandler.User">
        select *
        from users
        where gender = #{gender}
    </select>

</mapper>

其中selectByGender()的方法針對gender屬性明確指定了EUserGenderTypeHandler作為類型轉(zhuǎn)換器,selectByGenderWithoutSpecify()方法只是一個(gè)普通的方法定義。

完成上述的基礎(chǔ)數(shù)據(jù)的準(zhǔn)備之后,新建一個(gè)名為typeHanlderTest的單元測試類,測試常見的三種場景:

/**
 * 類型轉(zhuǎn)換器測試類
 *
 * @author HanQi [Jpanda@aliyun.com]
 * @version 1.0
 * @since 2020/3/14 10:45
 */
public class TypeHandlerTest {
    private static SqlSessionFactory sqlSessionFactory;

    @BeforeEach
    @SneakyThrows
    public void setup() {
        @Cleanup
        Reader reader = Resources.getResourceAsReader("org/apache/learning/typehandler/mybatis-config.xml");
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        sqlSessionFactory.getConfiguration().addMapper(Mapper.class);
        BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), "org/apache/learning/typehandler/CreateDB.sql");
    }

    /**
     * 聲明語句時(shí),指定類型轉(zhuǎn)換器
     */
    @Test
    public void testForCustomTypeHandler() {
        // 執(zhí)行準(zhǔn)備工作
        @Cleanup
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 執(zhí)行
        Mapper mapper = sqlSession.getMapper(Mapper.class);
        List<User> users = mapper.selectByGender(EUserGender.MALE);
        Assertions.assertEquals("Panda", users.get(0).getName());


    }

    /**
     * 普通方法且無類型轉(zhuǎn)換器
     */
    @Test
    public void testForCustomTypeHandlerWithoutSpecify() {
        // 執(zhí)行準(zhǔn)備工作
        @Cleanup
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 執(zhí)行
        Mapper mapper = sqlSession.getMapper(Mapper.class);
        List<User> users = mapper.selectByGenderWithoutSpecify(EUserGender.MALE);
        Assertions.assertTrue(users.isEmpty());
    }

    /**
     * 測試普通方法觸發(fā)全局類型轉(zhuǎn)換器
     */
    @Test
    public void testForCustomTypeHandlerWithoutSpecifyNeedSuccess() {
        // 注冊全局的類型轉(zhuǎn)換器
        sqlSessionFactory.getConfiguration().getTypeHandlerRegistry().register(EUserGenderTypeHandler.class);

        // 執(zhí)行準(zhǔn)備工作
        @Cleanup
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 執(zhí)行
        Mapper mapper = sqlSession.getMapper(Mapper.class);
        List<User> users = mapper.selectByGenderWithoutSpecify(EUserGender.MALE);
        Assertions.assertEquals("Panda", users.get(0).getName());
    }
}

毫無疑問,上面的單元測試都能正常運(yùn)行,這也就意味著我們自定義的TypeHandler生效了,那么TypeHandler到底是一個(gè)怎樣的東西呢?

類型轉(zhuǎn)換器——TypeHandler

TypeHandlermyabtis中定義的負(fù)責(zé)jdbc類型和java類型互相轉(zhuǎn)換的策略接口,我稱之為類型轉(zhuǎn)換器。

TypeHandler接口有一個(gè)泛型參數(shù)定義,這個(gè)泛型參數(shù)就是要處理的java類型,該定義還約束了標(biāo)準(zhǔn)的類型轉(zhuǎn)換器需要提供下列四種方法:

  • 根據(jù)參數(shù)索引和jdbc類型設(shè)置PreparedStatement語句中的值,即將?替換為實(shí)際值的setParameter(PreparedStatement,int,T,JdbcType).
  • 獲取指定ResultSet中指定名稱的列對應(yīng)的值,并轉(zhuǎn)換為相應(yīng)的JAVA類型的getResult(ResultSet,String)。
  • 獲取指定ResultSet中指定索引下面的列對應(yīng)的值,并轉(zhuǎn)換為相應(yīng)的JAVA類型的getResult(ResultSet,int)。
  • 獲取指定存儲過程(CallableStatement)中指定索引下標(biāo)對應(yīng)的值,并轉(zhuǎn)換為相應(yīng)的JAVA類型的getResult(CallableStatement,int
/**
 * 類型轉(zhuǎn)換處理器
 *
 * @author Clinton Begin
 */
public interface TypeHandler<T> {

    /**
     *  根據(jù)參數(shù)索引和jdbc類型設(shè)置PreparedStatement語句中的值,即將? 替換為實(shí)際值。
     *
     * @param ps        PreparedStatement 語句
     * @param i         參數(shù)索引
     * @param parameter 參數(shù)
     * @param jdbcType  jdbc類型
     * @throws SQLException SQL異常
     */
    void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

    /**
     * 獲取指定ResultSet中指定名稱的列對應(yīng)的值,并轉(zhuǎn)換為相應(yīng)的JAVA類型
     *
     * @param rs         ResultSet
     * @param columnName 列名稱
     * @return 值
     * @throws SQLException SQL異常
     */
    T getResult(ResultSet rs, String columnName) throws SQLException;

    /**
     * 獲取指定ResultSet中指定索引下面的列對應(yīng)的值,并轉(zhuǎn)換為相應(yīng)的JAVA類型
     *
     * @param rs          ResultSet
     * @param columnIndex 列索引
     * @return 值
     * @throws SQLException SQL異常
     */
    T getResult(ResultSet rs, int columnIndex) throws SQLException;

    /**
     * 獲取指定存儲過程(CallableStatement)中指定索引下標(biāo)對應(yīng)的值,并轉(zhuǎn)換為相應(yīng)的JAVA類型
     *
     * @param cs          存儲過程
     * @param columnIndex 列索引
     * @return 值
     * @throws SQLException SQL異常
     */
    T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}

為了簡化我們的操作,Mybatis定義了一個(gè)名為BaseTypeHandler的抽象類,實(shí)現(xiàn)了TypeHandler接口,他是Mybatis提供的一個(gè)默認(rèn)的類型轉(zhuǎn)換器的基類。

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

    /**
     * Mybatis配置
     *
     * @deprecated Since 3.5.0 - See https://github.com/mybatis/mybatis-3/issues/1203. This field will remove future.
     */
    @Deprecated
    protected Configuration configuration;

    /**
     * @deprecated Since 3.5.0 - See https://github.com/mybatis/mybatis-3/issues/1203. This property will remove future.
     */
    @Deprecated
    public void setConfiguration(Configuration c) {
        this.configuration = c;
    }

    /**
     * @param ps        PreparedStatement
     * @param i         參數(shù)索引
     * @param parameter 參數(shù)值
     * @param jdbcType  jdbc類型
     */
    @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 {
        try {
            return getNullableResult(rs, columnName);
        } catch (Exception e) {
            throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set.  Cause: " + e, e);
        }
    }

    @Override
    public T getResult(ResultSet rs, int columnIndex) throws SQLException {
        try {
            return getNullableResult(rs, columnIndex);
        } catch (Exception e) {
            throw new ResultMapException("Error attempting to get column #" + columnIndex + " from result set.  Cause: " + e, e);
        }
    }

    @Override
    public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
        try {
            return getNullableResult(cs, columnIndex);
        } catch (Exception e) {
            throw new ResultMapException("Error attempting to get column #" + columnIndex + " from callable statement.  Cause: " + e, e);
        }
    }

    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實(shí)現(xiàn)了TypeHandler接口定義的默認(rèn)方法,添加了基本的數(shù)據(jù)校驗(yàn),并暴露出了新的抽象方法供子類實(shí)現(xiàn)。

雖然他還添加了對Mybatis配置類Configuration的引用,并提供了對應(yīng)的設(shè)值方法,但是這一功能已被棄用,所以這里就不在討論。

獲取泛型定義原始類型——TypeReference

除此之外,BaseTypeHandler還繼承了TypeReference抽象類,提供了獲取泛型定義原始類型的能力。

public abstract class TypeReference<T> {

    /**
     * TypeReference<T> 中T的泛型類型
     */
    private final Type rawType;

    protected TypeReference() {
        // 獲取當(dāng)前類的泛型類型
        rawType = getSuperclassTypeParameter(getClass());
    }
    // ... 省略 getSuperclassTypeParameter() 方法
}

TypeReference抽象類中提供了一個(gè)Type類型的rawType屬性,并對外了暴露了該屬性的getter方法,該屬性的作用是保存類上泛型定義的類型,他的初始化工作是在TypeReference的構(gòu)造方法中完成的。

有關(guān)于泛型解析相關(guān)的知識,我們在介紹TypeParameterResolver時(shí)已經(jīng)做了很完整的分析,這里就不在贅述。

我們自己定義的EUserGenderTypeHandler就繼承了BaseTypeHandler基類,并通過泛型參數(shù)指定了要處理的java類型是EUserGender。

存儲類型轉(zhuǎn)換器的注冊表——TypeHandlerRegistry

在簡單的了解了TypeHandler之后,我們回過頭來繼續(xù)看TypeHandlerRegistry。

Tips:
我想換一種方式來描述對象的功能和實(shí)現(xiàn),之前描述一個(gè)類的源碼時(shí),都是站在程序員的角度去想,去寫,按照方法調(diào)用棧來解析源代碼?,F(xiàn)在我想站在產(chǎn)品的角度來解析代碼,
先給出實(shí)例存在的目的和宗旨,并圍繞著這個(gè)目的和宗旨,逐步的擴(kuò)展出代碼。

TypeHandlerRegistry的作用

TypeHandlerRegistry作為存儲類型轉(zhuǎn)換器的注冊表,他存在的目的就是為了保存所有類型轉(zhuǎn)換器,并提供相應(yīng)的注冊獲取類型轉(zhuǎn)換器的功能。

編程是一件開放性很高的事情,一千個(gè)人眼中就有一千個(gè)哈姆雷特,一千個(gè)程序員對于同一個(gè)功能可能有不止一萬種實(shí)現(xiàn)方案。

所以在接下來我們會根據(jù)需求去梳理TypeHandlerRegistry的實(shí)現(xiàn),而不是去重新實(shí)現(xiàn)一個(gè)新的TypeHandlerRegistry。

TypeHandlerRegistry的屬性定義

首先我們看一下TypeHandlerRegistry中的屬性定義:

/**
 * jdbc類型和處理器的映射關(guān)系
 */
private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<>(JdbcType.class);
/**
 * java類型和映射處理器的關(guān)系
 */
private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<>();
/**
 * 未知類型的對應(yīng)的默認(rèn)處理器
 */
private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this);
/**
 * 處理器類型和處理器實(shí)例的映射關(guān)系
 */
private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<>();

/**
 * 空的類型轉(zhuǎn)換器映射關(guān)系,僅僅是一個(gè)標(biāo)志性對象
 */
private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();

/**
 * 默認(rèn)的枚舉類型轉(zhuǎn)換器
 */
private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;

仔細(xì)看前兩個(gè)屬性的定義,JDBC_TYPE_HANDLER_MAP用于存放JdbcTypeTypeHandler的對應(yīng)關(guān)系。

TYPE_HANDLER_MAPkey值是一個(gè)java類型,valueMap<JdbcType, TypeHandler<?>>集合。

這兩個(gè)屬性的定義很容易讓人誤認(rèn)為TYPE_HANDLER_MAPvalue存放是的指定java類型對應(yīng)的JDBC_TYPE_HANDLER_MAP集合。

但是,事實(shí)上,在梳理源碼后,我們會發(fā)現(xiàn)二者毫無關(guān)系。

JDBC_TYPE_HANDLER_MAP的作用是:維護(hù)一個(gè)JdbcType和其默認(rèn)TypeHandler之間的對應(yīng)關(guān)系。

TYPE_HANDLER_MAP的作用是:維護(hù)java類型和其所有可轉(zhuǎn)換的JdbcType的關(guān)系,并保留負(fù)責(zé)進(jìn)行轉(zhuǎn)換操作的TypeHandler實(shí)例。

換句話,TYPE_HANDLER_MAP集合中的value雖然類型也是Map<JdbcType, TypeHandler<?>>,但是他存儲的JdbcTypeTypeHandler的對應(yīng)關(guān)系可能會因?yàn)樗鶎俚?code>java類型的不同而不同。

比如:

var TYPE_HANDLER_MAP={
 'Integer':{
 "JdbcType.INTEGER":"IntegerTypeHandler"
 },
 'Double':{
 "JdbcType.INTEGER":"DoubleTypeHandler"
 }
}

在上面這個(gè)通過JS描述的偽對象TYPE_HANDLER_MAP中,我們可以看到隨著java類型由Integer變成Double,JdbcType.INTEGER對應(yīng)的TypeHandler實(shí)例也由IntegerTypeHandler變成了DoubleTypeHandler。

除了這個(gè)容易讓人混淆的問題之外,還有一個(gè)問題困擾了我很久,按照前面的理解,一個(gè)jdbc類型應(yīng)該是可以對應(yīng)多個(gè)TypeHandler對象的,畢竟,一個(gè)jdbc類型可能會轉(zhuǎn)換成不同的JAVA對象,可是按照JDBC_TYPE_HANDLER_MAP的定義,一個(gè)jdbc類型只能對應(yīng)一個(gè)TypeHandler,這是為什么呢?

這是因?yàn)?code>JDBC_TYPE_HANDLER_MAP負(fù)責(zé)存儲的是JdbcType和其默認(rèn)TypeHandler之間的對應(yīng)關(guān)系,JDBC_TYPE_HANDLER_MAPTYPE_HANDLER_MAP的一個(gè)補(bǔ)充,當(dāng)TYPE_HANDLER_MAP中無法獲取TypeHandler時(shí),就會考慮根據(jù)jdbc類型從JDBC_TYPE_HANDLER_MAP中獲取一個(gè)默認(rèn)的TypeHandler來完成類型轉(zhuǎn)換的處理操作。

TypeHandlerRegistry還有其他幾個(gè)屬性,被final修飾的UNKNOWN_TYPE_HANDLER屬性是一個(gè)UnknownTypeHandler類型的類型轉(zhuǎn)換器。

UnknownTypeHandler雖然聽起來像是用于處理未知類型,但是實(shí)際上在他的實(shí)現(xiàn)中,我們會發(fā)現(xiàn)他更偏向于一個(gè)代理對象,他會在運(yùn)行期間,動態(tài)的去選擇一個(gè)有效的TypeHandler實(shí)例來完成類型轉(zhuǎn)換的工作。

類型為Map<JdbcType, TypeHandler<?>>NULL_TYPE_HANDLER_MAP屬性和JDBC_TYPE_HANDLER_MAP也沒有任何關(guān)系,他是一個(gè)標(biāo)志性的對象,用來表示一個(gè)空的類型轉(zhuǎn)換器映射關(guān)系。

類型為Map<Class<?>, TypeHandler<?>>ALL_TYPE_HANDLERS_MAP屬性,他的keyTypeHandler實(shí)例的具體類型,他的value則是TypeHandler實(shí)例本身,他的作用是維護(hù)所有有效的TypeHandler。

最后一個(gè)類型為Class<? extends TypeHandler>defaultEnumTypeHandler屬性,則定義了默認(rèn)的枚舉類型轉(zhuǎn)換器。

枚舉類型轉(zhuǎn)換器實(shí)際上是一個(gè)比較特殊的處理器,他有別于普通的處理器只用于處理特定的類,枚舉類型轉(zhuǎn)換器會處理任意繼承了Enum的類。

注冊類型轉(zhuǎn)換器功能的實(shí)現(xiàn)

TypeHandlerRegistry的幾個(gè)集合類型的屬性已經(jīng)為我們提供了保存類型轉(zhuǎn)換器的功能,現(xiàn)在我們看一下TypeHandlerRegistry如何實(shí)現(xiàn)注冊類型轉(zhuǎn)換器的功能。

現(xiàn)在已經(jīng)知道一個(gè)類型轉(zhuǎn)換器核心的數(shù)據(jù)有:被處理的Java類型,被處理的JDBC類型以及類型轉(zhuǎn)換器TypeHandler本身這三個(gè)核心要素,同時(shí)Java類型JDBC類型之間是一個(gè)多對多的關(guān)系。

那么就注冊功能的實(shí)現(xiàn)來講,最好的場景就是我們能夠直接拿到Java類型JDBC類型以及相應(yīng)的類型轉(zhuǎn)換器TypeHandler實(shí)例這三個(gè)參數(shù)。

如果不能,那就退而求其次,獲取類型轉(zhuǎn)換器TypeHandler加上Java類型JDBC類型中的一個(gè)。

如果還不行,只有類型轉(zhuǎn)換器TypeHandler也可以。

對于那些缺失的參數(shù),我們的實(shí)現(xiàn)通常是想辦法推導(dǎo)出它們,然后轉(zhuǎn)為完美場景進(jìn)行處理。

TypeHandlerRegistry在注冊類型轉(zhuǎn)換器的功能實(shí)現(xiàn)方面,提供了較多的register方法重載方法。

其中有一個(gè)方法簽名為private void register(Type javaType, JdbcType jdbcType,TypeHandler<?> handler)的重載方法,用于處理三要素齊全的場景,完成了事實(shí)上的類型轉(zhuǎn)換器的注冊工作:

/**
 * 注冊類型轉(zhuǎn)換器
 *
 * @param javaType java類型
 * @param jdbcType jdbc類型
 * @param handler  類型轉(zhuǎn)換器
 */
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
    if (javaType != null) {
        // 獲取該java類型對應(yīng)的jdbc類型轉(zhuǎn)換器的集合
        Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);

        if (map == null || map == NULL_TYPE_HANDLER_MAP) {
            map = new HashMap<>();
            // 注冊java類型和【jdbc和處理器的映射關(guān)系】的映射關(guān)系
            TYPE_HANDLER_MAP.put(javaType, map);
        }
        
        map.put(jdbcType, handler);
    }
    // 注冊處理器類型和實(shí)例的關(guān)系
    ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
}

這是register重載方法中最核心的方法,多數(shù)register重載方法的調(diào)用,最終會落在該方法上來完成最終的處理操作。

他的實(shí)現(xiàn)比較簡單,唯一值得注意的就是NULL_TYPE_HANDLER_MAP作為標(biāo)識符的作用就體現(xiàn)在這里。

還有一些register重載方法的入?yún)笔?code>Java類型與JDBC類型中的一個(gè),甚至是二者全部缺失。

我們前面說過,當(dāng)我們?nèi)鄙倌骋粎?shù)時(shí),會盡可能的去推導(dǎo)出缺少屬性的參數(shù)類型,然后轉(zhuǎn)為完美場景進(jìn)行處理。

推導(dǎo)Java類型參數(shù)

在缺失java類型參數(shù)的場景下,TypeHandlerRegistry給出的解決方案是提供MappedTypes注解和TypeReference抽象父類。

MappedTypes注解可以直接提供TypeHandler對象可轉(zhuǎn)換的java類型集合:

/**
 * 該注解用于給類型處理器指定可處理的JAVA類型
 *
 * @author Eduardo Macarron
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MappedTypes {
    /**
     * 可轉(zhuǎn)換的JAVA類型集合
     */
    Class<?>[] value();
}

TypeReference抽象父類則可以獲取其實(shí)現(xiàn)類的泛型參數(shù)定義。

具體的解析方法可以參考register(TypeHandler<T> typeHandler)方法:

public <T> void register(TypeHandler<T> typeHandler) {
    boolean mappedTypeFound = false;
    // 獲取MappedTypes注解
    MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);
    if (mappedTypes != null) {
        for (Class<?> handledType : mappedTypes.value()) {
            // 根據(jù)注解,注冊類型轉(zhuǎn)換器
            register(handledType, typeHandler);
            mappedTypeFound = true;
        }
    }

    //  解析泛型
    // @since 3.1.0 - try to auto-discover the mapped type
    if (!mappedTypeFound && typeHandler instanceof TypeReference) {
        try {
            TypeReference<T> typeReference = (TypeReference<T>) typeHandler;
            register(typeReference.getRawType(), typeHandler);
            mappedTypeFound = true;
        } catch (Throwable t) {
            // maybe users define the TypeReference with a different type and are not assignable, so just ignore it
        }
    }
    // 特殊的null類型
    if (!mappedTypeFound) {
        register((Class<T>) null, typeHandler);
    }
}

優(yōu)先解析MappedTypes注解中指定的類型,其次是根據(jù)TypeReference獲取泛型類型,如果還沒能獲取java類型,那就使用特殊的類型null


推導(dǎo)Jdbc類型參數(shù)

推導(dǎo)jdbc類型參數(shù),TypeHandlerRegistry給出的解決方案是提供MappedJdbcTypes注解,和MappedTypes相似。

MappedJdbcTypes注解的作用是標(biāo)注出指定TypeHandler可轉(zhuǎn)換的jdbc類型集合:

/**
 * 給TypeHandler指定處理的JdbcType
 * @author Eduardo Macarron
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MappedJdbcTypes {
    // 可處理的jdbc類型集合
    JdbcType[] value();
    // 是否額外注冊JDBC類型為null的處理器
    boolean includeNullJdbcType() default false;
}

MappedJdbcTypes除了可以指定jdbc類型集合之外,可有一個(gè)includeNullJdbcType()方法,用于指定是否額外注冊Jdbc類型為null的處理器。

負(fù)責(zé)推導(dǎo)jdbc類型參數(shù)的方法是private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler):

/**
 * 注冊java類型和類型轉(zhuǎn)換出處理器的關(guān)系
 *
 * @param javaType    java類型
 * @param typeHandler 類型轉(zhuǎn)換處理器實(shí)例
 * @param <T>         java類型
 */
private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
    // 尋找mappedJdbcTypes注解,mappedJdbcTypes用于給TypeHandler指定處理的JdbcType
    MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
    if (mappedJdbcTypes != null) {
        for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
            // 注冊java類型處理和jdbc類型以及類型轉(zhuǎn)換處理器的關(guān)系
            register(javaType, handledJdbcType, typeHandler);
        }
        if (mappedJdbcTypes.includeNullJdbcType()) {
            // 注冊JDBC類型為null的處理器
            register(javaType, null, typeHandler);
        }
    } else {
        // 注冊JDBC類型為null的處理器
        register(javaType, null, typeHandler);
    }
}

上面的兩個(gè)方法的實(shí)現(xiàn)上,分別

上面提到的三個(gè)register()方法,是TypeHandlerRegistry中比較核心的三個(gè)注冊方法,這三個(gè)方法相互配合使用基本可以完成絕大多數(shù)注冊場景。

還有一些其他的register()重載方法,這些重載方法中有一部分用于處理java類型的變種表現(xiàn):

比如,將String類型描述的Java類型轉(zhuǎn)換為實(shí)際的Java類型:

/**
 * 注冊指定java類型的指定類型轉(zhuǎn)換器
 *
 * @param javaTypeClassName    java類型名稱
 * @param typeHandlerClassName 類型轉(zhuǎn)換處理器名稱
 */
public void register(String javaTypeClassName, String typeHandlerClassName) throws ClassNotFoundException {
    register(Resources.classForName(javaTypeClassName), Resources.classForName(typeHandlerClassName));
}

比如,獲取TypeReference對應(yīng)的泛型類型,然后進(jìn)行注冊:

/**
 * 根據(jù)泛型注冊類型轉(zhuǎn)換處理器
 *
 * @param javaTypeReference java泛型
 * @param handler           處理器
 * @param <T>               泛型
 */
public <T> void register(TypeReference<T> javaTypeReference, TypeHandler<? extends T> handler) {
    register(javaTypeReference.getRawType(), handler);
}

還有一部分register()方法用于處理特殊的場景,比如:

/**
 * 注冊JDBC類型轉(zhuǎn)換器
 *
 * @param jdbcType jdbc類型
 * @param handler  處理器
 */
public void register(JdbcType jdbcType, TypeHandler<?> handler) {
    // 注冊jdbc類型轉(zhuǎn)換器
    JDBC_TYPE_HANDLER_MAP.put(jdbcType, handler);
}

這個(gè)方法注冊的是指定的Jdbc類型與其對應(yīng)的默認(rèn)轉(zhuǎn)換器,和前面提到那些register()方法在實(shí)際意義上完全不同。

還有一個(gè)register()方法用于批量注冊類型轉(zhuǎn)換器:

// 注冊指定包下所有的java類
public void register(String packageName) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    // 返回當(dāng)前已經(jīng)找到的所有TypeHandler的子類
    resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName);
    Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses();
    for (Class<?> type : handlerSet) {
        //Ignore inner classes and interfaces (including package-info.java) and abstract classes
        if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
            // 忽略匿名類、接口以及抽象類
            // 注冊類型轉(zhuǎn)換處理器
            register(type);
        }
    }
}

在這個(gè)方法中,他通過ResolverUtilsfind()方法來獲取初步符合預(yù)期的類集合,之后排除掉匿名類、成員類和接口,對剩下的類執(zhí)行別名注冊的操作。

有關(guān)于更多ResolverUtil的信息,在解析TypeAliases元素時(shí),已經(jīng)給出。


獲取類型轉(zhuǎn)換器功能的實(shí)現(xiàn)

在實(shí)現(xiàn)上TypeHandlerRegistry獲取類型轉(zhuǎn)換器的方法大致可以分為兩類,其中一類是用于判斷指定的類型轉(zhuǎn)換器是否存在的hasTypeHandler()方法,一類是實(shí)際獲取類型轉(zhuǎn)換器對象的getTypeHandler()方法。

這兩類方法的實(shí)現(xiàn)邏輯大致是一致的,hasTypeHandler()方法僅僅是對getTypeHandler()方法的返回結(jié)果做了一層非空判斷,并包裝成Boolean值而已。

getTypeHandler()方法的核心重載實(shí)現(xiàn)是:

/**
 * 獲取指定類型的處理器
 *
 * @param type     java類型
 * @param jdbcType jdbc類型
 * @param <T>      處理器類型
 */
@SuppressWarnings("unchecked")
private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
    if (ParamMap.class.equals(type)) {
        return null;
    }
    /*
     * 獲取所有指定類型的java處理器
     */
    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
    TypeHandler<?> handler = null;
    if (jdbcHandlerMap != null) {
        // 獲取對應(yīng)的jdbc處理器
        handler = jdbcHandlerMap.get(jdbcType);
        if (handler == null) {
            handler = jdbcHandlerMap.get(null);
        }
        if (handler == null) {
            // 選擇一個(gè)適合的惟一的處理器
            // #591
            handler = pickSoleHandler(jdbcHandlerMap);
        }
    }
    // 返回處理器
    // type drives generics here
    return (TypeHandler<T>) handler;
}

可以看到,方法實(shí)現(xiàn)比較簡單,先獲取指定java類型對應(yīng)的已注冊的jdbc類型及其轉(zhuǎn)換處理器,之后根據(jù)jdbc類型參數(shù)獲取轉(zhuǎn)換器,
如果沒有指定jdbc類型,則調(diào)用pickSoleHandler(jdbcHandlerMap)方法,嘗試獲取一個(gè)有效轉(zhuǎn)換器:

/**
 * 從一組類型轉(zhuǎn)換處理器中選擇唯一的一個(gè)轉(zhuǎn)換器,如果超過一個(gè)類型轉(zhuǎn)換處理器,返回null。
 *
 * @param jdbcHandlerMap jdbc類型對應(yīng)的類型轉(zhuǎn)換處理器集合
 */
private TypeHandler<?> pickSoleHandler(Map<JdbcType, TypeHandler<?>> jdbcHandlerMap) {
    TypeHandler<?> soleHandler = null;
    for (TypeHandler<?> handler : jdbcHandlerMap.values()) {
        if (soleHandler == null) {
            soleHandler = handler;
        } else if (!handler.getClass().equals(soleHandler.getClass())) {
            // More than one type handlers registered.
            return null;
        }
    }
    return soleHandler;
}

pickSoleHandler(jdbcHandlerMap)方法對于有效轉(zhuǎn)換器的定義比較簡單,那就是返回java類型對應(yīng)的唯一的類型轉(zhuǎn)換器,這個(gè)唯一的類型轉(zhuǎn)換器可以當(dāng)做是默認(rèn)的類型轉(zhuǎn)換器來使用。

負(fù)責(zé)獲取指定java類型對應(yīng)的已注冊jdbc類型及其轉(zhuǎn)換處理器的方法getJdbcHandlerMap()在實(shí)現(xiàn)上兼容了枚舉和子類的處理操作。

// 獲取指定java類型對應(yīng)所有類型轉(zhuǎn)換處理器
private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) {
    // 獲取當(dāng)前java類型對應(yīng)的類型轉(zhuǎn)換器集合
    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(type);

    if (NULL_TYPE_HANDLER_MAP.equals(jdbcHandlerMap)) {
        return null;
    }

    if (jdbcHandlerMap == null && type instanceof Class) {
        Class<?> clazz = (Class<?>) type;
        if (clazz.isEnum()) {
            // 根據(jù)枚舉查找類型轉(zhuǎn)換處理器
            jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(clazz, clazz);
            if (jdbcHandlerMap == null) {
                // 注冊類型轉(zhuǎn)換處理器
                register(clazz, getInstance(clazz, defaultEnumTypeHandler));
                // 返回類型轉(zhuǎn)換處理器
                return TYPE_HANDLER_MAP.get(clazz);
            }
        } else {
            // 根據(jù)父類查找類型轉(zhuǎn)換處理器
            jdbcHandlerMap = getJdbcHandlerMapForSuperclass(clazz);
        }
    }
    // 注冊類型轉(zhuǎn)換處理器
    TYPE_HANDLER_MAP.put(type, jdbcHandlerMap == null ? NULL_TYPE_HANDLER_MAP : jdbcHandlerMap);
    return jdbcHandlerMap;
}

枚舉兼容

在處理枚舉類型時(shí),如果該枚舉沒有定義相關(guān)的類型轉(zhuǎn)換器,將會遞歸調(diào)用getJdbcHandlerMapForEnumInterfaces()方法,嘗試從實(shí)現(xiàn)的接口定義中獲取類型轉(zhuǎn)換器。

如果其接口也沒有對應(yīng)的類型轉(zhuǎn)換器,那么將會使用TypeHandlerRegistrydefaultEnumTypeHandler屬性對應(yīng)的類型轉(zhuǎn)換器作為當(dāng)前枚舉的類型處理器。

/**
 * 根據(jù)枚舉接口獲取類型轉(zhuǎn)換器
 *
 * @param clazz     java類型
 * @param enumClazz 枚舉類型
 * @return 類型轉(zhuǎn)換處理器
 */
private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMapForEnumInterfaces(Class<?> clazz, Class<?> enumClazz) {
    for (Class<?> iface : clazz.getInterfaces()) {
        // 從指定java類的接口上找類型轉(zhuǎn)換處理器
        Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(iface);

        if (jdbcHandlerMap == null) {
            // 沒有對應(yīng)的類型轉(zhuǎn)換器,遞歸往上找
            jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(iface, enumClazz);
        }
        if (jdbcHandlerMap != null) {
            // 如果找到了類型轉(zhuǎn)換器
            // Found a type handler regsiterd to a super interface
            HashMap<JdbcType, TypeHandler<?>> newMap = new HashMap<>();
            for (Entry<JdbcType, TypeHandler<?>> entry : jdbcHandlerMap.entrySet()) {
                // Create a type handler instance with enum type as a constructor arg
                // 創(chuàng)建一個(gè)對應(yīng)該類的類型轉(zhuǎn)換器
                newMap.put(entry.getKey(), getInstance(enumClazz, entry.getValue().getClass()));
            }
            return newMap;
        }
    }
    return null;
}

子類兼容

在處理普通類型時(shí),如果該類型沒有定義相關(guān)的類型轉(zhuǎn)換器,將會遞歸調(diào)用getJdbcHandlerMapForSuperclass()方法,嘗試從繼承的父類定義中獲取類型轉(zhuǎn)換器。

private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMapForSuperclass(Class<?> clazz) {
    Class<?> superclass = clazz.getSuperclass();
    if (superclass == null || Object.class.equals(superclass)) {
        return null;
    }
    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(superclass);
    if (jdbcHandlerMap != null) {
        return jdbcHandlerMap;
    } else {
        return getJdbcHandlerMapForSuperclass(superclass);
    }
}

無論是針對普通類還是枚舉,在查找到新的類型轉(zhuǎn)換器關(guān)系之后,都會重新注冊到TypeHandlerRegistry中。

我思考了很久,為什么在做子類兼容時(shí),只遞歸查找父類對應(yīng)的類型轉(zhuǎn)換器集合,而不是連父接口對應(yīng)的類型轉(zhuǎn)換器一并查找?

先說結(jié)論:因?yàn)闆]必要。

首先,我們要明確一點(diǎn):接口,是抽象方法的集合,方法定義的是一個(gè)對象的行為,所以接口定義的是一類對象的行為,而不是特性。

被類型轉(zhuǎn)換器處理的java類型通常需要具有一定程度上的一致性和可預(yù)見性,比如,給定Jdbc數(shù)值A,可以獲取java對象a,那么理論上可預(yù)見的是:通過java對象a可以獲得Jdbc數(shù)值A。

這種特性意味著,被類型轉(zhuǎn)換器處理的java類型通常具有簡單性,不會是一個(gè)復(fù)雜的業(yè)務(wù)對象,這時(shí)候強(qiáng)調(diào)的往往是該對象的某一特性,而不是行為,這時(shí)候,這種對象通常會被直接定義為類而不是接口。

那么枚舉對象為什么適配的是接口而不是類呢?

枚舉對象也具有簡單性,但是枚舉默認(rèn)繼承了Enum父類,所以只能實(shí)現(xiàn)接口,沒有辦法直接定義屬性,所以退而求其次,通過方法來間接的限制屬性。

getTypeHandler()方法還有一些其他重載實(shí)現(xiàn),邏輯大致與register()方法重載實(shí)現(xiàn)一致,這里就不做討論了。

到這里,TypeHandlerRegistry就完成了保存所有類型轉(zhuǎn)換器,以及相應(yīng)的注冊獲取類型轉(zhuǎn)換器的功能的實(shí)現(xiàn)。

其他

TypeHandlerRegistry中還有一些其他方法定義,比如defaultEnumTypeHandler屬性的getter/setter方法和UNKNOWN_TYPE_HANDLER屬性的getter方法,以及通過反射獲取TypeHandler對象實(shí)例的getInstance方法。

這些方法的實(shí)現(xiàn)比較簡單,這里就不在贅述了。


解析typeHandlers元素

現(xiàn)在我們回到typeHandlers元素的解析工作中。

在Mybatis中關(guān)于typeHandlersDTD是如此定義的:

<!ELEMENT typeHandlers (typeHandler*,package*)>

typeHandlers下面可以出現(xiàn)零個(gè)或多個(gè)typeHandler或者package標(biāo)簽。

typeHandler元素用于注冊單個(gè)TypeHandler實(shí)例,它有三個(gè)屬性javaType,jdbcType以及handler,其中javaTypejdbcType是可選的,他們分別表示java類型和jdbc類型,handler屬性是必填的,他表示類型轉(zhuǎn)換器的類型,這三個(gè)屬性都可以使用類型別名。

<!ELEMENT typeHandler EMPTY>
<!ATTLIST typeHandler
javaType CDATA #IMPLIED
jdbcType CDATA #IMPLIED
handler CDATA #REQUIRED
>

package元素用于批量注冊TypeHandler實(shí)例,它只有一個(gè)必填的name屬性,他表示用戶需要注冊TypeHandler的基礎(chǔ)包名,Mybatis將會遞歸處理該基礎(chǔ)包及其子包下所有可用的TypeHandler實(shí)例。

<!ELEMENT package EMPTY>
<!ATTLIST package
name CDATA #REQUIRED
>

調(diào)用解析的入口(XmlConfigBuilder):

private void parseConfiguration(XNode root) {
    // ...
    // 注冊類型轉(zhuǎn)換器
    typeHandlerElement(root.evalNode("typeHandlers"));
   // ...
}

解析并注冊TypeHandler實(shí)例:

// 解析typeHandlers元素
private void typeHandlerElement(XNode parent) {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
                // 整包注冊
                String typeHandlerPackage = child.getStringAttribute("name");
                typeHandlerRegistry.register(typeHandlerPackage);
            } else {
                // 單個(gè)注冊
                // 獲取java類型的名稱
                String javaTypeName = child.getStringAttribute("javaType");
                // 獲取jdbc類型的名稱
                String jdbcTypeName = child.getStringAttribute("jdbcType");
                // 獲取類型轉(zhuǎn)換處理器的名稱
                String handlerTypeName = child.getStringAttribute("handler");
                // 解析出java類型
                Class<?> javaTypeClass = resolveClass(javaTypeName);
                // 解析jdbc類型
                JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
                // 解析出類型轉(zhuǎn)換處理器的類型
                Class<?> typeHandlerClass = resolveClass(handlerTypeName);

                // 注冊類型處理器
                if (javaTypeClass != null) {
                    if (jdbcType == null) {
                        typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
                    } else {
                        typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
                    }
                } else {
                    typeHandlerRegistry.register(typeHandlerClass);
                }
            }
        }
    }
}

根據(jù)子元素的不同,TypeHandler實(shí)例的解析注冊也分為兩種:

一類是解析typeHandler元素,嘗試獲取注冊TypeHandler實(shí)例的三要素完成注冊工作。

一類是解析package元素,獲取用于批量注冊的包名,調(diào)用TypeHandlerRegistryregister()方法完成整包批量注冊。

關(guān)于具體注冊方法的實(shí)現(xiàn),前面已經(jīng)有了詳細(xì)的闡述。

至此,整個(gè)typeHandlers元素的解析也已經(jīng)完成,Mybatis的基礎(chǔ)準(zhǔn)備工作也準(zhǔn)備的差不多了。

接下來就是解析MybatisMapper配置文件。

關(guān)注我,一起學(xué)習(xí)更多知識

關(guān)注我
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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