收藏不读系列:Mybatis3一杆到底

原创
2021/12/28 19:27
阅读数 143

前言

业务系统中都少不了Mybatis,之前都是在应用和大的理论层面,最近遇到一些问题 如『怎样Debug查看生成的Sql』『业务中添加的Mapper、TypeHandler、Plugin等怎么工作的』『怎样做Mybatis上的定制化扩展』等等,这对Mybatis的代码层面的实现需要进一步了解。心中默默立了Flag,直接2022年底把这个Flag实现,记录下来自己的学习内容,希望对有需要的深入了解Mybatis的同学有所帮助。本文的目标读者,有问题的地方希望大家斧正!

 

================= 上面是一段无用的废话,可以跳过 ==================

 

前情提要:文章内容是使用最新Mybatis 3.5.X的 master分支开鲁的。梳理下思路,Mybatis可以大致分为 『配置初始化』和『执行流程』。

  • 配置初始化,包含mybatis自身配置和用户mybatis配置,它决定了 有哪些扩展点。
  • 执行流程,即Sql实际执行流程,它决定了扩展生效的生命周期。

 

一、从示例开始

从一个普通的测试开始:

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

// 1. 配置初始化:
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");  // 读取配置
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);  // 初始化SqlSessionFactory

// 2. 查询数据:实际创建的是DefaultSqlSession对象
try (SqlSession sqlSession = sqlSessionFactory.openSession()){   // 创建session (关闭session是:使用try with特征,session实现了java.lang.AutoCloseable,)
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);   // 获取Mapper
    UserDO userDO = mapper.getById(1L);  // 查询数据
    System.out.println(userDO);  // 查询结果
}

来一个相对完整的 mybatis-config.xml。配置中包含也我们常用的properties、settings、typeHandlers、plugins、mappers,也包含不常用的typeAliases、objectFactory、objectWrapperFactory、reflectorFactory、environments、databaseIdProvider。

<configuration xmlns="http://mybatis.org/schema/mybatis-config"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://mybatis.org/schema/mybatis-config http://mybatis.org/schema/mybatis-config.xsd">
	<!-- 见3.1 -->
  <properties resource="org/apache/ibatis/builder/jdbc.properties">
    <property name="prop1" value="aaaa"/>
    <property name="jdbcTypeForNull" value="NULL" />
  </properties>
	<!-- 见3.2 -->
  <settings>
    <setting name="autoMappingBehavior" value="NONE"/>
    <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
    <setting name="cacheEnabled" value="false"/>
    <setting name="proxyFactory" value="CGLIB"/>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="true"/>
    <setting name="multipleResultSetsEnabled" value="false"/>
    <setting name="useColumnLabel" value="false"/>
    <setting name="useGeneratedKeys" value="true"/>
    <setting name="defaultExecutorType" value="BATCH"/>
    <setting name="defaultStatementTimeout" value="10"/>
    <setting name="defaultFetchSize" value="100"/>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    <setting name="safeRowBoundsEnabled" value="true"/>
    <setting name="localCacheScope" value="STATEMENT"/>
    <setting name="jdbcTypeForNull" value="${jdbcTypeForNull}"/>
    <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString,xxx"/>
    <setting name="safeResultHandlerEnabled" value="false"/>
    <setting name="defaultScriptingLanguage" value="org.apache.ibatis.scripting.defaults.RawLanguageDriver"/>
    <setting name="callSettersOnNulls" value="true"/>
    <setting name="logPrefix" value="mybatis_"/>
    <setting name="logImpl" value="SLF4J"/>
    <setting name="vfsImpl" value="org.apache.ibatis.io.JBoss6VFS"/>
    <setting name="configurationFactory" value="java.lang.String"/>
    <setting name="shrinkWhitespacesInSql" value="true"/>
  </settings>
	<!-- 见3.3 -->
  <typeAliases>
    <typeAlias alias="BlogAuthor" type="org.apache.ibatis.domain.blog.Author"/>
    <typeAlias type="org.apache.ibatis.domain.blog.Blog"/>
    <typeAlias type="org.apache.ibatis.domain.blog.Post"/>
    <package name="org.apache.ibatis.domain.jpetstore"/>
  </typeAliases>
	<!-- 见3.10 -->
  <typeHandlers>
    <typeHandler javaType="String" handler="org.apache.ibatis.builder.CustomStringTypeHandler"/>
    <typeHandler javaType="String" jdbcType="VARCHAR" handler="org.apache.ibatis.builder.CustomStringTypeHandler"/>
    <typeHandler handler="org.apache.ibatis.builder.CustomLongTypeHandler"/>
    <package name="org.apache.ibatis.builder.typehandler"/>
  </typeHandlers>
	<!-- 见3.5 -->
  <objectFactory type="org.apache.ibatis.builder.ExampleObjectFactory">
    <property name="objectFactoryProperty" value="100"/>
  </objectFactory>
	<!-- 见3.6 -->
  <objectWrapperFactory type="org.apache.ibatis.builder.CustomObjectWrapperFactory" />
	<!-- 见3.7 -->
  <reflectorFactory type="org.apache.ibatis.builder.CustomReflectorFactory"/>
	<!-- 见3.4 -->
  <plugins>
    <plugin interceptor="org.apache.ibatis.builder.ExamplePlugin">
      <property name="pluginProperty" value="100"/>
    </plugin>
  </plugins>
	<!-- 见3.8 -->
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC">
        <property name="" value=""/>
      </transactionManager>
      <dataSource type="UNPOOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
	<!-- 见3.9 -->
  <databaseIdProvider type="DB_VENDOR">
    <property name="Apache Derby" value="derby"/>
  </databaseIdProvider>
	<!-- 见3.11 -->
  <mappers>
    <mapper resource="org/apache/ibatis/builder/xsd/BlogMapper.xml"/>
    <mapper url="file:./src/test/java/org/apache/ibatis/builder/xsd/NestedBlogMapper.xml"/>
    <mapper class="org.apache.ibatis.builder.xsd.CachedAuthorMapper"/>
    <package name="org.apache.ibatis.builder.mapper"/>
  </mappers>

</configuration>

从例子可以看出,大致的分为6个步骤,2个阶段(初始化配置、执行流程)。

在进入源码阶段前,我们先大致的看下6个步骤中Mybatis对应的类。防止迷失在源码中:

 

