Mybatis 源码学习(八) —— type 包

Mybatis 系列文章导航

 MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。—— Mybatis 官网

类 UML 图

在这里插入图片描述

 注:由于 BaseTypeHandler 的实现类有很多,所以图中只展示了部分实现类。

 可以看到,将包中的类转换为 UML 图以后,看上去就清晰很多。

类型处理器

 从包名中可知这是和类型相关的包,根据官网的描述,我们可以知道 JDBC 类型和 Java 类型的相互转换就是这个包的主要作用。因为在 Java 中有 String、Integer 等类型,而在数据库中有 char、varchar、tinyint 等类型,不同类型的的字段所需要的读写方式是不同的,因此需要对不同类型的字段采取相应的处理方式

TypeHandler

 TypeHandler 是所有类型处理器都需要实现的接口。如果要自定义类型处理器的话,也是实现该接口。

public interface TypeHandler<T> {
  // 向 PreparedStatement 中设置参数
  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;

}

TypeReference

 如果在遇到一个 TypeHandler 时,却不知道它到底是用来处理哪一种 Java 类型的处理器时,那么 TypeReference 的作用就出来了。它能判断出一个 TypeHandler 用来处理的目标类型。

Type getSuperclassTypeParameter(Class<?> clazz) {
    Type genericSuperclass = clazz.getGenericSuperclass();
    if (genericSuperclass instanceof Class) {
      // 一直向上查找,直到找到 ParameterizedType
      if (TypeReference.class != genericSuperclass) {
        return getSuperclassTypeParameter(clazz.getSuperclass());
      }

      throw new TypeException("'" + getClass() + "' extends TypeReference but misses the type parameter. "
        + "Remove the extension or add a type parameter to it.");
    }
    
    Type rawType = ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0];
    if (rawType instanceof ParameterizedType) {
      rawType = ((ParameterizedType) rawType).getRawType();
    }

    return rawType;
  }

 由于 TypeReference 类是 BaseTypeHandler 的父类,因此所有的类型处理器都继承了 TypeReference 的功能。所以都能通过 getSuperclassTypeParameter 得到处理器用来处理的目标类型。

BaseTypeHandler

 BaseTypeHandler 是 TypeHandler 的基础实现,将每个方法的异常信息进行了封装,在调用 getResult 方法时抛出的都是 ResultMapExcetion,在调用 setParameter 方法时抛出的都是 TypeException,并且将 setParameter 做了基础实现,parameter = null 的情况。让子类实现 setNonNullParamter 方法。

类型注册表

 为了避免在类型转换过程中,快速找到对应的类型处理器,所以就需要使用类型注册表类存储所有类型处理器,并记录它们的映射关系。在 type 包下有三个注册表:TypeHandlerRegistrySimpleTypeRegistryTypeAliasRegistry

SimpleTypeRegistry

 SimpleTypeRegistry 存储的是简单类型,而且实现也非常简单,通过一个 Set 来存储所有的简单类型,主要用来判断一个类是不是简单类型。

public class SimpleTypeRegistry {

  private static final Set<Class<?>> SIMPLE_TYPE_SET = new HashSet<>();

  static {
    SIMPLE_TYPE_SET.add(String.class);
    SIMPLE_TYPE_SET.add(Byte.class);
    SIMPLE_TYPE_SET.add(Short.class);
    SIMPLE_TYPE_SET.add(Character.class);
    SIMPLE_TYPE_SET.add(Integer.class);
    SIMPLE_TYPE_SET.add(Long.class);
    SIMPLE_TYPE_SET.add(Float.class);
    SIMPLE_TYPE_SET.add(Double.class);
    SIMPLE_TYPE_SET.add(Boolean.class);
    SIMPLE_TYPE_SET.add(Date.class);
    SIMPLE_TYPE_SET.add(Class.class);
    SIMPLE_TYPE_SET.add(BigInteger.class);
    SIMPLE_TYPE_SET.add(BigDecimal.class);
  }

}

TypeAliasRegistry

 TypeAliasRegistry 用来存储类型的别名。因为有了这个注册表,我们可以在很多需要书写类型的地方,改为书写它的别名,使代码看上去更直观更简洁。Mybatis 已经帮我们内置了常用类型的别名。

别名映射的类型别名映射的类型
_bytebytedoubleDouble
_longlongfloatFloat
_shortshortbooleanBoolean
_int
_integer
intdateDate
_doubledoubledecimalBigDecimal
_floatfloatbigdecimalBigDecimal
_booleanbooleanobjectObject
stringStringmapMap
byteBytehashmapHashMap
longLonglistList
shortShortarraylistArrayList
int
integer
IntegercollectionCollection
iteratorIterator

