MyBatis配置文件(五)--objectFactory对象工厂

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

一、默认对象工厂DefaultObjectFactory.java

 1 /**
 2  * @author Clinton Begin
 3  */
 4 public class DefaultObjectFactory implements ObjectFactory, Serializable {
 5
 6   private static final long serialVersionUID = -8855120656740914948L;
 7
 8   @Override
 9   public <T> T create(Class<T> type) {
10     return create(type, null, null);
11   }
12
13   @SuppressWarnings("unchecked")
14   @Override
15   public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
16     Class<?> classToCreate = resolveInterface(type);
17     // we know types are assignable
18     return (T) instantiateClass(classToCreate, constructorArgTypes, constructorArgs);
19   }
20
21   @Override
22   public void setProperties(Properties properties) {
23     // no props for default
24   }
25
26   private  <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
27     try {
28       Constructor<T> constructor;
29       if (constructorArgTypes == null || constructorArgs == null) {
30         constructor = type.getDeclaredConstructor();
31         if (!constructor.isAccessible()) {
32           constructor.setAccessible(true);
33         }
34         return constructor.newInstance();
35       }
36       constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[constructorArgTypes.size()]));
37       if (!constructor.isAccessible()) {
38         constructor.setAccessible(true);
39       }
40       return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]));
41     } catch (Exception e) {
42       StringBuilder argTypes = new StringBuilder();
43       if (constructorArgTypes != null && !constructorArgTypes.isEmpty()) {
44         for (Class<?> argType : constructorArgTypes) {
45           argTypes.append(argType.getSimpleName());
46           argTypes.append(",");
47         }
48         argTypes.deleteCharAt(argTypes.length() - 1); // remove trailing ,
49       }
50       StringBuilder argValues = new StringBuilder();
51       if (constructorArgs != null && !constructorArgs.isEmpty()) {
52         for (Object argValue : constructorArgs) {
53           argValues.append(String.valueOf(argValue));
54           argValues.append(",");
55         }
56         argValues.deleteCharAt(argValues.length() - 1); // remove trailing ,
57       }
58       throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + e, e);
59     }
60   }
61
62   protected Class<?> resolveInterface(Class<?> type) {
63     Class<?> classToCreate;
64     if (type == List.class || type == Collection.class || type == Iterable.class) {
65       classToCreate = ArrayList.class;
66     } else if (type == Map.class) {
67       classToCreate = HashMap.class;
68     } else if (type == SortedSet.class) { // issue #510 Collections Support
69       classToCreate = TreeSet.class;
70     } else if (type == Set.class) {
71       classToCreate = HashSet.class;
72     } else {
73       classToCreate = type;
74     }
75     return classToCreate;
76   }
77
78   @Override
79   public <T> boolean isCollection(Class<T> type) {
80     return Collection.class.isAssignableFrom(type);
81   }
82
83 }

在这个类中有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

 1 package com.daily.objectfactory;
 2
 3 import java.util.List;
 4 import java.util.Properties;
 5
 6 import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
 7
 8 public class MyObjectFactory extends DefaultObjectFactory{
 9
10     private static final long serialVersionUID = 1L;
11
12     @Override
13     public void setProperties(Properties properties) {
14         super.setProperties(properties);
15         System.out.println("初始化参数:["+properties.toString()+"]");
16     }
17
18
19     @Override
20     public <T> T create(Class<T> type,List<Class<?>> constructorArgTypes,List<Object> constructorArgs) {
21         T result = super.create(type, constructorArgTypes, constructorArgs);
22         System.out.println("创建对象方法:"+result.getClass().getSimpleName());
23         return result;
24     }
25
26     @Override
27     public <T> boolean isCollection(Class<T> type) {
28         return super.isCollection(type);
29     }
30
31 }

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

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

第一步:配置

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

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

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

接口:

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

sql:

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

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

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

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

第三步:打印查询结果

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

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

