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<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对象工厂的更多相关文章
- MyBatis学习 之 五、MyBatis配置文件
在定义sqlSessionFactory时需要指定MyBatis主配置文件: <bean id="sqlSessionFactory" class="org.myb ...
- MyBatis学习(四)、MyBatis配置文件
四.MyBatis主配置文件 在定义sqlSessionFactory时需要指定MyBatis主配置文件: <bean id="sqlSessionFactory" clas ...
- MyBatis学习 之 四、MyBatis配置文件
目录(?)[-] 四MyBatis主配置文件 properties属性 settings设置 typeAliases类型别名 typeHandlers类型句柄 ObjectFactory对象工厂 pl ...
- 【Mybatis】MyBatis配置文件的使用(二)
本例在[Mybatis]MyBatis快速入门(一)基础上继续学习XML映射配置文件 MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置(settings)和属性(properti ...
- 02.MyBatis配置文件详解
MyBatis入参考文档:http://mybatis.org/mybatis-3/zh/ 1.properties 属性 1.在MyBatis配置文件中引用属性文件 MyBatis ...
- MyBatis配置文件之概述
MyBatis配置文件所有元素 <?xml version="1.0" encoding="UTF-8"?> <configuration&g ...
- 深入Mybatis配置文件
Configuration是干嘛的 Configuration就像是Mybatis的总管,Mybatis的所有配置信息都存放在这里,此外,它还提供了设置这些配置信息的方法.Configuration可 ...
- Mybatis-学习笔记(2)Mybatis配置文件
3>typeAliases:类型别名.2种指定方式. 1>给某个类起个别名 <typeAliases> <typeAlias type="com.lfy.b ...
- Mybatis框架基础支持层——反射工具箱之对象工厂ObjectFactory&DefaultObjectFactory(5)
ObjectFactory官方简介:MyBatis每次创建结果集对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成. 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认 ...
随机推荐
- 牛客多校第五场 B generator 1 矩阵快速幂
题意: 给定$x_0,x_1,a,b,n,mod, x_i=a*x_{i-1}+b*x_{i-2}$ ,求$x_n % mod$ n最大有1e6位 题解: 矩阵快速幂. 巨大的n并不是障碍,写一个十进 ...
- P1977 出租车拼车
P1977 出租车拼车 题目背景 话说小 x 有一次去参加比赛,虽然学校离比赛地点不太远,但小 x 还是想坐 出租车去.大学城的出租车总是比较另类,有“拼车”一说,也就是说,你一个人 坐车去,还是一堆 ...
- CCPC 2019 网络赛 HDU huntian oy (杜教筛)
1005 huntian oy (HDU 6706) 题意: 令,有T次询问,求 f(n, a, b). 其中 T = 10^4,1 <= n,a,b <= 1e9,保证每次 a,b互质. ...
- 响应式编程(Reactive Programming)(Rx)介绍
很明显你是有兴趣学习这种被称作响应式编程的新技术才来看这篇文章的. 学习响应式编程是很困难的一个过程,特别是在缺乏优秀资料的前提下.刚开始学习时,我试过去找一些教程,并找到了为数不多的实用教程,但是它 ...
- js加密数据爬取
- 中国空气质量在线监测分析平台是一个收录全国各大城市天气数据的网站,包括温度.湿度.PM 2.5.AQI 等数据,链接为:https://www.aqistudy.cn/html/city_deta ...
- php array_unshift,array_push追加数组元素
追加元素在数组前面:<?php $a=array("a"=>"Cat","b"=>"Dog"); ar ...
- unittest零碎知识
给unittest传值: class TesCase(unittest.TestCase): # k1 = expect = msg = None # requests的返回结果和用例的预期值 def ...
- 【LGP5437】【XR-2】约定
题目 显然每一条边出现在生成树中的方案数是相等的 根据矩阵树定理,\(n\)个节点的完全图生成树个数是\(n^{n-2}\)种,完全图共有\(\frac{n(n-1)}{2}\)条边,一棵生成树共\( ...
- neo4j中cypher语句多个模糊查询
总结一下经验: neo4j中,cypher语句的模糊查询,好像是个正则表达式结构. 对于一个属性的多个模糊查询,可以使用如下写法: 比如,查询N类型中,属性attr包含'a1'或者'a2'的所有节点. ...
- pywebview gui='cef' 生成app报错—— 中断点 已到达中断点
pywebview是一个轻量级跨平台包装器,允许在其自己的本机GUI窗口中显示HTML内容.它提供了桌面应用程序中Web技术的强大功能,隐藏了GUI基于浏览器的事实.这个玩意儿好啊,可以直接让我们做的 ...