:这只是一部分,完整的可以去 TypeAliasRegistry 中看。

类注册到别名表

下面的代码将类注册到了别名表中,保存在了 typeAliases 中。

public void registerAlias(Class<?> type) {
    // 如果类上有 Alias 注解,就将其 value 作为别名
    // 如果没有注解,则将类名(不带包名称)的设置为别名
    String alias = type.getSimpleName();
    Alias aliasAnnotation = type.getAnnotation(Alias.class);
    if (aliasAnnotation != null) {
      alias = aliasAnnotation.value();
    }
    registerAlias(alias, type);
  }

  public void registerAlias(String alias, Class<?> value) {
    if (alias == null) {
      throw new TypeException("The parameter alias cannot be null");
    }
    // 将别名都转换为小写,所以别名都是忽略大小写的
    String key = alias.toLowerCase(Locale.ENGLISH);
    if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
      throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
    }
    typeAliases.put(key, value);
  }
从注册表中获取类
public <T> Class<T> resolveAlias(String string) {
    try {
      if (string == null) {
        return null;
      }
      
      // 将传入的名称都变为了小写,所以是忽略大小写的,也就是传 user 或者 User 其实都可以找到对应的类
      String key = string.toLowerCase(Locale.ENGLISH);
      Class<T> value;
      if (typeAliases.containsKey(key)) {
        value = (Class<T>) typeAliases.get(key);
      } else {
        // 也可以通过传完整的类名来获取类型
        value = (Class<T>) Resources.classForName(string);
      }
      return value;
    } catch (ClassNotFoundException e) {
      // ...
    }
  }

TypeHandlerRegistry

 TypeHandlerRegistry 是 type 包下最重要的类之一,它记录所有的 TypeHandler 以备使用。

将 TypeHandler 注册到表中

 由于注册的途径不同,所以在 TypeHandlerRegistry 中其实有在了很多方法,但是大部分的方法的作用都是适配器,即经过层层的适配,最后将注册的任务交给了最后的一个方法。

 可以观察下图可以便于理解。
在这里插入图片描述

在这里插入图片描述

 从图中我们可以知道,最底层的其实就是 register(Type, JdbcType, TypeHandler<?>) 方法,所以我们只要重点关注这个方法就好了。


  /**
   * 注册类型处理器到注册表中
   *
   * @param javaType Java 的类型
   * @param jdbcType Jdbc 的类型
   * @param handler  对应的类型处理器
   */
  private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
    if (javaType != null) {
      // 如果 JavaType 不为空,则可以进行注册,注册到 typeHandlerMap 中
      // Map<Type, Map<JdbcType, TypeHandler<?>>> 一个Java类型,可以转换到不同的 JDBC 类型,都由对应的类型处理器进行转换
      Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType);
      if (map == null || map == NULL_TYPE_HANDLER_MAP) {
        map = new HashMap<>();
      }
      map.put(jdbcType, handler);
      typeHandlerMap.put(javaType, map);
    }
    allTypeHandlersMap.put(handler.getClass(), handler);
  }

MappedTypes 和 MappedJdbcTypes

 Mybatis 给我们内置了很多的 TypeHandler,但是在写业务代码的过程中,可能你还需要自定义 TypeHanlder,那么 MappedTypesMappedJdbcTypes 的作用就来了。

 MappedTypes 是用来标示这个 TypeHanlder 可以处理的 Java 类型,MappedJdbcTypes 是用来标示这个 TypeHanlder 可以用来处理的 Jdbc 类型。

