乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 MyBatis概述与快速上手

    • 本章定位
    • MyBatis简介
    • 环境搭建
    • 第一个MyBatis程序
    • SqlSessionFactoryBuilder与openSession重载
    • SqlSessionFactory与SqlSession
    • SqlSession核心方法
    • 不使用 XML 构建 SqlSessionFactory
    • Mapper接口与映射方式
    • Java API 目录结构
  • 第2章 全局配置文件详解

    • 本章定位
    • properties
    • settings
    • typeAliases
    • typeHandlers
    • objectFactory
    • plugins
    • environments
    • transactionManager
    • dataSource
    • databaseIdProvider
    • mappers
    • 日志配置
  • 第3章 SQL映射文件基础

    • 本章定位
    • select
    • insert
    • update
    • delete
    • 参数传递与占位符
    • 主键生成策略
    • resultType
    • resultMap
    • 自动映射详解
    • sql片段
    • SQL 语句构建器
  • 第4章 动态SQL

    • 本章定位
    • if
    • choose、when、otherwise
    • where
    • set
    • foreach
    • trim
    • bind
    • script 元素:在注解映射器中启用动态 SQL
    • _databaseId 与动态 SQL 的多数据库支持
    • 动态 SQL 中插入脚本语言
  • 第5章 结果映射与关联查询

    • 本章定位
    • resultMap详解
    • association
    • collection
    • discriminator
    • N+1查询问题
    • 延迟加载
  • 第6章 MyBatis注解开发

    • 本章定位
    • @Select
    • @Insert
    • @Update
    • @Delete
    • @Param
    • @Options
    • @SelectKey
    • @Results
    • @Result
    • @One
    • @Many
    • @SelectProvider
  • 第7章 缓存与性能优化

    • 本章定位
    • 一级缓存
    • 二级缓存
    • 缓存配置详解
    • 自定义缓存
    • Executor执行器类型
    • 分页插件

objectFactory

导学

本节学习目标:

  • 理解 objectFactory 在 MyBatis 结果映射中的定位:负责创建结果对象实例
  • 掌握默认 DefaultObjectFactory 的行为与能力边界
  • 了解自定义对象工厂的适用场景与实现方式
  • 通过源码流程图深入理解 MyBatis 对象实例化的完整链路

定义

当 MyBatis 执行查询并将结果集映射为 Java 对象时,面临一个基础问题:如何创建目标类的实例?

  • 对于无参构造方法的普通类,newInstance() 即可
  • 但对于某些特殊场景,可能需要:
    • 从对象池中获取实例而非直接 new
    • 通过动态代理创建实例以实现 AOP
    • 对实例进行创建后的统一初始化(如注入依赖、设置默认值)

objectFactory 元素的作用就是指定创建结果对象实例的工厂类。MyBatis 默认使用 DefaultObjectFactory,它通过反射调用无参或有参构造方法创建对象。在绝大多数项目中,无需修改此配置;但理解其原理,是深入掌握 MyBatis 结果映射机制的关键一环。


适用位置与核心属性

objectFactory 位于 mybatis-config.xml 中 typeHandlers 之后、plugins 之前。

<objectFactory type="com.feixiang.factory.EmployeeObjectFactory">
  <property name="defaultDepartment" value="DEV001"/>
</objectFactory>
属性是否必填说明
type必填对象工厂类的全限定名,必须继承 DefaultObjectFactory 或实现 ObjectFactory 接口

子标签 property 用于向对象工厂实例注入属性值,MyBatis 会通过 setter 方法或构造方法传入。


核心原理

对象创建流程图

源码层面:DefaultResultSetHandler 在创建结果对象时,会调用 ObjectFactory.create(Class<T> type) 或 create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs)。默认实现使用 Class.newInstance() 或构造方法反射创建实例。

ObjectFactory 接口定义:

public interface ObjectFactory {
    // 设置配置属性
    void setProperties(Properties properties);
    
    // 无参创建
    <T> T create(Class<T> type);
    
    // 带参创建(用于有参构造方法的结果映射)
    <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs);
    
    // 判断是否为集合类型
    <T> boolean isCollection(Class<T> type);
}

完整示例

场景说明

乐途公司员工管理系统中,Employee 实体有一个业务规则:所有新创建的员工对象,默认所属部门应为 "DEV001"(研发部),除非数据库查询结果明确指定了其他部门。通过自定义 ObjectFactory,可以在对象创建阶段统一注入默认值,避免在每个 Mapper 或 Service 中重复编写初始化逻辑。

操作前的状态

Employee 实体类:

public class Employee {
    private Integer employeeId;
    private String employeeName;
    private String departmentCode; // 期望默认值 DEV001
    private Date createTime;
    // getter / setter 省略
}

未配置自定义工厂时,从数据库查询 department_code 为 NULL 的记录,departmentCode 属性也为 null。

完整配置代码

步骤一:实现自定义 ObjectFactory

package com.feixiang.factory;

import com.feixiang.entity.Employee;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;

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

public class EmployeeObjectFactory extends DefaultObjectFactory {

    private String defaultDepartment;

    @Override
    public void setProperties(Properties properties) {
        // 读取 XML 中配置的 property
        this.defaultDepartment = properties.getProperty("defaultDepartment", "DEV001");
        System.out.println("【ObjectFactory】初始化,默认部门设置为: " + defaultDepartment);
    }

    @Override
    public <T> T create(Class<T> type) {
        T instance = super.create(type);
        return applyDefaults(type, instance);
    }

