我们在使用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<T> type):这个方法我认为是创建结果集的方法,但它其实是直接调用了第二个create方法,只不过后两个参数传的是null,所以直接看下面的方法;

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

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

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

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

6⃣️isCollection(Class<T> 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
3 </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;

按照我的理解,mybatis创建结果集的过程是这样的:首先根据sql执行的结果,即返回条数判断是不是一个集合类型,然后将每条结果实例化成一个对象,如果前面判断返回的是多条,则将这些对象设置到一个List中,否则就是一个单独的对象,然后根据我们在映射器接口中定义的返回类型进行适配,如果接口定义返回List就返回List,如果接口定义返回一个单独的类型,则返回实例化的对象。后来仔细一想不对呀,这样的话如果查询结果是一条,但是我在接口中定义的返回类型是List,那岂不是冲突了?为了研究这个创建过程,我又写了两个查询接口,一个只返回一个对象,一个查询所有但返回一个对象(这个按理说是不对的,主要看看mybatis会报什么错),然后在DefaultObjectFactory中打断点进行调试,下面介绍我的实验过程:

第一步:再创建两个查询接口

1⃣️根据ID查询

 //查询接口
public Product selectById(String id);

sql配置

 <select id="selectById" parameterType="String" resultType="product">
SELECT * FROM product p WHERE p.id = #{id,jdbcType=VARCHAR}
</select>

这个是根据ID查询,返回类型是Product

2⃣️查询所有

 public Product getAllProducts();

sql配置

 <select id="getAllProducts" resultType="product">
SELECT * FROM product
</select>

这个是查询所有,但返回类型是Product,即一个对象

第二步:对三种情况打断点进行调试

在调试过程中我发现以下几点:

1、在获取SQL session对象的过程中就已经调用了DefaultObjectFactory中的setProperties方法,其实这个不难理解,毕竟在获取SQL session之前就已经加载了mybatis的配置文件,首先当然要进行配置;

2、然后会进入到DefaultObjectFactory中的isCollection方法,而传给它的参数类型就是我在映射器接口中定义的返回类型;

3、不管查询出多少条数据,也不管返回类型是什么,首先都会调用create方法创建一个ArrayList对象;

4、查询出多少条数据,就会调用多少次create方法,将一条数据封装成一个类并进行实例化;

以上是三种不同的查询结果都会执行的步骤,其他的中间还有很复杂的创建过程,根据我有限的理解,推断出mybatis在创建结果集的过程如下:

1⃣️先判断接口返回的类型是一个单独的类还是一个集合;

2⃣️创建一个ArrayList对象;

3⃣️将每个查询结果实例化成一个对象,然后设置到ArrayList对象中;

4⃣️如果在第一步中接口返回的是单独的类,此时查看第三步封装的结果,如果只有一条数据,则获取对象并返回;如果有多条,则报错;

如果在第一步中接口返回的是一个集合,则直接返回第三步封装的结果;

其实所谓结果集,必定是个集合,所以才会有创建ArrayList并将实例化的对象添加在其中这一步,至于怎么返回就要看接口中的定义了,如果mybatis能根据接口定义返回指定类型,则没有任何问题,否则就会报错,就像上面的第三种情况,封装的结果集是个含多个product对象的集合,但是接口中定义只要一个对象,那这时候mybatis就不知道给哪个合适,所以只能报错了。

以上就是我对objectFactory这个配置项的理解,不正确的地方希望看到文章的你能给予指正,谢了~~




MyBatis配置文件(五)--objectFactory对象工厂的更多相关文章

  1. MyBatis学习 之 五、MyBatis配置文件

    在定义sqlSessionFactory时需要指定MyBatis主配置文件: <bean id="sqlSessionFactory" class="org.myb ...

  2. MyBatis学习(四)、MyBatis配置文件

    四.MyBatis主配置文件 在定义sqlSessionFactory时需要指定MyBatis主配置文件: <bean id="sqlSessionFactory" clas ...

  3. MyBatis学习 之 四、MyBatis配置文件

    目录(?)[-] 四MyBatis主配置文件 properties属性 settings设置 typeAliases类型别名 typeHandlers类型句柄 ObjectFactory对象工厂 pl ...

  4. 【Mybatis】MyBatis配置文件的使用(二)

    本例在[Mybatis]MyBatis快速入门(一)基础上继续学习XML映射配置文件 MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置(settings)和属性(properti ...

  5. 02.MyBatis配置文件详解

        MyBatis入参考文档:http://mybatis.org/mybatis-3/zh/  1.properties 属性 1.在MyBatis配置文件中引用属性文件     MyBatis ...

  6. MyBatis配置文件之概述

    MyBatis配置文件所有元素 <?xml version="1.0" encoding="UTF-8"?> <configuration&g ...

  7. 深入Mybatis配置文件

    Configuration是干嘛的 Configuration就像是Mybatis的总管,Mybatis的所有配置信息都存放在这里,此外,它还提供了设置这些配置信息的方法.Configuration可 ...

  8. Mybatis-学习笔记(2)Mybatis配置文件

      3>typeAliases:类型别名.2种指定方式. 1>给某个类起个别名 <typeAliases> <typeAlias type="com.lfy.b ...

  9. Mybatis框架基础支持层——反射工具箱之对象工厂ObjectFactory&DefaultObjectFactory(5)

    ObjectFactory官方简介:MyBatis每次创建结果集对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成. 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认 ...

随机推荐

  1. hdu多校第一场1003 (hdu6580)Milk 背包

    题意: 有一个n*m的矩阵,左右可以随便走,但只能在每一行的中点往下走,每走一格花费时间1. 现在这个矩阵里放了k瓶牛奶,第i个牛奶喝下去需要ti时间 起点是(1,1) 对于每个i∈[1,k],问喝掉 ...

  2. 杂项-IM:IM(即时通讯),一种基于互联网的即时交流消息的业务

    ylbtech-杂项-IM:IM(即时通讯),一种基于互联网的即时交流消息的业务 即时通讯(Instant Messaging)是目前Internet上最为流行的通讯方式,各种各样的即时通讯软件也层出 ...

  3. spss logistic回归分析结果如何分析

    spss logistic回归分析结果如何分析 如何用spss17.0进行二元和多元logistic回归分析 一.二元logistic回归分析 二元logistic回归分析的前提为因变量是可以转化为0 ...

  4. 4_2.springboot2.x配置之springmvc自动配置

    1.Spring MVC auto-configuration 查看官方文档: Spring Boot为Spring MVC提供了自动配置,适用于大多数应用程序. 自动配置在Spring的默认值之上添 ...

  5. 07_mybatis延迟加载

    1. 延迟加载 ​ resultMap可以实现高级映射(使用association.collection实现一对一及一对多映射),association.collection具备延迟加载功能. 需求: ...

  6. <selenium>selenium基础操作

    from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.c ...

  7. SQL语句的四种连接

    SQL的四种连接查询 内连接 inner join 或者 join 外连接 左连接   left join 或者 left outer join 右连接  right join 或者 right ou ...

  8. JS规则 自加一,自减一 ( ++和- -) 【mynum = mynum + 1;//等同于mynum++;】

    自加一,自减一 ( ++和- -) 算术操作符除了(+.-.*./)外,还有两个非常常用的操作符,自加一"++":自减一"--".首先来看一个例子: mynum ...

  9. 【笔记篇】C#笔记3

    笔记目录:http://blog.csdn.net/enzymii/article/details/77169928 C#的接口有点意思,我们说过可以用来多重继承.. using System; na ...

  10. Redis学习目录

    目录   持续更新... Redis简介 Redis安装及基本配置 Redis持久化 Redis开发及管理实战 Redis高可用及集群 Redis多API开发