Brief Introduction and Configuration of Mybatis Series 5-TypeHandler (Mybatis Source)

Note: This article is reproduced from Nan Ke Meng

In the last article, "Deep and Simple Mybatis Series (4) - - Configuration Detailed Type Aliases Aliases Aliases Aliases Aliases Aliases Aliases Aliases Aliases Aliases Aliases Aliases Aliases Aliases Aliases Alias (Mybatis Source Chapter)" introduced the use of aliases in mybatis and its source code This article will introduce TypeHandler and briefly analyze its source code.

What is the TypeHandler in Mybatis?

Whether MyBatis sets a parameter in a Prepared Statement or fetches a value from a result set, it uses a type processor to convert the acquired value into a Java type in an appropriate way. Mybatis implements many TypeHandlers for us by default. When we do not configure the specified TypeHandler, Mybatis defaults to select the appropriate TypeHandler processing for us, depending on the parameters or the results returned.

So, what TypeHandler does Mybatis implement for us? How do we customize a TypeHandler? These will be seen in the source code of Mybatis in the next section.

Before looking at the source code, or as before, first look at how to configure it?

Configure TypeHandler

<configuration>
    <typeHandlers>
      <!-- 
          When configuring the package, mybatis scans the TypeHandler for the package to be configured.
          <package name="com.dy.demo"/>
       -->
      
      <! -- The handler attribute directly configures the TypeHandler we want to specify - >.
      <typeHandler handler=""/>
      
      <!-- 
          JavaType configures java types, such as String, if accompanied by javaType, 
    Then the specified typeHandler only works on the specified type 
    -->
      <typeHandler javaType="" handler=""/>
      
      <!-- 
          JdbcType configures database basic data types, such as varchar, with jdbcType,
The specified typeHandler then acts only on the specified type  
    -->
      <typeHandler jdbcType="" handler=""/>
      
      <! - Both can also be configured - >.
      <typeHandler javaType="" jdbcType="" handler=""/>
      
  </typeHandlers>
  
  ......
  
</configuration>

Here's a brief introduction to TypeHandler. Now let's look at the source code of TypeHandler in mybatis.

The old rule begins with an analysis of xml:

TypeeHandlers Node Source

/**
 * Resolving typeHandlers nodes
 */
private void typeHandlerElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        //When the child node is package, get the value of its name attribute, and then scan the custom type handler under package automatically.
        if ("package".equals(child.getName())) {
          String typeHandlerPackage = child.getStringAttribute("name");
          typeHandlerRegistry.register(typeHandlerPackage);
        } else {
          /**
          * When the child node is typeHandler, you can specify the javaType attribute. 
          * You can also specify jdbcType, or both
          * javaType Is the specified java type
          * jdbcType Is the specified jdbc type (database type: such as varchar)
          */
          String javaTypeName = child.getStringAttribute("javaType");
          String jdbcTypeName = child.getStringAttribute("jdbcType");
          //Handler is the type handler we configure
          String handlerTypeName = child.getStringAttribute("handler");

          //The resolveClass method is the way to handle aliases in the TypeAlias Registry described in our previous article.
          Class<?> javaTypeClass = resolveClass(javaTypeName);
          //JdbcType is an enumeration type. The resolveJdbcType method is to get the value of the enumeration type.
          JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
          Class<?> typeHandlerClass = resolveClass(handlerTypeName);

          //Register typeHandler, which is managed through the Class TypeHandler Registry
          if (javaTypeClass != null) {
            if (jdbcType == null) {
              typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
            } else {
              typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
            }
          } else {
            typeHandlerRegistry.register(typeHandlerClass);
          }
        }
      }
    }
}

Next, look at the management registration class of TypeHandler:

TypeHandler Registry source code

/**
 * typeHandler Registration Management Class
 */
public final class TypeHandlerRegistry {

  //As soon as the source code comes up, without saying a word, several large HashMap s appear, which is similar to the registration of type Aliases mentioned last time.

  //Basic Data Types and Their Packaging Classes
  private static final Map<Class<?>, Class<?>> reversePrimitiveMap 
    = new HashMap<Class<?>, Class<?>>() {
    private static final long serialVersionUID = 1L;
    {
      put(Byte.class, byte.class);
      put(Short.class, short.class);
      put(Integer.class, int.class);
      put(Long.class, long.class);
      put(Float.class, float.class);
      put(Double.class, double.class);
      put(Boolean.class, boolean.class);
      put(Character.class, char.class);
    }
  };