例如:

    // 用来处理 JdbcType.CHAR 和 JdbcType.VARCHAR 类型
   @MappedJdbcTypes({JdbcType.CHAR, JdbcType.VARCHAR})
   public class StringTrimmingTypeHandler implements TypeHandler<String> {
     // ...
   }
    // 用来处理 String 类型的
   @MappedTypes(String.class)
   public class StringTrimmingTypeHandler implements TypeHandler<String> {
     // ...
   }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: MyBatis 是一个开源的持久层框架,可以方便地将 SQL 语句和 Java 对象进行映射。如果您想要学习 MyBatis 源码,可以按照以下步骤进行: 1. 了解 MyBatis 的架构和设计原理。可以阅读官方文档和相关书籍,例如《MyBatis 技术内幕》。 2. 下载 MyBatis 的源代码,并导入到 IDE 中。MyBatis 使用 Maven 进行构建,您可以使用 IDE 的 Maven 插件来下载依赖项。 3. 查看 MyBatis 的源代码结构。MyBatis 的主要代码在 `mybatis-3` 模块中,括 `src/main/java` 和 `src/main/resources` 目录。其中,`src/main/java` 目录含了 MyBatis 的核心代码,例如 `org.apache.ibatis.session.SqlSession` 类;`src/main/resources` 目录含了 MyBatis 的配置文件和映射文件。 4. 阅读 MyBatis 的源代码。可以从 MyBatis 的入口处 `org.apache.ibatis.session.SqlSessionFactoryBuilder` 开始,深入了解 MyBatis 的初始化流程、SQL 语句的执行流程、映射文件的解析和缓存等。 5. 调试 MyBatis 的源代码。可以使用 IDE 的调试功能,对 MyBatis 进行单步调试,观察代码的执行流程,加深对 MyBatis 的理解。 6. 学习 MyBatis 的单元测试。MyBatis 的单元测试位于 `src/test/java` 目录中,可以通过单元测试来了解 MyBatis 的各个功能点的使用方法和测试用例。 7. 参与 MyBatis 的开发。如果您对 MyBatis 源码有深入的了解,并希望为 MyBatis 做出贡献,可以参与 MyBatis 的开发,贡献代码和文档,提交 issue 和 PR。MyBatis 的开发社区非常活跃,可以在官方网站和 GitHub 上找到相关信息。 希望这些步骤对您学习 MyBatis 源码有所帮助。 ### 回答2: MyBatis是一个开源的Java持久层框架,通过操作对象与数据库关系映射来提供数据持久化的功能。了解MyBatis源码学习和使用该框架的重要一步。 首先,MyBatis源码结构比较清晰,主要分为核心模块和附属模块。核心模块括XML配置解析、SQL语句解析、参数处理、数据库连接管理等功能的实现,是实现MyBatis基本功能的核心部分。附属模块括缓存、事务、插件等额外功能的实现,可以根据需要进行扩展和配置。 学习MyBatis源码可以从以下几个方面入手: 1. 配置文件解析:MyBatis通过XML配置文件来进行相关的配置,了解配置文件的解析过程可以帮助理解MyBatis的初始化过程和各项配置的作用。 2. SQL语句解析与执行:MyBatis将SQL语句封装成MappedStatement对象进行管理,了解MappedStatement的生成过程,以及SQL语句的解析、参数处理和执行过程,可以深入了解MyBatis的SQL执行原理。 3. 会话管理和事务处理:MyBatis采用SqlSessionFactory和SqlSession来管理数据库连接和事务,在MyBatis源码中可以学习到如何管理数据库连接池、事务的提交和回滚等核心功能的实现。 4. 缓存机制:MyBatis提供了一级缓存和二级缓存的功能,了解缓存的生成和更新过程,以及缓存的命中和失效原理,可以提高数据库查询性能。 总之,通过学习MyBatis源码,可以加深对该框架的理解,掌握其内部实现原理,有助于在使用时更加灵活和高效地进行开发。同时,也为以后解决一些特殊问题提供了更多的思路和方法。 ### 回答3: MyBatis是一个优秀的持久层框架,学习源码有助于理解其底层原理和设计思想。 首先,可以从MyBatis的入口开始学习,即SqlSessionFactoryBuilder类。该类负责解析配置文件、创建Configuration对象,并通过Configuration对象创建SqlSessionFactory实例。 接下来,可以学习Configuration类,该类负责管理整个MyBatis的配置信息。其中括了数据库连接信息、映射文件信息、缓存信息等。在该类内部,会调用XMLMapperBuilder类解析映射文件,在解析映射文件过程中,会创建MappedStatement对象,该对象表示一条SQL语句的映射信息。 学习MappedStatement对象可以了解MyBatis的SQL语句解析过程。该对象含了SQL语句的相关信息,括参数映射关系、返回结果映射关系等。在执行SQL语句时,会使用ParameterHandler类处理参数,通过ResultSetHandler类处理查询结果。 同时,学习到Executor接口及其实现类,可以了解MyBatis的执行过程。Executor负责执行SQL语句,其中括了写操作的update方法和读操作的query方法。在执行过程中,会通过StatementHandler类创建PreparedStatement对象,并通过ResultSetHandler类处理执行结果。 最后,还可以学习MyBatis的事务处理和缓存机制。Transaction接口及其实现类负责事务管理,通过JDBC的事务机制实现了事务的提交和回滚。而Cache接口及其实现类负责缓存查询结果,在查询时会先从缓存中查找结果。 总结来说,通过学习MyBatis源码可以深入理解其底层原理和设计思想。从SqlSessionFactory的创建开始,到Configuration的配置解析、MappedStatement的创建,再到Executor的执行过程和Transaction的事务管理,以及Cache的缓存机制,逐步掌握MyBatis的各个组件和它们之间的交互关系。这对于我们使用MyBatis开发项目,解决问题和优化性能都具有积极的意义。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值