二、配置初始化

配置初始化过程:

1、初始化入口

//【方法】org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.Reader, java.lang.String, java.util.Properties)
//  @param reader :mybatis-config.xml;
//  @param environment :指定环境
//  @param properties :xml的配置变量,最终放到org.apache.ibatis.session.Configuration#setVariables(java.util.Properties)
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
    // parse **核心配置初始化**,返回:org.apache.ibatis.session.Configuration
    // build 返回:new DefaultSqlSessionFactory(config)
    return build(parser.parse());
}

//【方法】org.apache.ibatis.session.SqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration)
public SqlSessionFactory build(Configuration config) {
    // 默认使用org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
    return new DefaultSqlSessionFactory(config);
}

2、核心配置初始化

//【方法】org.apache.ibatis.builder.xml.XMLConfigBuilder#parse
public Configuration parse() {
    // 初始化XML的configuration节点
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
}

3、解析mybatis-config.xml / configuration ,节点树如下

  • mybatis-config.xml
    • configuration
      • properties
      • settings
      • typeAliases
      • plugins
      • objectFactory
      • objectWrapperFactory
      • reflectorFactory
      • environments
      • databaseIdProvider
      • typeHandlers
      • mappers
//【方法】org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
private void parseConfiguration(XNode root) {
    // 见3.1
    propertiesElement(root.evalNode("properties"));
    // 见3.2
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    loadCustomVfs(settings);
    loadCustomLogImpl(settings);
    // 见3.3
    typeAliasesElement(root.evalNode("typeAliases"));
    // 见3.4
    pluginElement(root.evalNode("plugins"));
    // 见3.5
    objectFactoryElement(root.evalNode("objectFactory"));
    // 见3.6
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    // 见3.7
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    settingsElement(settings);
    // 见3.8
    environmentsElement(root.evalNode("environments"));
    // 见3.9
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    // 见3.10
    typeHandlerElement(root.evalNode("typeHandlers"));
    // 见3.11
    mapperElement(root.evalNode("mappers"));
}

3.1、解析properties:

这是声明可替换的属性,可以在properties文件中声明,也可以通过<properties>的子标签传入。

使用示例:

文档参考:https://mybatis.org/mybatis-3/configuration.html#properties

// XML中配置属性,也可通过JAVA直接传入,参考:『1、初始化入口中 @param properties参数』
<properties resource="org/mybatis/example/config.properties">
  <property name="username" value="dev_user"/>
  <property name="password" value="F2Fa3!33TYyg"/>
</properties>

// 使用属性
<dataSource type="POOLED">
  <property name="driver" value="${driver}"/>
  <property name="url" value="${url}"/>
  <property name="username" value="${username}"/>
  <property name="password" value="${password}"/>
</dataSource>

解析代码:

//【方法】org.apache.ibatis.builder.xml.XMLConfigBuilder#propertiesElement
private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
        Properties defaults = context.getChildrenAsProperties();
        String resource = context.getStringAttribute("resource");
        String url = context.getStringAttribute("url");
        if (resource != null) {
            // resource本地配置文件
            //   在节点树位置(mybatis-config.xml / configuration / properties / resource)
            defaults.putAll(Resources.getResourceAsProperties(resource));
        } else if (url != null) {
            // url远程读取配置文件:
            //   在节点树位置(mybatis-config.xml / configuration / properties / url)
            defaults.putAll(Resources.getUrlAsProperties(url));
        }
        // 合并从java中传递的属性
        Properties vars = configuration.getVariables();
        if (vars != null) {
            defaults.putAll(vars);
        }
        // 【XML中的变量替换】使用,见下面的示例
        //  -> org.apache.ibatis.parsing.XPathParser.setVariables
        //  -> org.apache.ibatis.session.Configuration.setVariables
        parser.setVariables(defaults);
        configuration.setVariables(defaults);
    }
}

3.2、解析settings:

settings提供了修改mybatis运行时行为的能力。

使用示例:

文档参见:https://mybatis.org/mybatis-3/configuration.html#settings

<!-- mybatis-config.xml -->
<settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="multipleResultSetsEnabled" value="true"/>
  <setting name="useColumnLabel" value="true"/>
  <setting name="useGeneratedKeys" value="false"/>
  <setting name="autoMappingBehavior" value="PARTIAL"/>
  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
  <setting name="defaultExecutorType" value="SIMPLE"/>
  <setting name="defaultStatementTimeout" value="25"/>
  <setting name="defaultFetchSize" value="100"/>
  <setting name="safeRowBoundsEnabled" value="false"/>
  <setting name="mapUnderscoreToCamelCase" value="false"/>
  <setting name="localCacheScope" value="SESSION"/>
  <setting name="jdbcTypeForNull" value="OTHER"/>
  <setting name="lazyLoadTriggerMethods"
    value="equals,clone,hashCode,toString"/>
</settings>

解析代码:

import org.apache.ibatis.session.Configuration;

//【方法】org.apache.ibatis.builder.xml.XMLConfigBuilder#settingsAsProperties
private Properties settingsAsProperties(XNode context) {
    // 解析:<setting name="cacheEnabled" value="true"/>
    Properties props = context.getChildrenAsProperties();
    
    // 检查:所有settings都在configuration类中,是否有set方法
    MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
    for (Object key : props.keySet()) {
        if (!metaConfig.hasSetter(String.valueOf(key))) {
            throw new BuilderException(key+"未找到set方法,请注意大小写");
        }
    }
    return props;
}

3.3、解析typeAliases:

typeAliases提供了给Java类起别名的能力,以减少xml中Java类 冗长的名称。

使用示例:

文档参见:https://mybatis.org/mybatis-3/configuration.html#typeAliases

在mybatis-config.xml中增加:

<!-- mybatis-config.xml -->
<typeAliases>
    // 指定某个类的别名
		<typeAlias alias="user" type="cn.itcast.pojo.User"/>
  	// 批量注册『某个包下』类的别名:
    <package name="com.mxz.mybatis.domain"/>
</typeAliases>

在UserMapper.xml中使用别名 resultType="user"

<select id="loadUserById" parameterType="integer" resultType="user">
    select * from user where id = #{id}
</select>

当前mybatis也有默认的别名:https://mybatis.org/mybatis-3/configuration.html#typeAliases

解析代码:

import org.apache.ibatis.session.Configuration;