第四步:查询结果

 1 初始化参数:[{prop1=value1}]
 2 创建对象方法:ArrayList
 3 创建对象方法:Product
 4 创建对象方法:Product
 5 创建对象方法:Product
 6 创建对象方法:Product
 7 衬衫
 8 卫衣
 9 连衣裙
10 连衣裙

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

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

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

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

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

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

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

1??根据ID查询

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

sql配置

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

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

2??查询所有

1 public Product getAllProducts();

sql配置

1 <select id="getAllProducts" resultType="product">
2         SELECT * FROM product
3 </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这个配置项的理解,不正确的地方希望看到文章的你能给予指正,谢了~~

原文地址:https://www.cnblogs.com/hellowhy/p/9672942.html

时间: 2024-11-04 01:01:41

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

MyBatis学习 之 五、MyBatis配置文件

在定义sqlSessionFactory时需要指定MyBatis主配置文件: Xml代码 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="configLocation" value="classpath:mybatis-config.xml" /> <pro

MyBatis学习 之 四、MyBatis配置文件

四.MyBatis主配置文件 在定义sqlSessionFactory时需要指定MyBatis主配置文件: Xml代码   <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="configLocation" value="classpath:mybatis-config.xml&qu

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

四.MyBatis主配置文件 在定义sqlSessionFactory时需要指定MyBatis主配置文件: Xml代码   <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="configLocation" value="classpath:mybatis-config.xml&qu

MyBatis 配置文件记录

概念 将数据库表中记录的数据查询出来,并封装成一个实体类的对象. SqlSession代表和数据库的一次会话,是用openSession对象实现的,用完必须关闭,释放资源. SqlSession和connection一样都是非线程安全的,每次使用都应该获取新的对象. mapper接口没有实现类,但是mybatis会为这个接口生成一个代理对象. 1  初级的mybatis 拥有两个配置文件:全局配置文件 和 映射文件,必须创建mybatis环境.这里用IDEA创建maven项目,只需要在相应的PO

MyBatis 配置文件基本结构

一.MyBatis 配置文件基本结构 在使用mybatis框架时,首先导入其对应的jar包,并进行相应的配置,所以得对配置文件的每个参数都得了解.一个完全的mybatis配置文件结构如下: [html] view plain copy <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0/

Mybatis配置文件SqlMapConfig.xml中的标签

SqlMapConfig.xml配置文件中的属性 1 配置内容 properties(属性) settings(全局配置参数) typeAliases(类型别名) typeHandlers(类型处理器) objectFactory(对象工厂) plugins(插件) environments(环境集合属性对象) environment(环境子属性对象) transactionManager(事务管理) dataSource(数据源) mappers(映射器) 注: sqlMapConfg.xml

Mybatis学习记录(三)--Mybatis配置文件详解

关于mybatis的配置文件,主要有两个,一个是SqlMapperConfig.xml文件一个是mapper.xml文件,分别对这两个进行深入全面学习. 一.SqlMapperConfig.xml文件 1.标签概况 在SqlMapperConfig.xml中主要有以下标签,其中环境集合environments和spring整合后废除不用.objectFactory和plugins不经常使用. properties(属性) settings(全局配置参数) typeAliases(类型别名) ty

MyBatis配置文件(一)――properties属性

MyBatis配置文件中有很多配置项,这些配置项分别代表什么,有什么作用,需要理一下了.先通过下面这个例子来看都有哪些配置项 1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE configuration 3 PUBLIC "-//mybatis.org//DTD Config 3.0//EN" 4 "http://mybatis.org/dtd/mybatis-3

MyBatis配置文件解析

MyBatis配置文件解析(概要) 1.configuration:根元素 1.1 properties:定义配置外在化 1.2 settings:一些全局性的配置 1.3 typeAliases:为一些类定义别名 1.4 typeHandlers:定义类型处理,也就是定义JAVA类型与数据库中的数据类型之间的转换关系 1.5 objectFactory 1.6 plugins:mybatis插件,插件可以修改MyBatis内部运行规则 1.7 environments:配置Mybatis的环境