Object Factory for Mybatis

Keywords: Programming Mybatis Apache xml Java

Preface

Above How Mybatis XML maps to methods When you talk about result mapping in Mybatis, you need to create objects and assign values to their attributes. To create objects, you use Mybatis's built-in object factory class, DefaultObjectFactory. Of course, Mybatis also provides an extension mechanism that allows users to implement their own object factory.

Object Factory

The logic associated with result mapping is described above in the DefaultResultSetHandler processor, and the following highlights how result objects are created:

  private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)
      throws SQLException {
    final Class<?> resultType = resultMap.getType();
    final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
    final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
    if (hasTypeHandlerForResultObject(rsw, resultType)) {
      return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
    } else if (!constructorMappings.isEmpty()) {
      return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
    } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
      return objectFactory.create(resultType);
    } else if (shouldApplyAutomaticMappings(resultMap, false)) {
      return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs, columnPrefix);
    }
    throw new ExecutorException("Do not know how to create an instance of " + resultType);
  }

This is the core method of creating result objects, which are divided into four situations:
1. The result object Mybatis itself provides a processor, xxTypeHandler, which is more implemented in the path of org.apache.ibatis.type. In this case, the result return value can be obtained directly from Resultset, which can be regarded as the native type.
2. Constructors are specified in the result map, and the default constructor is not required;
3. The result object is an interface or the result object has a default constructor;
4. None of the above will check if automatic mapping is configured and turn on by default.
If none of the above is satisfied, an exception will be thrown directly. The following is a detailed analysis of when all four types are executed.

1. Native Types

Indicates that the result set is a native type, such as string type, and the associated xml configuration, such as:

    <select id="selectBlog" parameterType="hashmap" resultType="string">
        select title from blog where id = #{id} and
        author=#{author,javaType=string}
    </select>

Returning directly to the native type string, Mybatis provides the StringTypeHandler processor:

private Object createPrimitiveResultObject(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
    final Class<?> resultType = resultMap.getType();
    final String columnName;
    if (!resultMap.getResultMappings().isEmpty()) {
      final List<ResultMapping> resultMappingList = resultMap.getResultMappings();
      final ResultMapping mapping = resultMappingList.get(0);
      columnName = prependPrefix(mapping.getColumn(), columnPrefix);
    } else {
      columnName = rsw.getColumnNames().get(0);
    }
    final TypeHandler<?> typeHandler = rsw.getTypeHandler(resultType, columnName);
    return typeHandler.getResult(rsw.getResultSet(), columnName);
  }

First get the field name, then get the processor by return type, and finally get the result directly from the ResultSet. There are two cases when getting the field name: directly configuring the resultType and configuring the resultMap. In either case, if you configure more than one mapping field, only get the first one, for example:

select id,title from blog where id = #{id} and author=#{author,javaType=string}

This only returns the value of id and is converted to string type.

2. Specify a constructor

Constructor parameters are specified in the resultmap, such as the following example:

    <resultMap id="blogResultMap" type="blog">
        <constructor>
            <idArg column="id" javaType="long"/>
        </constructor>
        <result property="title" column="title" />
    </resultMap>

As shown above, a constructor with id as a parameter is specified, in which case the default parameterless constructor is not used when creating objects through the object factory. Instead, a constructor with an id parameter is used, with some code as follows:

objectFactory.create(resultType, constructorArgTypes, constructorArgs)

The three parameters are the object type returned, the constructor parameter type, and the constructor parameter value.

3. Default Constructor

There is no need to specify a constructor parameter type and a constructor parameter value, provided the class provides a default constructor and calls the create method directly:

objectFactory.create(resultType)

The only difference from specifying a constructor is that both the constructor parameter type and the constructor parameter value are null; here's a specific look at the default implementation of the object factory:

@Override
  public <T> T create(Class<T> type) {
    return create(type, null, null);
  }

  @SuppressWarnings("unchecked")
  @Override
  public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    Class<?> classToCreate = resolveInterface(type);
    // we know types are assignable
    return (T) instantiateClass(classToCreate, constructorArgTypes, constructorArgs);
  }
  
private  <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    try {
      Constructor<T> constructor;
      if (constructorArgTypes == null || constructorArgs == null) {
        constructor = type.getDeclaredConstructor();
        if (!constructor.isAccessible()) {
          constructor.setAccessible(true);
        }
        return constructor.newInstance();
      }
      constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[constructorArgTypes.size()]));
      if (!constructor.isAccessible()) {
        constructor.setAccessible(true);
      }
      return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]));
    } catch (Exception e) {
      ...ellipsis...
    }
  }

Constructors with and without parameters are provided, and default constructors are called directly when there are no parameters; constructors with parameter types are obtained when there are specified constructor parameter types; of course, constructors with corresponding parameters need to be provided in the corresponding classes, or errors will occur; after obtaining the constructors, class objects are created directly through newInstance;

4. Automatic Mapping

Mybatis automatically maps if neither the default constructor nor the constructor that specifies the construction parameters are provided; the automatic mapping has a switch, which is turned on by default, and the autoMapping switch can be configured in configuration s and resultMap s; prepare the following examples to see how the mapping works:

    <resultMap id="blogResultMap" type="blog" autoMapping="true">
        <result property="id" column="id" />
        <result property="title" column="title" />
        <result property="content" column="content"/>
    </resultMap>
    <select id="selectBlogMap" parameterType="hashmap" resultMap="blogResultMap">
        select id,title from blog where id = #{id} and
        author=#{author,javaType=string}
    </select>

The blog class provides a constructor with id as a parameter, so there is no default constructor; at this point Mybatis will find out if there is an id in the blog, and the constructor for the title class parameter. If there is one, it will get the constructor to create the object, and if it does not, it will get an error as follows:

Caused by: org.apache.ibatis.executor.ExecutorException: No constructor found in com.mybatis.vo.Blog matching [java.lang.Long, java.lang.String]

5. Unable to create object

If the fourth case is not satisfied, you can configure autoMapping="false", then Mybatis throws the object directly and cannot be created, with the following exception:

Caused by: org.apache.ibatis.executor.ExecutorException: Do not know how to create an instance of class com.mybatis.vo.Blog

Custom Object Factory

It's also easy to implement your own object factory, either by implementing the interface ObjectFactory or by overloading the DefaultObjectFactory. To make it easier for us to directly overload the DefaultObjectFactory and to implement a default value if the object you want to instantiate is a Blog, if no author is specified, as follows:

public class MyObjectFactory extends DefaultObjectFactory {

    private static final long serialVersionUID = 1L;

    @Override
    public <T> T create(Class<T> type) {
        System.out.println("create:" + type);
         if (type.equals(Blog.class)){
             Blog blog = (Blog)super.create(type);
             blog.setAuthor("ksfzhaohui");
             return (T) blog;
         }
        return super.create(type);
    }
    
    ...ellipsis...
}

The implementation is also simple, simply determine the specified type as Blog in the create method, call the parent class's create method to create the object, and set the default value; of course, you also need to configure a custom object factory in the configuration to take effect:

    <objectFactory type="com.mybatis.myObjectFactory.MyObjectFactory"> 
            <property name="name" value="MyObjectFactory"/> 
    </objectFactory>

summary

This article focuses on the default object factory and the five cases in which the object is created: the native type, the constructor specified, the class with the default constructor, the use of automatic mapping, and the treatment that does not satisfy the first four cases. Finally, a simple trial of a custom object factory is made to assign default attribute values to the specified class object.

Sample Code Address

Github

Posted by spider on Sun, 10 Nov 2019 17:52:52 -0800