//【方法】org.apache.ibatis.builder.xml.XMLConfigBuilder#typeAliasesElement
private void typeAliasesElement(XNode parent) {
    for (XNode child : parent.getChildren()) {
        // 1. 批量注册『包中的』类别名
        if ("package".equals(child.getName())) {
            // 解析:<package name="com.mxz.mybatis.domain"/>
            String typeAliasPackage = child.getStringAttribute("name");
            // 参考『 批量注册『包中的』类别名的逻辑: 』
            configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
        } 
        // 2. 单个注册别名
        else {
            // 解析:<typeAlias alias="user" type="cn.itcast.pojo.User"/>
            String alias = child.getStringAttribute("alias");
            String type = child.getStringAttribute("type");
            Class<?> clazz = Resources.classForName(type);
            if (alias == null) {
                //【方法】org.apache.ibatis.type.TypeAliasRegistry#registerAlias(java.lang.Class<?>)
                typeAliasRegistry.registerAlias(clazz);
            } else {
                //【方法】org.apache.ibatis.type.TypeAliasRegistry#registerAlias(java.lang.String, java.lang.Class<?>)
                typeAliasRegistry.registerAlias(alias, clazz);
            }
        }
    }
}

// 1. 批量注册『包中的』类别名
// 【方法】org.apache.ibatis.type.TypeAliasRegistry#registerAliases(java.lang.String, java.lang.Class<?>)
public void registerAliases(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    // 扫描包<package name="com.mxz.mybatis.domain"/>下面的类
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
    for (Class<?> type : typeSet) {
        // 忽略匿名类,忽略接口,忽略内部类
        if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
            registerAlias(type);
        }
    }
}

// 2. 单个注册别名
//【方法】org.apache.ibatis.type.TypeAliasRegistry#registerAlias(java.lang.Class<?>)
public void registerAlias(Class<?> type) {
    // 默认使用 短类名作为别名
    String alias = type.getSimpleName();
    // 读取类上的@Alias中的别名
    Alias aliasAnnotation = type.getAnnotation(Alias.class);
    if (aliasAnnotation != null) {
        // @Alias存在时,优先
        alias = aliasAnnotation.value();
    }
    registerAlias(alias, type);
}

//【方法】org.apache.ibatis.type.TypeAliasRegistry#registerAlias(java.lang.String, java.lang.Class<?>)
public void registerAlias(String alias, Class<?> value) {
    if (alias == null) {
        throw new TypeException("The parameter alias cannot be null");
    }
    // issue #748
    String key = alias.toLowerCase(Locale.ENGLISH);
    if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
        throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
    }
    // 放入别名map中
    typeAliases.put(key, value);
}

 

3.4、解析plugins:

plugins提供了修改Mybatis核心逻辑的功能,使用时要格外小心,它提供了下面4个类,可以拦截。

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

为什么只有这4个类才可以拦截呢?因为只有这4个类,调用了org.apache.ibatis.plugin.InterceptorChain#pluginAll方法(其中InterceptorChain包含xml中配置的拦截器)。

使用示例:

文档参考:https://mybatis.org/mybatis-3/configuration.html#plugins

在mybatis-config.xml中增加:

<!-- mybatis-config.xml -->
<plugins>
  <plugin interceptor="org.apache.ibatis.builder.ExamplePlugin">
    <property name="pluginProperty" value="100"/>
  </plugin>
</plugins>

实现一个简单的插件类:

import java.util.Properties;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;

@Intercepts({@Signature(
  type = Executor.class,
  method = "update",
  args = {MappedStatement.class, Object.class})})
public class ExamplePlugin implements Interceptor {
  private Properties properties;

  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    // 调用前的处理
    Object returnObject = invocation.proceed();
    // 调用后的处理
    return returnObject;
  }

  @Override
  public Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  @Override
  public void setProperties(Properties properties) {
    this.properties = properties;
  }

  public Properties getProperties() {
    return properties;
  }
}

解析代码:

//【方法】org.apache.ibatis.builder.xml.XMLConfigBuilder#pluginElement
private void pluginElement(XNode parent) throws Exception {
    for (XNode child : parent.getChildren()) {
        String interceptor = child.getStringAttribute("interceptor");
        Properties properties = child.getChildrenAsProperties();
        // 实例化拦截器,需要实现:org.apache.ibatis.plugin.Interceptor
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
        interceptorInstance.setProperties(properties);
        // 添加到configuration类中
        configuration.addInterceptor(interceptorInstance);
    }
}

// 读取<plugin>下的<property>标签
//【方法】org.apache.ibatis.parsing.XNode#getChildrenAsProperties
public Properties getChildrenAsProperties() {
    Properties properties = new Properties();
    for (XNode child : getChildren()) {
        String name = child.getStringAttribute("name");
        String value = child.getStringAttribute("value");
        if (name != null && value != null) {
            properties.setProperty(name, value);
        }
    }
    return properties;
}

//【方法】org.apache.ibatis.session.Configuration#addInterceptor
public void addInterceptor(Interceptor interceptor) {
    // 添加到拦截器链中(内部实现为拦截器List)
    interceptorChain.addInterceptor(interceptor);
}

 

3.5、解析objectFactory:

objectFactory是用来实例化DO类的(比如:实例化UserDO),默认实现DefaultObjectFactory只是简单的调用了类的构造方法。

使用示例:

文档参考:https://mybatis.org/mybatis-3/configuration.html#objectFactory

创建ObjectFactory实例

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

public class ExampleObjectFactory extends DefaultObjectFactory {
  private Properties properties;

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

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

  @Override
  public void setProperties(Properties properties) {
    super.setProperties(properties);
    this.properties = properties;
  }

  public Properties getProperties() {
    return properties;
  }

}

配置objectFactory:

<!-- mybatis-config.xml -->
<objectFactory type="demo.ibatis..ExampleObjectFactory">
  <property name="someProperty" value="100"/>
</objectFactory>

解析代码:

//【方法】org.apache.ibatis.builder.xml.XMLConfigBuilder#objectFactoryElement
private void objectFactoryElement(XNode context) throws Exception {
    if (context != null) {
        String type = context.getStringAttribute("type");
        Properties properties = context.getChildrenAsProperties();
        // 实例化ObjectFactory【注意它是一个单例,实现时需要考虑线程安全问题】
        //   需要实现:org.apache.ibatis.reflection.factory.ObjectFactory
        ObjectFactory factory = (ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance();
        factory.setProperties(properties);
        configuration.setObjectFactory(factory);
    }
}

 

3.6、解析objectWrapperFactory:

POJO对象的包装,抽象了『查询对象属性』『更新属性』的方法。

使用示例:

新建ObjectWrapperFactory

import org.apache.ibatis.reflection.wrapper.ObjectWrapper;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;

public class CustomBeanWrapperFactory implements ObjectWrapperFactory {
  @Override
  public boolean hasWrapperFor(Object object) {
    if (object instanceof Author) {
      return true;
    } else {
      return false;
    }
  }

  @Override
  public ObjectWrapper getWrapperFor(MetaObject metaObject, Object object) {
    return new CustomBeanWrapper(metaObject, object);
  }
}

配置objectWrapperFactory:

<!-- mybatis-config.xml -->
<objectWrapperFactory type="demo.ibatis.CustomBeanWrapperFactory" />

解析代码:

//【方法】org.apache.ibatis.builder.xml.XMLConfigBuilder#objectWrapperFactoryElement
private void objectWrapperFactoryElement(XNode context) throws Exception {
    if (context != null) {
        String type = context.getStringAttribute("type");
        // 实例化ObjectWrapperFactory【注意它是一个单例,实现时需要考虑线程安全问题】
        //    需要实现:org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory
        ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).getDeclaredConstructor().newInstance();
        configuration.setObjectWrapperFactory(factory);
    }
 }

3.7、解析reflectorFactory:

Reflector是为POJO类提供反射相关方法(查找get/set方法,获取set类型等等),其中缓存了反射操作所需要的类的信息,ReflectorFactory顾名思义是Reflector的工厂类。它也可以用于我们平常的反射操作中。

使用示例:

创建ObjectFactory实例

import org.apache.ibatis.reflection.DefaultReflectorFactory;

public class CustomReflectorFactory extends DefaultReflectorFactory {

}

配置reflectorFactory:

<!-- mybatis-config.xml -->
<reflectorFactory type="demo.ibatis.CustomReflectorFactory"/>

解析代码:

 

3.8、解析environments:

mybatis可以配置多个环境(比如:开发环境、测试环境、线上环境),以满足我们需要配置多个环境的需求。

使用示例:

文档参见:https://mybatis.org/mybatis-3/configuration.html#environments

<!-- mybatis-config.xml -->
<environments default="development">
  <environment id="development">
    <transactionManager type="JDBC">
      <property name="" value=""/>
    </transactionManager>
    <dataSource type="UNPOOLED">
      <property name="driver" value="org.hsqldb.jdbcDriver"/>
      <property name="url" value="jdbc:hsqldb:mem:automapping"/>
      <property name="username" value="sa"/>
    </dataSource>
  </environment>
</environments>

解析代码:

import org.apache.ibatis.session.Configuration;

//【方法】org.apache.ibatis.builder.xml.XMLConfigBuilder#environmentsElement
private void environmentsElement(XNode context) throws Exception {
    if (environment == null) {
        // 未指定环境时:获取默认的环境:解析<environments default="development">
        environment = context.getStringAttribute("default");
    }
    for (XNode child : context.getChildren()) {
        String id = child.getStringAttribute("id");
        // 在哪儿设置的环境变量?『1、初始化入口中 @param environment参数』
        //  相当于this.environment.equals(id)
        if (isSpecifiedEnvironment(id)) {  
            // 加载环境中相应的:事务工厂、数据源
            TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
            DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
            DataSource dataSource = dsFactory.getDataSource();
            Environment.Builder environmentBuilder = new Environment.Builder(id)
                .transactionFactory(txFactory)
                .dataSource(dataSource);
            configuration.setEnvironment(environmentBuilder.build());
            break;
        }
    }
}

 

3.9、解析databaseIdProvider:

databaseId属性是为了解决不同数据库执行语句不同的问题。mybatis默认加载『无databaseId属性』和『databaseId能匹配当前数据库』的所有语句。

使用示例:

文档参见:https://mybatis.org/mybatis-3/configuration.html#databaseIdProvider

配置databaseId别名:

<!-- mybatis-config.xml -->
<databaseIdProvider type="DB_VENDOR">
  // MySQL 命名为别名 ms
  <property name="MySQL" value="ms" />
  // Oracle 命名为别名 oc
  <property name="Oracle" value="oracle" />
</databaseIdProvider>

在xxxMapper.xml文件,需要支持多数据库的语句上,添加databaseId属性:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.elements.user.dao.dbMapper" >

  <select id="SelectTime"   resultType="String" databaseId="ms">
   SELECT  NOW() FROM dual 
  </select>

  <select id="SelectTime"   resultType="String" databaseId="oc">
   SELECT  'oralce'||to_char(sysdate,'yyyy-mm-dd hh24:mi:ss')  FROM dual 
  </select>

</mapper>

解析代码:

import org.apache.ibatis.session.Configuration;

//【方法】org.apache.ibatis.builder.xml.XMLConfigBuilder#databaseIdProviderElement
private void databaseIdProviderElement(XNode context) throws Exception {
    DatabaseIdProvider databaseIdProvider = null;
    if (context != null) {
        String type = context.getStringAttribute("type");
        // awful patch to keep backward compatibility
        if ("VENDOR".equals(type)) {
            type = "DB_VENDOR";
        }
        Properties properties = context.getChildrenAsProperties();
        // 实例化DatabaseIdProvider
        databaseIdProvider = (DatabaseIdProvider) resolveClass(type).getDeclaredConstructor().newInstance();
        databaseIdProvider.setProperties(properties);
    }
    
    Environment environment = configuration.getEnvironment();
    if (environment != null && databaseIdProvider != null) {
        // 设置databaseId
        String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
        configuration.setDatabaseId(databaseId);
    }
}

 

3.10、解析typeHandlers:

TypeHandler是解决mybatis如何将数据库中的值,转化为POJO中对应的java类型的转换器。Mybatis中内置了常见的转换器,也支持自定义类型的扩展。比如:将DB中的字符串转化为fastJson中的com.alibaba.fastjson.JSONObject。

使用示例:

文档参见:https://mybatis.org/mybatis-3/configuration.html#typeHandlers

配置自定义:

