MyBatis六objectFactory对象工厂

我们在使用MyBatis执行查询语句的时候,通常都会有一个返回类型,这个是在mapper文件中给sql增加一个resultType(或resultMap)属性进行控制。resultType和resultMap都能控制返回类型,只要定义了这个配置就能自动返回我想要的结果,于是我就很纳闷这个自动过程的实现原理,想必大多数人刚开始的时候应该也有和我一样的困惑和好奇,那么今天我就把自己的研究分享一下。在JDBC中查询的结果会保存在一个结果集中,其实MyBatis也是这个原理,只不过MyBatis在创建结果集的时候,会使用其定义的对象工厂DefaultObjectFactory来完成对应的工作,下面来看一下其源代码

一、默认对象工厂DefaultObjectFactory.java

/**
 * @author Clinton Begin
 */
public class DefaultObjectFactory implements ObjectFactory, Serializable {

  private static final long serialVersionUID = -8855120656740914948L;

  @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);
  }

  @Override
  public void setProperties(Properties properties) {
    // no props for default
  }

  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) {
      StringBuilder argTypes = new StringBuilder();
      if (constructorArgTypes != null && !constructorArgTypes.isEmpty()) {
        for (Class<?> argType : constructorArgTypes) {
          argTypes.append(argType.getSimpleName());
          argTypes.append(",");
        }
        argTypes.deleteCharAt(argTypes.length() - 1); // remove trailing ,
      }
      StringBuilder argValues = new StringBuilder();
      if (constructorArgs != null && !constructorArgs.isEmpty()) {
        for (Object argValue : constructorArgs) {
          argValues.append(String.valueOf(argValue));
          argValues.append(",");
        }
        argValues.deleteCharAt(argValues.length() - 1); // remove trailing ,
      }
      throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + e, e);
    }
  }

  protected Class<?> resolveInterface(Class<?> type) {
    Class<?> classToCreate;
    if (type == List.class || type == Collection.class || type == Iterable.class) {
      classToCreate = ArrayList.class;
    } else if (type == Map.class) {
      classToCreate = HashMap.class;
    } else if (type == SortedSet.class) { // issue #510 Collections Support
      classToCreate = TreeSet.class;
    } else if (type == Set.class) {
      classToCreate = HashSet.class;
    } else {
      classToCreate = type;
    }
    return classToCreate;
  }

  @Override
  public <T> boolean isCollection(Class<T> type) {
    return Collection.class.isAssignableFrom(type);
  }

}

在这个类中有6个方法:

1⃣️create(Class type):这个方法我认为是创建结果集的方法,但它其实是直接调用了第二个create方法,只不过后两个参数传的是null,所以直接看下面的方法;

2⃣️create(Class type, List<Class<?>> constructorArgTypes, List constructorArgs):这个方法中传了三个参数,分别应该是返回的结果集类型、此类构造函数参数类型、此类构造函数参数值。从它的内部实现来看,首先调用了下面的resolveInterface方法获取返回类型,其次调用instantiateClass方法实例化出我们所需的结果集;

3⃣️setProperties(Properties properties):这个方法是用来设置一些配置信息,比如我们给objectFactory定义一个property子元素时,就是通过这个方法进行配置的;

4⃣️instantiateClass(Class type, List<Class<?>> constructorArgTypes, List constructorArgs):此方法是用来实例化一个类,需要实例化的类型是通过下面的resolveInterface方法决定,从内部实现来看,这个实例化过程是通过反射实现的;

5⃣️resolveInterface(Class<?> type):此方法是用来对集合类型进行处理,即如果我们定义一个resultType为集合类型,那么它就会根据这个类型决定出即将创建的结果集类型;

6⃣️isCollection(Class type):这个方法是用来判断我们配置的类型是不是一个集合。比如如果返回多条数据,但是我们配置resultType是个普通类,那么在执行过程中就会报错;

以上就是MyBatis默认objectFactory中的具体实现,通过它来创建我们配置的结果集,一般情况下都会使用默认的对象工厂,但是我们也可以自定义一个,只要继承DefaultObjectFactory.java即可。

二、自定义一个对象工厂MyObjectFactory.java

package com.daily.objectfactory;

import java.util.List;
import java.util.Properties;

import org.apache.ibatis.reflection.factory.DefaultObjectFactory;

public class MyObjectFactory extends DefaultObjectFactory{

    private static final long serialVersionUID = 1L;
    
    @Override
    public void setProperties(Properties properties) {
        super.setProperties(properties);
        System.out.println("初始化参数:["+properties.toString()+"]");
    }
    
    
    @Override
    public <T> T create(Class<T> type,List<Class<?>> constructorArgTypes,List<Object> constructorArgs) {
        T result = super.create(type, constructorArgTypes, constructorArgs);
        System.out.println("创建对象方法:"+result.getClass().getSimpleName());
        return result;
    }
    
    @Override
    public <T> boolean isCollection(Class<T> type) {
        return super.isCollection(type);
    }

}

其实我们自定义的时候只要重写其中的create方法就可以,至于选择哪个,其实两个都行,因为第一个方法内部还是调用了第二个,我在这里调用了第二个,还重写了setProperties方法,用来查看其作用。

下面据一个例子来查看怎么使用自定义的objectFactory

第一步:配置

在mybatis-config.xml中做如下配置,并自定义一个property子元素,设置其name和value

 <!--对象工厂 -->
     <objectFactory type="com.daily.objectfactory.MyObjectFactory">
         <property name="prop1" value="value1"/>
     </objectFactory>

第二步:配置查询映射器接口和sql

接口:

 //获取所有的产品
 public List<Product> getAllProduct();

sql:

 <select id="getAllProduct"resultMap="BaseResultMap">
         SELECT * FROM product
 </select>

在上面的代码中,接口返回的是一个List,而其中的元素是Product类,sql定义返回的结果集是一个BaseResultMap,其定义如下:

 <resultMap id="BaseResultMap" type="com.daily.pojo.Product">
         <id column="id" jdbcType="VARCHAR" property="id" />
         <result column="product_name" jdbcType="VARCHAR" property="productName" />
         <result column="product_price" jdbcType="VARCHAR" property="productPrice" />
         <result column="product_type" jdbcType="VARCHAR" property="productType" />
 </resultMap>

它们都在同一个mapper.xml文件中.

第三步:打印查询结果

// 获取商品列表
    public void getProductList() {
        List<Product> productList = productService.getProductList();
        for (Product product : productList) {
            System.out.println(product.getProductName());
        }
    }

获取到商品列表之后进行遍历并打印名称

第四步:查询结果

初始化参数:[{prop1=value1}]
创建对象方法:ArrayList
创建对象方法:Product
创建对象方法:Product
创建对象方法:Product
创建对象方法:Product
衬衫
卫衣
连衣裙
连衣裙

从执行结果来看,创建结果集的步骤如下:

1⃣️首先进行初始化参数,也就是根据objectFactory中的配置;

2⃣️然后创建了一个ArrayList对象;

3⃣️再创建四个Product对象(注意:查询结果有几条就创建几个 );

4⃣️将四个对象设置到ArrayList中封装成我们需要的返回类型List;

posted @ 2022-09-29 14:12  leepandar  阅读(28)  评论(0编辑  收藏  举报