  //Needless to say, these MAP s know what's stored, the benefits of naming
  private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP 
        = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);

  private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP 
        = new HashMap<Type, Map<JdbcType, TypeHandler<?>>>();

  private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER 
        = new UnknownTypeHandler(this);

  private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP 
        = new HashMap<Class<?>, TypeHandler<?>>();

  //Like the typeAliases mentioned in the previous article, mybatis has registered us with a number of typeHandler s by default.
  //As follows
  public TypeHandlerRegistry() {
    register(Boolean.class, new BooleanTypeHandler());
    register(boolean.class, new BooleanTypeHandler());
    register(JdbcType.BOOLEAN, new BooleanTypeHandler());
    register(JdbcType.BIT, new BooleanTypeHandler());

    register(Byte.class, new ByteTypeHandler());
    register(byte.class, new ByteTypeHandler());
    register(JdbcType.TINYINT, new ByteTypeHandler());

    register(Short.class, new ShortTypeHandler());
    register(short.class, new ShortTypeHandler());
    register(JdbcType.SMALLINT, new ShortTypeHandler());

    register(Integer.class, new IntegerTypeHandler());
    register(int.class, new IntegerTypeHandler());
    register(JdbcType.INTEGER, new IntegerTypeHandler());

    register(Long.class, new LongTypeHandler());
    register(long.class, new LongTypeHandler());

    register(Float.class, new FloatTypeHandler());
    register(float.class, new FloatTypeHandler());
    register(JdbcType.FLOAT, new FloatTypeHandler());

    register(Double.class, new DoubleTypeHandler());
    register(double.class, new DoubleTypeHandler());
    register(JdbcType.DOUBLE, new DoubleTypeHandler());

    register(String.class, new StringTypeHandler());
    register(String.class, JdbcType.CHAR, new StringTypeHandler());
    register(String.class, JdbcType.CLOB, new ClobTypeHandler());
    register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
    register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler());
    register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
    register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
    register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
    register(JdbcType.CHAR, new StringTypeHandler());
    register(JdbcType.VARCHAR, new StringTypeHandler());
    register(JdbcType.CLOB, new ClobTypeHandler());
    register(JdbcType.LONGVARCHAR, new ClobTypeHandler());
    register(JdbcType.NVARCHAR, new NStringTypeHandler());
    register(JdbcType.NCHAR, new NStringTypeHandler());
    register(JdbcType.NCLOB, new NClobTypeHandler());

    register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
    register(JdbcType.ARRAY, new ArrayTypeHandler());

    register(BigInteger.class, new BigIntegerTypeHandler());
    register(JdbcType.BIGINT, new LongTypeHandler());

    register(BigDecimal.class, new BigDecimalTypeHandler());
    register(JdbcType.REAL, new BigDecimalTypeHandler());
    register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
    register(JdbcType.NUMERIC, new BigDecimalTypeHandler());

    register(Byte[].class, new ByteObjectArrayTypeHandler());
    register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
    register(Byte[].class, JdbcType.LONGVARBINARY,new BlobByteObjectArrayTypeHandler());
    register(byte[].class, new ByteArrayTypeHandler());
    register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
    register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
    register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
    register(JdbcType.BLOB, new BlobTypeHandler());

    register(Object.class, UNKNOWN_TYPE_HANDLER);
    register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
    register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);

    register(Date.class, new DateTypeHandler());
    register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
    register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
    register(JdbcType.TIMESTAMP, new DateTypeHandler());
    register(JdbcType.DATE, new DateOnlyTypeHandler());
    register(JdbcType.TIME, new TimeOnlyTypeHandler());

    register(java.sql.Date.class, new SqlDateTypeHandler());
    register(java.sql.Time.class, new SqlTimeTypeHandler());
    register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());

    // issue #273
    register(Character.class, new CharacterTypeHandler());
    register(char.class, new CharacterTypeHandler());
  }

  public boolean hasTypeHandler(Class<?> javaType) {
    return hasTypeHandler(javaType, null);
  }

  public boolean hasTypeHandler(TypeReference<?> javaTypeReference) {
    return hasTypeHandler(javaTypeReference, null);
  }

  public boolean hasTypeHandler(Class<?> javaType, JdbcType jdbcType) {
    return javaType != null && getTypeHandler((Type) javaType, jdbcType) != null;
  }

  public boolean hasTypeHandler(TypeReference<?> javaTypeReference,
        JdbcType jdbcType) {
    return javaTypeReference != null 
        && getTypeHandler(javaTypeReference, jdbcType) != null;
  }

  public TypeHandler<?> getMappingTypeHandler(
        Class<? extends TypeHandler<?>> handlerType) {
    return ALL_TYPE_HANDLERS_MAP.get(handlerType);
  }

  public <T> TypeHandler<T> getTypeHandler(Class<T> type) {
    return getTypeHandler((Type) type, null);
  }

  public <T> TypeHandler<T> getTypeHandler(TypeReference<T> javaTypeReference) {
    return getTypeHandler(javaTypeReference, null);
  }

  public TypeHandler<?> getTypeHandler(JdbcType jdbcType) {
    return JDBC_TYPE_HANDLER_MAP.get(jdbcType);
  }

  public <T> TypeHandler<T> getTypeHandler(Class<T> type, JdbcType jdbcType) {
    return getTypeHandler((Type) type, jdbcType);
  }

  public <T> TypeHandler<T> getTypeHandler(
        TypeReference<T> javaTypeReference, JdbcType jdbcType) {
    return getTypeHandler(javaTypeReference.getRawType(), jdbcType);
  }

  private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(type);
    TypeHandler<?> handler = null;
    if (jdbcHandlerMap != null) {
      handler = jdbcHandlerMap.get(jdbcType);
      if (handler == null) {
        handler = jdbcHandlerMap.get(null);
      }
    }
    if (handler == null 
    && type != null 
    && type instanceof Class 
    && Enum.class.isAssignableFrom((Class<?>) type)) {
      handler = new EnumTypeHandler((Class<?>) type);
    }
    @SuppressWarnings("unchecked")
    // type drives generics here
    TypeHandler<T> returned = (TypeHandler<T>) handler;
    return returned;
  }

  public TypeHandler<Object> getUnknownTypeHandler() {
    return UNKNOWN_TYPE_HANDLER;
  }

  public void register(JdbcType jdbcType, TypeHandler<?> handler) {
    JDBC_TYPE_HANDLER_MAP.put(jdbcType, handler);
  }

  //
  // REGISTER INSTANCE
  //

  /**
   * Only typeHandler is configured, and no jdbcType or javaType is configured.
   */
  @SuppressWarnings("unchecked")
  public <T> void register(TypeHandler<T> typeHandler) {
    boolean mappedTypeFound = false;
    //When customizing type handler, you can add annotations to MappedTypes to specify the associated javaType
    //Therefore, you need to scan the MappedTypes annotations here
    MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);
    if (mappedTypes != null) {
      for (Class<?> handledType : mappedTypes.value()) {
        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
        */
      }
    }
    if (!mappedTypeFound) {
      register((Class<T>) null, typeHandler);
    }
  }

  /**
   * TypeeHandlerhe and javaType are configured
   */
  public <T> void register(Class<T> javaType, TypeHandler<? extends T> typeHandler) {
    register((Type) javaType, typeHandler);
  }

  private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
    //Scanning annotations MappedJdbcTypes
    MappedJdbcTypes mappedJdbcTypes 
            = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
    if (mappedJdbcTypes != null) {
      for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
        register(javaType, handledJdbcType, typeHandler);
      }
      if (mappedJdbcTypes.includeNullJdbcType()) {
        register(javaType, null, typeHandler);
      }
    } else {
      register(javaType, null, typeHandler);
    }
  }

  public <T> void register(TypeReference<T> javaTypeReference, 
        TypeHandler<? extends T> handler) {
    register(javaTypeReference.getRawType(), handler);
  }

  /**
   * typeHandlerhe,javaType,jdbcType They are all configured.
   */
  public <T> void register(Class<T> type, 
                JdbcType jdbcType, 
                TypeHandler<? extends T> handler) {
    register((Type) type, jdbcType, handler);
  }

  /**
   * The Core Method of Registering TypeeHandler
   * It's just adding data to Map.
   */
  private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
    if (javaType != null) {
      Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
      if (map == null) {
        map = new HashMap<JdbcType, TypeHandler<?>>();
        TYPE_HANDLER_MAP.put(javaType, map);
      }
      map.put(jdbcType, handler);
      if (reversePrimitiveMap.containsKey(javaType)) {
        register(reversePrimitiveMap.get(javaType), jdbcType, handler);
      }
    }
    ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
  }

  //
  // REGISTER CLASS
  //

  // Only handler type

  public void register(Class<?> typeHandlerClass) {
    boolean mappedTypeFound = false;
    MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
    if (mappedTypes != null) {
      for (Class<?> javaTypeClass : mappedTypes.value()) {
        register(javaTypeClass, typeHandlerClass);
        mappedTypeFound = true;
      }
    }
    if (!mappedTypeFound) {
      register(getInstance(null, typeHandlerClass));
    }
  }

  // java type + handler type

  public void register(Class<?> javaTypeClass, Class<?> typeHandlerClass) {
    register(javaTypeClass, getInstance(javaTypeClass, typeHandlerClass));
  }

  // java type + jdbc type + handler type

  public void register(Class<?> javaTypeClass, 
            JdbcType jdbcType, 
            Class<?> typeHandlerClass) {
    register(javaTypeClass, jdbcType, getInstance(javaTypeClass, typeHandlerClass));
  }

  // Construct a handler (used also from Builders)

  @SuppressWarnings("unchecked")
  public <T> TypeHandler<T> getInstance(Class<?> javaTypeClass, 
                    Class<?> typeHandlerClass) {
    if (javaTypeClass != null) {
      try {
        Constructor<?> c = typeHandlerClass.getConstructor(Class.class);
        return (TypeHandler<T>) c.newInstance(javaTypeClass);
      } catch (NoSuchMethodException ignored) {
        // ignored
      } catch (Exception e) {
        throw new TypeException("Failed invoking constructor for handler " 
        + typeHandlerClass, e);
      }
    }
    try {
      Constructor<?> c = typeHandlerClass.getConstructor();
      return (TypeHandler<T>) c.newInstance();
    } catch (Exception e) {
      throw new TypeException("Unable to find a usable constructor for " 
            + typeHandlerClass, e);
    }
  }

 
  /**
   * Scan the custom type hander according to the specified pacakge and register
   */
  public void register(String packageName) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    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())) {
        register(type);
      }
    }
  }
  
  // get information
  
  /**
   * All registered typeHandler s can be retrieved through the configuration object
   */
  public Collection<TypeHandler<?>> getTypeHandlers() {
    return Collections.unmodifiableCollection(ALL_TYPE_HANDLERS_MAP.values());
  }
  
}