<!-- mybatis-config.xml -->
<typeHandlers>
  <typeHandler handler="demo.ibatis.JSONObjectTypeHandler"/>
</typeHandlers>

自定义扩展:String 转换为 JSONObject

package com.youku.adx.common.biz.dal.handler;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import org.apache.ibatis.type.TypeHandler;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;


@MappedTypes(JSONObject.class)
@MappedJdbcTypes(JdbcType.VARCHAR)
public class JSONObjectTypeHandler implements TypeHandler<JSONObject> {
    @Override
    public void setParameter(PreparedStatement ps, int i, JSONObject parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, parameter == null ? null : JSON.toJSONString(parameter));
    }

    @Override
    public JSONObject getResult(ResultSet rs, String columnName) throws SQLException {
        String value = rs.getString(columnName);
        return value == null ? null : JSON.parseObject(value, JSONObject.class);
    }

    @Override
    public JSONObject getResult(ResultSet rs, int columnIndex) throws SQLException {
        String value = rs.getString(columnIndex);
        return value == null ? null : JSON.parseObject(value, JSONObject.class);
    }

    @Override
    public JSONObject getResult(CallableStatement cs, int columnIndex) throws SQLException {
        String value = cs.getString(columnIndex);
        return value == null ? null : JSON.parseObject(value, JSONObject.class);
    }
}

解析代码:

import org.apache.ibatis.session.Configuration;

//【方法】org.apache.ibatis.builder.xml.XMLConfigBuilder#typeHandlerElement
private void typeHandlerElement(XNode parent) {
    for (XNode child : parent.getChildren()) {
        // 1. 扫描整个包下的typeHander
        if ("package".equals(child.getName())) {
            String typeHandlerPackage = child.getStringAttribute("name");
            typeHandlerRegistry.register(typeHandlerPackage);
        } 
        // 2. 注册单个TypeHander
        else {
            // 获取指定的Java类型
            String javaTypeName = child.getStringAttribute("javaType");
            // 获取指定的jdbc类型
            String jdbcTypeName = child.getStringAttribute("jdbcType");
            // 获取hander全类名
            String handlerTypeName = child.getStringAttribute("handler");
            Class<?> javaTypeClass = resolveClass(javaTypeName);
            JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
            Class<?> typeHandlerClass = resolveClass(handlerTypeName);
            if (javaTypeClass != null) {
                if (jdbcType == null) {
                    // javaTypeClass!=null && jdbcType==null时,使用javaType+typeHandler注册
                    typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
                } else {
                    // javaTypeClass!=null && jdbcType!=null时,使用javaType+jdbcType+typeHandler注册
                    typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
                }
            } else {
                // javaTypeClass == null时,使用typeHandler注册
                typeHandlerRegistry.register(typeHandlerClass);
            }
        }
    }
}

// 1. 扫描整个包下的typeHander
//【方法】org.apache.ibatis.type.TypeHandlerRegistry#register(java.lang.String)
public void register(String packageName) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName);
    Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses();
    for (Class<?> type : handlerSet) {
        // 忽略匿名类,忽略接口,忽略内部类
        if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
            register(type);
        }
    }
}

仅使用null + null + typeHandler注册

//【方法】org.apache.ibatis.type.TypeHandlerRegistry#register(java.lang.Class<?>)
public void register(Class<?> typeHandlerClass) {
    boolean mappedTypeFound = false;
    // 尝试获取TypeHander上的@MappedTypes注解
    MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
    if (mappedTypes != null) {
        for (Class<?> javaTypeClass : mappedTypes.value()) {
            // 使用JavaType+TypeHander注册
            register(javaTypeClass, typeHandlerClass);
            mappedTypeFound = true;
        }
    }
    if (!mappedTypeFound) {
        // 实例化注册TypeHander
        register(getInstance(null, typeHandlerClass));
    }
}

public <T> void register(TypeHandler<T> typeHandler) {
    // ....此处有省略....
    if (!mappedTypeFound && typeHandler instanceof TypeReference) {
      try {
        // 实现TypeReference<T>接口时,拉取实现中的 泛型类型 + typeHandler 注册
        TypeReference<T> typeReference = (TypeReference<T>) typeHandler;
        register(typeReference.getRawType(), typeHandler);
        mappedTypeFound = true;
      } catch (Throwable t) {
        // maybe users define the TypeReference with a different type and are not assignable, so just ignore it
      }
    }
    if (!mappedTypeFound) {
      // javaType、jdbcType都没有时,注册为allTypeHandler
      register((Class<T>) null, typeHandler);
    }
  }

使用javaType + null + typeHandler注册

//【方法】org.apache.ibatis.type.TypeHandlerRegistry#register(java.lang.reflect.Type, org.apache.ibatis.type.TypeHandler<? extends T>)
private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
    // 尝试获取TypeHander上的@MappedJdbcTypes注解
    MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
    if (mappedJdbcTypes != null) {
        for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
            // javaType + jdbcType + typeHander注册
            register(javaType, handledJdbcType, typeHandler);
        }
        // 是否注册空值Jdbc类型
        if (mappedJdbcTypes.includeNullJdbcType()) {
            // javaType + null + typeHander注册
            register(javaType, null, typeHandler);
        }
    } else {
        // javaType + null + typeHander注册
        register(javaType, null, typeHandler);
    }
}

使用javaType+jdbcType+typeHandler注册(最终都是这个方法注册的)

//【方法】org.apache.ibatis.type.TypeHandlerRegistry#register(java.lang.reflect.Type, org.apache.ibatis.type.JdbcType, org.apache.ibatis.type.TypeHandler<?>)
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
    if (javaType != null) {
        Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType);
        if (map == null || map == NULL_TYPE_HANDLER_MAP) {
            map = new HashMap<>();
        }
        map.put(jdbcType, handler);
        //typeHandlerMap结构为: Map<Type, Map<JdbcType, TypeHandler<?>>>
        // 一个JavaType+JdbcType,只有有一个转换器
        typeHandlerMap.put(javaType, map);
    }
    allTypeHandlersMap.put(handler.getClass(), handler);
}

 

3.11、解析mappers:

mapper声明了SQL以及结果映射相关的定义。

使用示例:

文档参见:https://mybatis.org/mybatis-3/configuration.html#mappers

配置mybatis-config.xml

