preface

  • Mybatis database has its own data type, Java has its own data type, so Mybatis is how to put the type of database and Java data type corresponding to it?

  • This article will talk about the black box TypeHandler(type processor) in Mybatis, it is not too much to say that it is a black box, always silently dedicated, but unknown.

Environment configuration

  • Everything in this article is based onMybatis3.5andSpringBoot - 2.3.3. RELEASE.

What is TypeHandler?

  • As the name implies, the type processor will convert the input parameter and result to the required type. Mybatis has many built-in type processors, which are enough to be used in practical development, as shown in the following figure:

  • The type handler interface is actually very simple. There are four methods in total. One method converts the Java type data of the input parameter to JDBC type, and three methods convert the return result to Java type. The source code is as follows:

public interface TypeHandler<T> {
  // Set the parameter to convert the Java type to JDBC type
  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
  // Convert the result of the query to Java type
  T getResult(ResultSet rs, String columnName) throws SQLException;

  T getResult(ResultSet rs, int columnIndex) throws SQLException;

  T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
Copy the code

How do I customize and use TypeHandler?

  • In practical application development, it is inevitable that some requirements need to be customizedTypeHandler, such as a requirement: the age of the front end ismale.female, but database-defined fields areintType (1 male, 2 female). At this point, you can customize an age type handler for conversion.

How to customize?

  • There are two ways to customize, one is implementationTypeHandlerThis interface, the other one is inheritanceBaseTypeHandlerThis is a convenient abstract class.
  • So let’s inherit directlyBaseTypeHandlerThis abstract class defines an age type handler as follows:
@MappedJdbcTypes(JdbcType.INTEGER)
@MappedTypes(String.class)
public class GenderTypeHandler extends BaseTypeHandler {

    // Sets the parameter, which converts the Java String type to the JDBC Integer type
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
        ps.setInt(i, StringUtils.equals(parameter.toString(),"Male")?1:2);
    }

    // The following three parameters convert the results of the query
    @Override
    public Object getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return rs.getInt(columnName)==1?"Male":"Female";
    }

    @Override
    public Object getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return rs.getInt(columnIndex)==1?"Male":"Female";
    }

    @Override
    public Object getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return cs.getInt(columnIndex)==1?"Male":"Female"; }}Copy the code
  • There are two comments involved here, as follows:
    • @MappedTypes : Specifies that it is associated withJavaType list. If thejavaTypeProperty, the configuration on the annotation is ignored.
    • @MappedJdbcTypes: Specifies that it is associated withJDBCType list. If thejdbcTypeProperty, the configuration on the annotation is ignored.

How do I add it to Mybatis?

  • Mybatis is easy to integrate with SpringBoot, but there are two ways to configure Mybatis, as described below.
  • The first kind of: only in the configuration fileapplication.propertiesAdd a line of configuration as follows:
Handlers - - set up a package for your custom Typehandler to be scanned into Mybatispackage=cn.cb.demo.typehandler
Copy the code
  • In fact, any framework with Springboot integration, as long as the configuration file can be configured, in the configuration class can be configured (unless there is a special custom, do not easily override automatic configuration). As follows:
@Bean("sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATOIN));
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        // Automatically convert underscores in the database to hump format
        configuration.setMapUnderscoreToCamelCase(true);
        configuration.setDefaultFetchSize(100);
        configuration.setDefaultStatementTimeout(30);
        sqlSessionFactoryBean.setConfiguration(configuration);
        // Register TypeHandler with mybatis
        GenderTypeHandler genderTypeHandler = new GenderTypeHandler();
        TypeHandler[] typeHandlers=new TypeHandler[]{genderTypeHandler};
        sqlSessionFactoryBean.setTypeHandlers(typeHandlers);
        return sqlSessionFactoryBean.getObject();
    }
Copy the code
  • The idea behind the second approach is to override the auto-configuration classMybatisAutoConfigurationMethod in.Note: Do not overwrite methods in auto-configuration classes unless you have specific customization.

How do I specify TypeHandler in an XML file?

  • The above two steps are customized and injected into Mybatis respectivelyXMLWhat about files?
  • Use is actually very simple, divided into two kinds, one isupdate, a kind ofThe query, will be introduced one by one below.
  • update: Delete needless to say, what is said here isupdateandinsertTwo, just need to be in# {}Property specified intypeHandlerThis parameter is customizedFull name of the classThe code is as follows:
<insert id="insertUser">
        insert into user_info(user_id,his_id,name,gender,password,create_time)
        values(#{userId,jdbcType=VARCHAR},#{hisId,jdbcType=VARCHAR},#{name,jdbcType=VARCHAR},
        #{gender,jdbcType=INTEGER,typeHandler=cn.cb.demo.typehandler.GenderTypeHandler},#{password,jdbcType=VARCHAR},now())
    </insert>
Copy the code
  • The query: Type processing will convert JDBC types to Java types during query, so it needs to be specifiedtypeHandlerThat need to be inresultMapSpecified in thetypeHandlerThis property has a value of zeroFull name of the classThat is as follows:
<resultMap id="userResultMap" type="cn.cb.demo.domain.UserInfo">
        <id column="id" property="id"/>
        <result column="user_id" property="userId"/>
        <result column="his_id" property="hisId"/>
            <! -- Specify typeHandler property as full class name -->
        <result column="gender" property="gender" typeHandler="cn.cb.demo.typehandler.GenderTypeHandler"/>
        <result column="name" property="name"/>
        <result column="password" property="password"/>
    </resultMap>

    <select id="selectList" resultMap="userResultMap">
        select * from user_info where status=1
        and user_id in
        <foreach collection="userIds" item="item" open="(" separator="," close=")" >
            #{item}
        </foreach>
    </select>
Copy the code

How do YOU implement TypeHandler in source code?

  • Since you know how to useTypeHandlerIn Mybatis, how does the type handler workJDBCThe type andJavaType conversion, the following will be introduced in detail from the source point of view.

How to convert an input parameter?

  • This must happen in the process of setting parameters, detailed code inPreparedStatementHandlerIn theparameterize()Method, which is the method that sets parameters. The source code is as follows:
 @Override
  public void parameterize(Statement statement) throws SQLException {
    // DefaultParameterHandler is actually called
    parameterHandler.setParameters((PreparedStatement) statement);
  }
Copy the code
  • The actual implementation isDefaultParameterHandlerIn thesetParametersThe methods are as follows:
public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    // Get the parameter mapping
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    // Go through the parameter mapping and set one by one
    if(parameterMappings ! =null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if(parameterMapping.getMode() ! = ParameterMode.OUT) { Object value; String propertyName = parameterMapping.getProperty();if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          // Get the type handler, if none exists, use the default
          TypeHandler typeHandler = parameterMapping.getTypeHandler();    
          //JdbcType
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            // Call the method in the type handler to set the parameter to convert the Java type to JDBC type
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          } catch (SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }
Copy the code
  • You can see this line of code from the source code abovetypeHandler.setParameter(ps, i + 1, value, jdbcType);Is to call the method in the type handler that sets the parameterJavaType conversion toJDBCType.

How do the results translate?

  • This process must occur in the execution of the query statement, previously also introduced Mybatis six swordsmen, one of theResultSetHandlerThis component processes the results of the query, so it must be some method in this component.
  • inPreparedStatementHandlerAfter executing the query, the call isResultSetHandlerIn thehandleResultSets()Method, the results are processed as follows:
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    / / SQL execution
    ps.execute();
    // Process the result
    return resultSetHandler.handleResultSets(ps);
  }
Copy the code
  • The ultimate inDefaultResultHandlerIn thegetPropertyMappingValue()Method is calledTypeHandlerIn thegetResult()The methods are as follows:
private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
      throws SQLException {
    if(propertyMapping.getNestedQueryId() ! =null) {
      return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
    } else if(propertyMapping.getResultSet() ! =null) {
      addPendingChildRelation(rs, metaResultObject, propertyMapping);   // TODO is that OK?
      return DEFERRED;
    } else {
      finalTypeHandler<? > typeHandler = propertyMapping.getTypeHandler();final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
      // Execute the method in typeHandler to get the result and convert it to the corresponding Java type
      returntypeHandler.getResult(rs, column); }}Copy the code

conclusion

  • The above is only a brief introduction to how the type handler is implemented in Mybatis. If some concepts are not clear, please refer to the author’s previous article as follows:
    • Mybatis source code analysis of six swordsmen
    • Mybatis source code how to read, teach you a trick!!
    • Mybatis how to execute Select statement, do you know?

conclusion

  • In this article, TypeHandler is introduced in detail in Mybatis application, custom use and from the point of view of source code analysis of the execution process of the type processor, if you feel that the author wrote a good, harvest, you may wish to pay attention to a little, share a wave.