    @Override
    public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
        T instance = super.create(type, constructorArgTypes, constructorArgs);
        return applyDefaults(type, instance);
    }

    @SuppressWarnings("unchecked")
    private <T> T applyDefaults(Class<T> type, T instance) {
        // 仅对 Employee 类型注入默认值
        if (Employee.class.isAssignableFrom(type)) {
            Employee emp = (Employee) instance;
            if (emp.getDepartmentCode() == null) {
                emp.setDepartmentCode(defaultDepartment);
                System.out.println("【ObjectFactory】为 Employee 注入默认部门: " + defaultDepartment);
            }
        }
        return instance;
    }
}

步骤二:在 mybatis-config.xml 中配置

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

  <properties resource="db.properties"/>
  <settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
  </settings>
  <typeAliases>
    <package name="com.feixiang.entity"/>
  </typeAliases>

  <!-- 对象工厂配置 -->
  <objectFactory type="com.feixiang.factory.EmployeeObjectFactory">
    <property name="defaultDepartment" value="DEV001"/>
  </objectFactory>

  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>

  <mappers>
    <mapper resource="mapper/EmployeeMapper.xml"/>
  </mappers>

</configuration>

步骤三:准备测试数据

-- 插入一条 department_code 为 NULL 的记录
INSERT INTO employee (employee_name, department_code, create_time)
VALUES ('王五', NULL, NOW());

步骤四:Java 测试代码

public class ObjectFactoryDemo {
    public static void main(String[] args) throws Exception {
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);

        try (SqlSession session = factory.openSession()) {
            EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
            
            // 查询 department_code 为 NULL 的员工
            Employee emp = mapper.selectById(1);
            System.out.println("员工姓名: " + emp.getEmployeeName());
            System.out.println("所属部门: " + emp.getDepartmentCode());
        }
    }
}

实际效果/结果

控制台输出:

【ObjectFactory】初始化,默认部门设置为: DEV001
【ObjectFactory】为 Employee 注入默认部门: DEV001
员工姓名: 王五
所属部门: DEV001

效果验证:数据库中 department_code 为 NULL,但查询结果中 departmentCode 被自动填充为 DEV001。

分析

  • 自定义 ObjectFactory 的切入点在"对象创建后、属性填充前",因此可以通过 super.create() 先获得实例,再进行自定义初始化
  • setProperties() 方法在 ObjectFactory 实例化后立即调用,适合读取 XML 中配置的参数
  • 由于 ObjectFactory 全局唯一,所有结果对象的创建都会经过它,因此判断类型(Employee.class.isAssignableFrom(type))是必要的,避免对其他类型产生副作用

易错场景/常见误区

误区错误表现正解
在 create() 中直接 new 对象而不调用 super.create()丢失了 MyBatis 对构造方法参数的解析能力,有参构造映射失效始终先调用 super.create(...) 获得基础实例,再在此基础上扩展
自定义工厂中未判断类型,对所有类统一处理非 Employee 类型的查询结果也被注入 departmentCode,导致 ClassCastException使用 type.isAssignableFrom(Employee.class) 或 Employee.class.isAssignableFrom(type) 进行类型过滤
试图用 ObjectFactory 替代 Spring 的依赖注入ObjectFactory 创建的对象不会被 Spring 管理,后续无法使用 @AutowiredObjectFactory 仅用于结果对象创建时的轻量级初始化,复杂依赖注入应集成 Spring
认为 ObjectFactory 影响参数对象(如插入时的实体)发现插入时对象的默认值未被注入ObjectFactory 只负责结果对象的创建,不影响用户传入的参数对象
配置 property 但 setProperties 未被调用属性值为 null确保自定义工厂类确实重写了 setProperties 方法,且方法参数为 Properties

面试考点

Q1:MyBatis 默认使用什么对象工厂?什么情况下需要自定义?

A:默认使用 DefaultObjectFactory,它通过反射调用无参或有参构造方法创建实例。需要自定义的场景极少,通常包括:① 需要从对象池获取实例而非直接 new;② 创建后需要统一初始化(如设置默认值、注入轻量级依赖);③ 需要通过动态代理创建实例以实现某些横切逻辑。

Q2:ObjectFactory 和 ObjectWrapperFactory 有什么区别?

A:ObjectFactory 负责创建对象实例(new 的阶段);ObjectWrapperFactory 负责包装已创建的对象实例,提供属性读写的统一接口(MetaObject 的底层)。两者在结果映射链路中先后执行:ObjectFactory 创建实例 → ObjectWrapperFactory 包装实例 → 通过 MetaObject 设置属性值。

Q3:自定义 ObjectFactory 时,create(Class<T> type) 和 create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) 分别在什么场景被调用?

A:无参 create(Class<T> type) 是最常用的场景,适用于大多数无参构造方法的实体类。带参版本在有参构造方法的结果映射时被调用,例如 <constructor> 标签中指定了构造参数,MyBatis 会通过带参 create 传入解析后的参数类型和值。


小结

objectFactory 是 MyBatis 结果映射链路的"起点",它决定了查询结果将以何种方式实例化。虽然 99% 的项目无需离开默认的 DefaultObjectFactory,但理解其接口定义、调用时机和扩展方式,有助于在需要时(对象池、统一初始化、代理创建)精准切入。切记:ObjectFactory 只负责创建,不负责依赖管理,不要试图用它替代 Spring IoC。


下一章引子

对象创建之后,MyBatis 还提供了在 SQL 执行全链路中"横切干预"的能力——这就是 plugins(插件)机制。通过拦截 Executor、StatementHandler、ParameterHandler、ResultSetHandler 四大核心对象,插件可以实现 SQL 打印、性能监控、分页逻辑、数据权限等强大功能。接下来,我们将学习如何编写和注册一个 MyBatis 插件。

上一页
typeHandlers
下一页
plugins