<!-- mybatis-config.xml -->
<mappers>
  <!-- 使用classpath相对路径配置 -->
  <mapper resource="org/apache/ibatis/builder/xsd/BlogMapper.xml"/>
  <!-- 使用文件路径配置 -->
  <mapper url="file:./src/test/java/org/apache/ibatis/builder/xsd/NestedBlogMapper.xml"/>
  <!-- 使用class配置 -->
  <mapper class="org.apache.ibatis.builder.xsd.CachedAuthorMapper"/>
  <!-- 使用package路径配置 -->
  <package name="org.apache.ibatis.builder.mapper"/>
</mappers>

解析代码:

import org.apache.ibatis.session.Configuration;

//【方法】org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement
private void mapperElement(XNode parent) throws Exception {
    for (XNode child : parent.getChildren()) {
        // 1. 批量解析Class类型的mapper:整个包下
        if ("package".equals(child.getName())) {
            String mapperPackage = child.getStringAttribute("name");
            configuration.addMappers(mapperPackage);
        }
        // 2. 单个解析:整个包下的mapper
        else {
            String resource = child.getStringAttribute("resource");
            String url = child.getStringAttribute("url");
            String mapperClass = child.getStringAttribute("class");
            // 2.1 解析XML: classpath相对路径中的mapper
            if (resource != null && url == null && mapperClass == null) {
                ErrorContext.instance().resource(resource);
                try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                    mapperParser.parse();
                }
            } 
            // 2.2 解析XML:文件路径配置中的mapper
            else if (resource == null && url != null && mapperClass == null) {
                ErrorContext.instance().resource(url);
                try (InputStream inputStream = Resources.getUrlAsStream(url)) {
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                    mapperParser.parse();
                }
            } 
            // 2.3 解析Class:配置的mapper
            else if (resource == null && url == null && mapperClass != null) {
                Class<?> mapperInterface = Resources.classForName(mapperClass);
                configuration.addMapper(mapperInterface);
            } 
            // 2.4 其它情况,异常
            else {
                throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
            }
        }
    }
}

从上面可以看出mapper最终分为class、xml两种。那么我们分别来看下这两种解析方式:

  • class Mapper:
// 1. 批量扫描某个包下的class Mapper
//【方法】org.apache.ibatis.binding.MapperRegistry#addMappers(java.lang.String, java.lang.Class<?>)
public void addMappers(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
        // 添加class Mapper
        //【方法】org.apache.ibatis.binding.MapperRegistry#addMapper
        addMapper(mapperClass);
    }
}

// 2. 添加单个class Mapper
//【方法】org.apache.ibatis.binding.MapperRegistry#addMapper
public <T> void addMapper(Class<T> type) {
    // 仅处理接口类型的
    if (type.isInterface()) {
        // 检查是否已经存在
        if (hasMapper(type)) {
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
        }
        boolean loadCompleted = false;
        try {
            // 生成Mapper代理工厂
            knownMappers.put(type, new MapperProxyFactory<>(type));
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            // 解析
            //【方法】org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parse
            parser.parse();
            loadCompleted = true;
        } finally {
            if (!loadCompleted) {
                knownMappers.remove(type);
            }
        }
    }
}

// 3. 解析Mapper
//【方法】org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parse
public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
        // 加载Xml:XMLMapperBuilder.parse()
        loadXmlResource();
        configuration.addLoadedResource(resource);
        assistant.setCurrentNamespace(type.getName());
        // 解析@CacheNamespace
        parseCache();
        // 解析@CacheNamespaceRef
        parseCacheRef();
        for (Method method : type.getMethods()) {
            // 忽略桥接方式,忽略默认方法
            if (!canHaveStatement(method)) {
                continue;
            }
            // 解析:@Select、@SelectProvider
            if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
                && method.getAnnotation(ResultMap.class) == null) {
                // 解析@Arg、@Result、@Results、@TypeDiscriminator
                parseResultMap(method);
            }
            try {
                // 解析@Select、@Insert、@Delete、@Update、@SelectKey、@ResultMap
                parseStatement(method);
            } catch (IncompleteElementException e) {
                configuration.addIncompleteMethod(new MethodResolver(this, method));
            }
        }
    }
    parsePendingMethods();
}
  • xml Mapper
//【方法】org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
        // 解析xml中的mapper节点
        //【方法】org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement
        configurationElement(parser.evalNode("/mapper"));
        configuration.addLoadedResource(resource);
        bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
}

// 解析xml中的mapper节点
//【方法】org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement
private void configurationElement(XNode context) {
    try {
        String namespace = context.getStringAttribute("namespace");
        if (namespace == null || namespace.isEmpty()) {
            throw new BuilderException("Mapper's namespace cannot be empty");
        }
        builderAssistant.setCurrentNamespace(namespace);
        cacheRefElement(context.evalNode("cache-ref"));
        cacheElement(context.evalNode("cache"));
        parameterMapElement(context.evalNodes("/mapper/parameterMap"));
        resultMapElements(context.evalNodes("/mapper/resultMap"));
        sqlElement(context.evalNodes("/mapper/sql"));
        buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
        throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
}

 

三、执行流程

执行流程涉及的主要实体:

1、打开会话sqlSessionFactory.openSession()

这里从org.apache.ibatis.session.defaults.DefaultSqlSessionFactory开始。

1.1、创建Session

//【方法】org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession()
public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}


//【方法】org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      // configuration中包含了Mybatis所有配置
      // 1. 获取环境信息
      final Environment environment = configuration.getEnvironment();
        
      // 2. 读取事务管理器工厂:从environment中
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        
      // 3. 创建transactionFactory
      //    默认new JdbcTransaction(数据源, 隔离级别, 自动提交)
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        
      // 4. 创建Executor:根据ExecutorType
      //  - ExecutorType.SIMPLE(默认),对应org.apache.ibatis.executor.SimpleExecutor
	  //  - ExecutorType.REUSE,对应ReuseExecutor是会重用预处理语句(PreparedStatements)
      //  - ExecutorType.BATCH,对应BatchExecutor是批处理执行器
      final Executor executor = configuration.newExecutor(tx, execType);
        
      // 5. 创建SqlSession: 线程不安全
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

1.2、创建Executor