As you can see from the source code, mybatis implements so many TypeHandlers for us. Open a TypeHandler casually and look at its source code. You can see that it inherits from an abstract class: BaseTypeHandler. So can we also implement a custom TypeHandler by inheriting BaseTypeHandler? The answer is yes, so let's show you now. Define TypeHandler:

Custom TypeHandler

@MappedJdbcTypes(JdbcType.VARCHAR)  
/**
* If you do not specify jdbcType with annotations here, you can specify it in the configuration file by using the "jdbcType" attribute.
* Similarly, javaType can also be specified by @MappedTypes
*/
public class ExampleTypeHandler 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);
  }
}

Then it's time to configure our custom TypeHandler:

<configuration>
  <typeHandlers>
      <!-- 
    Since the custom TypeHandler has specified jdbcType through annotations at the time of definition, there is no need to configure jdbcType here.
      -->
      <typeHandler handler="ExampleTypeHandler"/>
  </typeHandlers>
  
  ......
  
</configuration>

That is to say, when we customize TypeHandler, we can specify jdbcType through @MappedJdbcTypes in TypeHandler and javaType through @MappedTypes. If we do not specify it with annotations, then we need to configure it in the configuration file.

Well, that's the end of this article.

Keywords: Python Mybatis Java Attribute SQL

Added by phpete2 on Fri, 24 May 2019 23:39:16 +0300