//【方法】org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    // 1. 设置默认ExecutorType:defaultExecutorType = ExecutorType.SIMPLE
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    
    // 2. Executor创建
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    
    // 3. 代理注入:
    // - 插件生效的位置,见配置初始化中『mybatis-config.xml/configuration/plugins』
    // - 注解:@Intercepts @Signature生效的位置
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

1.3、Executor中注入Plugins

2、获取Mapper:sqlSession.getMapper(XX.class)

调用链路较长,先看个时序图:

再来看看实现:

// 入口
//【方法】org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper
public <T> T getMapper(Class<T> type) {
    // configuration为配置初始化中的配置类:org.apache.ibatis.session.Configuration
    return configuration.getMapper(type, this);
}

//【方法】org.apache.ibatis.binding.MapperRegistry#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
        // 创建Mapper代理
        return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

MapperProxyFactory创建MapperProxy

//【方法】org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.session.SqlSession)
public T newInstance(SqlSession sqlSession) {
    
	// 创建代理:
    // - mapperInterface:定义的UserMapper.class等JavaMapper接口
    // - methodCache: 它支持并发ConcurrentHashMap,实际方法调用时,放入: Map<Method, MapperMethodInvoker> = new ConcurrentHashMap<>();
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}

//【方法】org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.binding.MapperProxy<T>)
protected T newInstance(MapperProxy<T> mapperProxy) {
    
    // JDK动态代理
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

3、执行查询:mapper.getById(1L)

执行过程中,使用到的主要对象:

3.1、主调用流程

 

调用getById时,实际调用到MapperProxy.invoke方法中。首先来看下invoke方法:

//【方法】org.apache.ibatis.binding.MapperProxy#invoke
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        if (Object.class.equals(method.getDeclaringClass())) {
            // Object时,直接调用 比如toString方法
            return method.invoke(this, args);
        } else {
            // 默认走这个实现
            // 执行查询方法:PlainMethodInvoker.MapperMethod.execute(sqlSession, args)
            return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
        }
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
}

// 获取目标方法调用的MapperMethodInvoker
//【方法】org.apache.ibatis.binding.MapperProxy#cachedInvoker
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
        return MapUtil.computeIfAbsent(methodCache, method, m -> {
            // 方法为接口默认方法?
            if (m.isDefault()) {
                try {
                    if (privateLookupInMethod == null) {
                        return new DefaultMethodInvoker(getMethodHandleJava8(method));
                    } else {
                        return new DefaultMethodInvoker(getMethodHandleJava9(method));
                    }
                } catch (IllegalAccessException | InstantiationException | InvocationTargetException
                         | NoSuchMethodException e) {
                    throw new RuntimeException(e);
                }
            } 
            
            // 普通方法
            else {
                // MapperMethod是重点:它是最终执行的实现,
                return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
            }
        });
    } catch (RuntimeException re) {
        Throwable cause = re.getCause();
        throw cause == null ? re : cause;
    }
}

从上面可以看出Mybatis最终执行的是MapperMethod.execute,而调用前的方法初始化是在MapperMethod的构造方法。

 

3.2、执行增删改查

下面从执行方法中也可以看出,其中MapperMethod根据SqlCommand -> SqlCommandType 来确定增、删、改、查方法。

//【方法】org.apache.ibatis.binding.MapperMethod#execute
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    // SqlCommandType枚举类型 UNKNOWN、INSERT、UPDATE、DELETE、SELECT、FLUSH
    switch (command.getType()) {
      case INSERT: { ... }
      case UPDATE: { ... }
      case DELETE: { ... }
      case SELECT:
      	// 无返回值
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        }
        // 返回多值
        else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        }
        // 返回Map
        else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        }
        // 返回游标
        else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        }
        // 返回实体对象,比如:UserDO等
        else {
        
          //【1. 转换查询参数】
          Object param = method.convertArgsToSqlCommandParam(args);
          //【2. 执行查询】
          result = sqlSession.selectOne(command.getName(), param);
          
          ...
        }
        break;
      case FLUSH: { ... }
      default: { ... }
    }
    ...
    return result;
  }

接下来我们查看转换查询参数

 

3.2、转换查询参数

//【1. 转换查询参数】
Object param = method.convertArgsToSqlCommandParam(args);

//【方法】org.apache.ibatis.binding.MapperMethod.MethodSignature#convertArgsToSqlCommandParam
public Object convertArgsToSqlCommandParam(Object[] args) {
    return paramNameResolver.getNamedParams(args);
}

//【方法】org.apache.ibatis.reflection.ParamNameResolver#getNamedParams
public Object getNamedParams(Object[] args) {
    final int paramCount = names.size();
    
    // 0个参数时
    if (args == null || paramCount == 0) {
        return null;
    } 
    
    // 1个参数 && 没有@Param注解 时
    else if (!hasParamAnnotation && paramCount == 1) {
        Object value = args[names.firstKey()];
        return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
    } 
    
    // N个参数
    else {
        final Map<String, Object> param = new ParamMap<>();
        int i = 0;
        for (Map.Entry<Integer, String> entry : names.entrySet()) {
            param.put(entry.getValue(), args[entry.getKey()]);
            // 通用名称 (param1, param2, ...)
            final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
            // 确认是否可以覆盖@Param声明的名称
            if (!names.containsValue(genericParamName)) {
                param.put(genericParamName, args[entry.getKey()]);
            }
            i++;
        }
        return param;
    }
}

 

3.3、执行查询

3.3.1、执行SqlSession#selectOne

继续查看 SqlSession#selectOne 方法, sqlSession 是一个接口, 具体还是要看实现类 DefaultSqlSession

//【2. 执行查询】
result = sqlSession.selectOne(command.getName(), param);

//【方法】org.apache.ibatis.session.defaults.DefaultSqlSession#selectOne(java.lang.String, java.lang.Object)
public <T> T selectOne(String statement, Object parameter) {
    // 单个查询,也是走的List查询
    //【方法】org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object)
    List<T> list = this.selectList(statement, parameter);
    if (list.size() == 1) {
        return list.get(0);
    } else if (list.size() > 1) {
        throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
        return null;
    }
}

//【方法】org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object)
public <E> List<E> selectList(String statement, Object parameter) {
    // RowBounds.DEFAULT:设置offset=0, limit=Integer.MAX_VALUE
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
}

//【方法】org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    // Executor.NO_RESULT_HANDLER:设置没有handler
    return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
}

//【方法】org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
        //根据statement获取MappedStatement
        MappedStatement ms = configuration.getMappedStatement(statement);
        // 3.3.1、执行Executor#query
        return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

3.3.2、执行Executor#query

接下面看executor.query是怎么查询的。Executor默认是 CachingExecutor, 使用了装饰者模式, 在类中保持了 Executor 接口的引用, CachingExecutor 在持有的执行器基础上增加了缓存的功能。

//executor中执行查询
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);

//【方法】org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 获取BoundSql
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 生成缓存key
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    // 执行查询
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

//【方法】org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();
    // 有缓存时
    if (cache != null) {
        flushCacheIfRequired(ms);
        if (ms.isUseCache() && resultHandler == null) {
            ensureNoOutParams(ms, boundSql);
            
            // 1. 获取TransactionalCacheManager中的缓存
            List<E> list = (List<E>) tcm.getObject(cache, key);
            if (list == null) {
                // 2. 没有缓存时,执行默认SimpleExecutor
                list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                // 3. 放入缓存
                tcm.putObject(cache, key, list);
            }
            return list;
        }
    }
    
    // 无缓存时,默认SimpleExecutor
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

delegate.query代理了默认SimpleExecutor, query方法统一在抽象父类BaseExecutor

//【方法】org.apache.ibatis.executor.BaseExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ...
        
    List<E> list;
    try {
        queryStack++;
        // 获取本地缓存
        list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
        if (list != null) {
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
        } else {
            // 执行查询
            //【方法】org.apache.ibatis.executor.BaseExecutor#queryFromDatabase
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
        }
    } finally {
        queryStack--;
    }
    
    ...
        
    return list;
}


//【方法】org.apache.ibatis.executor.BaseExecutor#queryFromDatabase
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    // 缓存占位
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
        // 执行查询
        //【方法】org.apache.ibatis.executor.SimpleExecutor#doQuery
        list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
        localCache.removeObject(key);
    }
    // 加入缓存
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
        localOutputParameterCache.putObject(key, parameter);
    }
    return list;
}


//【方法】org.apache.ibatis.executor.SimpleExecutor#doQuery
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
        Configuration configuration = ms.getConfiguration();
        // 1. 创建StatementHandler,并注入代理
        // - 插件生效的位置,见配置初始化中『mybatis-config.xml/configuration/plugins』
        // - 注解:@Intercepts @Signature生效的位置
        StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        
        // 创建Statement
        stmt = prepareStatement(handler, ms.getStatementLog());
        
        // 执行查询:StatementHandler.query
        return handler.query(stmt, resultHandler);
    } finally {
        closeStatement(stmt);
    }
}

3.3.3、handler.query中的StatementHandler是谁?

执行handler.query(stmt, resultHandler)的handler默认有4种实现,实际使用的哪一种呢?

从名字上看到RoutingStatementHandler就是一个做路由的Handler,实际我们可以看下代码中

// 构建handler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // 创建RoutingStatementHandler:默认RoutingStatementHandler
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    // 注入代理插件
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
}

public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
	 // 根据MappedStatement.statementType路由(StatementType枚举:STATEMENT, PREPARED, CALLABLE)
     // MappedStatement初始化时默认为 StatementType = PREPARED
     switch (ms.getStatementType()) {
         case STATEMENT:
             delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
             break;
         case PREPARED:
             // 默认实现
             delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
             break;
         case CALLABLE:
             delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
             break;
         default:
             throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
     }

 }

由于多数情况下sql中使用了参数占位符,而这是由PreparedStatementHandler实现的,所以默认情况下使用的PreparedStatementHandler。

 

3.4、执行SQL:PreparedStatementHandler.query

终于回到想要找的主题,哪里执行SQL:

//【方法】org.apache.ibatis.executor.statement.PreparedStatementHandler#query
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 执行JDBC SQL查询
    ps.execute();
    // 返回数据解析
    return resultSetHandler.handleResultSets(ps);
}

3.5、返回数据解析

// 返回数据解析
return resultSetHandler.handleResultSets(ps);

//【方法】org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets
public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
	
    // 最终结果
    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);

	// 获取SQL的结果映射
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    
    // 处理结果集
    while (rsw != null && resultMapCount > resultSetCount) {
        ResultMap resultMap = resultMaps.get(resultSetCount);
        handleResultSet(rsw, resultMap, multipleResults, null);
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
    }
	
    // 处理嵌套结果
    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
        while (rsw != null && resultSetCount < resultSets.length) {
            ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
            // 是否包含父集
            if (parentMapping != null) {
                String nestedResultMapId = parentMapping.getNestedResultMapId();
                ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
                handleResultSet(rsw, resultMap, null, parentMapping);
            }
            rsw = getNextResultSet(stmt);
            cleanUpAfterHandlingResultSet();
            resultSetCount++;
        }
    }

    return collapseSingleResultList(multipleResults);
}

 

总结

Mybatis是一个抽象程度简单的框架,也提供了足够多的扩展点。

  • 『配置初始化』过程 核心是将xml配置解析成java对象Configuration,提供了扩展能力
    • 运行时行为配置能力:settings提供了少量可以修改mybatis运行时行为的配置;
    • 核心流程替换能力:plugins提供了修改或替换Mybatis核心逻辑的功能,而仅需要实现Interceptor接口,就可以做到,设计灵活且强大。
      • ​​​​​​​Executor:执行过程
      • ParameterHandler:sql参数处理过程
      • ResultSetHandler:返回结果过程
      • StatementHandler:sql查询过程
    • DO 生命周期控制:创建、反射、替换、结果映射等能力
      • objectFactory:控制DO的对象创建
      • objectWrapperFactory:DO对象的包装,抽象了『查询对象属性』『更新属性』的方法
      • reflectorFactory:是为DO类提供反射相关方法(查找get/set方法,获取set类型等等)
  • 『执行流程』过程就是利用配置,执行用户预期行为的过程。过程中提供懒加载、拦截器、路由等设计,为第三方扩展框架留下了足够多的扩展能力。

 

--------------------当你读到这里,代表这篇文章对你是有价值的,鼓励下作者,点个赞吧!!!--------------------

 

参考资料

https://zhuanlan.zhihu.com/p/150008843

https://codeantenna.com/a/1z848Pz3fm

https://blog.csdn.net/qq_37781649/article/details/108393311

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部