互联网轻量级框架整合之MyBatis底层运转逻辑

MyBatis运转过程中主要步骤有两个,其一读取配置文件缓存到Configuration对象,用于构建SqlSessionFactory;其二是SqlSession的执行过程,这其中SqlSessionFactory的构建过程相对很好理解,而SqlSession的执行过程就相对复杂很多,它用到了不少复杂的技术,例如反射、动态代理等

可以翻回去之前的文章互联网轻量级框架整合之设计模式详细讲解了反射及MyBatis底层运转用到的设计模式,例如动态代理、工厂模式等等

熟悉了反射、及必要的设计模式,有利于理解MyBatis的底层运转逻辑,了解了MyBatis底层运转逻辑,有助于掌握MyBatis最强大也最危险的插件技术,在MyBatis底层对象调度过程中插入代码,完成一些特殊任务,这便是MyBatis插件技术,然而如果使用不当也会引发难以预估的错误,因此精通MyBatis的解析和运行原理尤为重要,否则尽量不要使用插件技术

构建SqlSessionFactory过程

SqlSessionFactory是MyBatis的核心组件之一,其主要职责是构建MyBatis的核心接口SQLSession,再回顾之前讲到的核心组件的关系
在这里插入图片描述

MyBatis采用Builder的模式构建SqlSessionFactory,分为两个步骤,第一步通过org.apache.ibatis.builder.xml.XMLConfigBuilder解析配置的XML文件,读取配置内容,并将读取的内容存入org.apache.ibatis.session.Configuration类对象中。Configuration采用单例模式,MyBatis配置内容均会存放在这个单例对象中,以便后续读取这些内容;第二步使用Configuration对象构建SQLSessionFactory,在MyBatis中的SqlSessionFactory是一个接口,而不是一个实现类,MyBatis为这个接口提供了一个默认的实现类org.apache.ibatis.session.defalults.DefaultSqlSessionFactory,大部分情况下没必要自己构建新的SqlSessionFactory实现类,用MyBatis提供的这个默认实现类即可

这种构建的方式是一种Builder模式,对于要构建的对象复杂且多样的场景,使用构造参数很难实现且难以维护,这时候使用一个类(Configuration)作为引导,一步步构建所需内容,然后通过它去构建最终对象(SqlSessionFactory),结构更加清晰,且更易于维护和扩展,能够构建更灵活复杂的对象

XMLConfigBuilder解析XML的源码如下:

package org.apache.ibatis.builder.xml;
import java.io.InputStream;
import java.io.Reader;
import java.util.Iterator;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.ibatis.builder.BaseBuilder;
import org.apache.ibatis.builder.BuilderException;
import org.apache.ibatis.datasource.DataSourceFactory;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.loader.ProxyFactory;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.io.VFS;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaClass;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.session.AutoMappingBehavior;
import org.apache.ibatis.session.AutoMappingUnknownColumnBehavior;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.LocalCacheScope;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.type.JdbcType;

/**
 * XML配置文件解析器构造器类,用于构建MyBatis的配置对象。
 */
public class XMLConfigBuilder extends BaseBuilder {
    // 标记配置是否已被解析
    private boolean parsed;
    // XML解析器
    private final XPathParser parser;
    // 当前环境标识
    private String environment;
    // 当前解析器工厂
    private final ReflectorFactory localReflectorFactory;

    /**
     * 构造函数,初始化XML配置解析器。
     *
     * @param reader      用于读取配置文件的Reader。
     * @param environment 环境标识,用于指定配置文件中的环境。
     * @param props       用户定义的属性。
     */
    public XMLConfigBuilder(Reader reader) {
        this(reader, null, null);
    }

    /**
     * 构造函数,初始化XML配置解析器。
     *
     * @param reader      用于读取配置文件的Reader。
     * @param environment 环境标识,用于指定配置文件中的环境。
     */
    public XMLConfigBuilder(Reader reader, String environment) {
        this(reader, environment, null);
    }

    /**
     * 构造函数,初始化XML配置解析器。
     *
     * @param reader      用于读取配置文件的Reader。
     * @param environment 环境标识,用于指定配置文件中的环境。
     * @param props       用户定义的属性。
     */
    public XMLConfigBuilder(Reader reader, String environment, Properties props) {
        this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
    }

    /**
     * 构造函数,初始化XML配置解析器。
     *
     * @param inputStream 用于读取配置文件的InputStream。
     * @param environment 环境标识,用于指定配置文件中的环境。
     * @param props       用户定义的属性。
     */
    public XMLConfigBuilder(InputStream inputStream) {
        this(inputStream, null, null);
    }

    /**
     * 构造函数,初始化XML配置解析器。
     *
     * @param inputStream 用于读取配置文件的InputStream。
     * @param environment 环境标识,用于指定配置文件中的环境。
     */
    public XMLConfigBuilder(InputStream inputStream, String environment) {
        this(inputStream, environment, null);
    }

    /**
     * 构造函数,初始化XML配置解析器。
     *
     * @param inputStream 用于读取配置文件的InputStream。
     * @param environment 环境标识,用于指定配置文件中的环境。
     * @param props       用户定义的属性。
     */
    public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
        this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
    }

    /**
     * 私有构造函数,用于内部创建实例。
     *
     * @param parser      XML解析器。
     * @param environment 环境标识,用于指定配置文件中的环境。
     * @param props       用户定义的属性。
     */
    private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        super(new Configuration());
        this.localReflectorFactory = new DefaultReflectorFactory();
        ErrorContext.instance().resource("SQL Mapper Configuration");
        this.configuration.setVariables(props);
        this.parsed = false;
        this.environment = environment;
        this.parser = parser;
    }

    /**
     * 解析配置文件。
     *
     * @return Configuration 解析完成的配置对象。
     * @throws BuilderException 如果配置已经解析过或解析过程中出现错误,则抛出异常。
     */
    public Configuration parse() {
        if (this.parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        } else {
            this.parsed = true;
            this.parseConfiguration(this.parser.evalNode("/configuration"));
            return this.configuration;
        }
    }

    /**
     * 解析配置元素。
     *
     * @param root 配置文件的根节点。
     */
    private void parseConfiguration(XNode root) {
        try {
            // 解析配置文件中的各个元素
            this.propertiesElement(root.evalNode("properties"));
            Properties settings = this.settingsAsProperties(root.evalNode("settings"));
            this.loadCustomVfs(settings);
            this.loadCustomLogImpl(settings);
            this.typeAliasesElement(root.evalNode("typeAliases"));
            this.pluginElement(root.evalNode("plugins"));
            this.objectFactoryElement(root.evalNode("objectFactory"));
            this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
            this.settingsElement(settings);
            this.environmentsElement(root.evalNode("environments"));
            this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            this.typeHandlerElement(root.evalNode("typeHandlers"));
            this.mapperElement(root.evalNode("mappers"));
        } catch (Exception var3) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
        }
    }

    /**
     * 将设置节点转换为属性对象。
     *
     * @param context 设置节点。
     * @return Properties 转换后的属性对象。
     */
    private Properties settingsAsProperties(XNode context) {
        if (context == null) {
            return new Properties();
        } else {
            Properties props = context.getChildrenAsProperties();
            MetaClass metaConfig = MetaClass.forClass(Configuration.class, this.localReflectorFactory);
            Iterator var4 = props.keySet().iterator();

            while (var4.hasNext()) {
                Object key = var4.next();
                if (!metaConfig.hasSetter(String.valueOf(key))) {
                    throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
                }
            }

            return props;
        }
    }

    /**
     * 加载自定义的虚拟文件系统(VFS)实现。
     * 该方法通过读取配置属性中的"vfsImpl"属性,来加载一个或多个VFS实现类,并将其设置为当前配置的VFS实现。
     *
     * @param props 包含配置属性的Properties对象,其中应包含"vfsImpl"属性以指定VFS实现的类名(多个类名使用逗号分隔)。
     * @throws ClassNotFoundException 如果指定的VFS实现类无法找到。
     */
    private void loadCustomVfs(Properties props) throws ClassNotFoundException {
        // 从属性中获取VFS实现的类名列表
        String value = props.getProperty("vfsImpl");
        if (value != null) {
            // 分割类名字符串为数组
            String[] clazzes = value.split(",");
            String[] var4 = clazzes;
            int var5 = clazzes.length;

            // 遍历并加载每个指定的VFS实现类
            for(int var6 = 0; var6 < var5; ++var6) {
                String clazz = var4[var6];
                // 忽略空字符串
                if (!clazz.isEmpty()) {
                    // 加载类并设置为当前配置的VFS实现
                    Class<? extends VFS> vfsImpl = Resources.classForName(clazz);
                    this.configuration.setVfsImpl(vfsImpl);
                }
            }
        }
    }
      /**
     * 加载自定义日志实现类。
     * @param props 包含配置信息的属性集合,预期包含"logImpl"属性以指定日志实现类的名称。
     */
    private void loadCustomLogImpl(Properties props) {
        // 根据配置的"logImpl"属性解析对应的日志实现类
        Class<? extends Log> logImpl = this.resolveClass(props.getProperty("logImpl"));
        // 设置解析得到的日志实现类为当前配置的日志实现
        this.configuration.setLogImpl(logImpl);
    }
    /**
     * 处理类型别名元素。
     * @param parent XNode对象,表示类型别名的配置元素。
     */
    private void typeAliasesElement(XNode parent) {
        // 遍历所有子元素,处理package和type别名
        if (parent != null) {
            Iterator var2 = parent.getChildren().iterator();

            while(var2.hasNext()) {
                XNode child = (XNode)var2.next();
                String alias;
                // 处理package类型的别名
                if ("package".equals(child.getName())) {
                    alias = child.getStringAttribute("name");
                    this.configuration.getTypeAliasRegistry().registerAliases(alias);
                } else { // 处理指定类型的别名
                    alias = child.getStringAttribute("alias");
                    String type = child.getStringAttribute("type");
                    try {
                        Class<?> clazz = Resources.classForName(type);
                        // 如果未指定别名,则自动注册类名作为别名
                        if (alias == null) {
                            this.typeAliasRegistry.registerAlias(clazz);
                        } else {
                            this.typeAliasRegistry.registerAlias(alias, clazz);
                        }
                    } catch (ClassNotFoundException var7) {
                        throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + var7, var7);
                    }
                }
            }
        }
    }

    /**
     * 处理插件元素。
     * @param parent XNode对象,表示插件的配置元素。
     * @throws Exception 如果插件加载失败,则抛出异常。
     */
    private void pluginElement(XNode parent) throws Exception {
        // 遍历所有子元素,实例化并添加拦截器
        if (parent != null) {
            Iterator var2 = parent.getChildren().iterator();

            while(var2.hasNext()) {
                XNode child = (XNode)var2.next();
                String interceptor = child.getStringAttribute("interceptor");
                Properties properties = child.getChildrenAsProperties();
                // 根据配置创建拦截器实例,并设置属性
                Interceptor interceptorInstance = (Interceptor)this.resolveClass(interceptor).getDeclaredConstructor().newInstance();
                interceptorInstance.setProperties(properties);
                this.configuration.addInterceptor(interceptorInstance);
            }
        }
    }
    /**
     * 处理对象工厂元素。
     * @param context XNode对象,表示对象工厂的配置元素。
     * @throws Exception 如果工厂加载失败,则抛出异常。
     */
    private void objectFactoryElement(XNode context) throws Exception {
        // 根据配置创建对象工厂实例,并设置属性
        if (context != null) {
            String type = context.getStringAttribute("type");
            Properties properties = context.getChildrenAsProperties();
            ObjectFactory factory = (ObjectFactory)this.resolveClass(type).getDeclaredConstructor().newInstance();
            factory.setProperties(properties);
            this.configuration.setObjectFactory(factory);
        }
    }

    /**
     * 处理对象包装工厂元素。
     * @param context XNode对象,表示对象包装工厂的配置元素。
     * @throws Exception 如果工厂加载失败,则抛出异常。
     */
    private void objectWrapperFactoryElement(XNode context) throws Exception {
        // 根据配置创建对象包装工厂实例
        if (context != null) {
            String type = context.getStringAttribute("type");
            ObjectWrapperFactory factory = (ObjectWrapperFactory)this.resolveClass(type).getDeclaredConstructor().newInstance();
            this.configuration.setObjectWrapperFactory(factory);
        }
    }

    /**
     * 处理反射工厂元素。
     * @param context XNode对象,表示反射工厂的配置元素。
     * @throws Exception 如果工厂加载失败,则抛出异常。
     */
    private void reflectorFactoryElement(XNode context) throws Exception {
        // 根据配置创建反射工厂实例
        if (context != null) {
            String type = context.getStringAttribute("type");
            ReflectorFactory factory = (ReflectorFactory)this.resolveClass(type).getDeclaredConstructor().newInstance();
            this.configuration.setReflectorFactory(factory);
        }
    }

    /**
     * 处理属性元素。
     * @param context XNode对象,表示属性的配置元素。
     * @throws Exception 如果处理过程出错,则抛出异常。
     */
    private void propertiesElement(XNode context) throws Exception {
        // 综合处理属性文件路径或URL,加载并合并属性
        if (context != null) {
            Properties defaults = context.getChildrenAsProperties();
            String resource = context.getStringAttribute("resource");
            String url = context.getStringAttribute("url");
            if (resource != null && url != null) {
                throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
            }

            if (resource != null) {
                defaults.putAll(Resources.getResourceAsProperties(resource));
            } else if (url != null) {
                defaults.putAll(Resources.getUrlAsProperties(url));
            }

            Properties vars = this.configuration.getVariables();
            if (vars != null) {
                defaults.putAll(vars);
            }
            this.parser.setVariables(defaults);
            this.configuration.setVariables(defaults);
        }
    }
    /**
     * 根据配置属性设置Configuration元素。
     *
     * @param props 配置属性,包含了MyBatis配置文件中的属性值。
     */
    private void settingsElement(Properties props) {
        // 设置自动映射行为
        this.configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
        // 设置自动映射未知列行为
        this.configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
        // 设置缓存是否启用
        this.configuration.setCacheEnabled(this.booleanValueOf(props.getProperty("cacheEnabled"), true));
        // 设置代理工厂
        this.configuration.setProxyFactory((ProxyFactory) this.createInstance(props.getProperty("proxyFactory")));
        // 设置延迟加载是否启用
        this.configuration.setLazyLoadingEnabled(this.booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
        // 设置积极的延迟加载
        this.configuration.setAggressiveLazyLoading(this.booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
        // 设置是否启用多个结果集
        this.configuration.setMultipleResultSetsEnabled(this.booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
        // 设置是否使用列标签
        this.configuration.setUseColumnLabel(this.booleanValueOf(props.getProperty("useColumnLabel"), true));
        // 设置是否使用生成的键
        this.configuration.setUseGeneratedKeys(this.booleanValueOf(props.getProperty("useGeneratedKeys"), false));
        // 设置默认执行器类型
        this.configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
        // 设置默认语句超时时间
        this.configuration.setDefaultStatementTimeout(this.integerValueOf(props.getProperty("defaultStatementTimeout"), (Integer) null));
        // 设置默认获取结果集大小
        this.configuration.setDefaultFetchSize(this.integerValueOf(props.getProperty("defaultFetchSize"), (Integer) null));
        // 设置默认结果集类型
        this.configuration.setDefaultResultSetType(this.resolveResultSetType(props.getProperty("defaultResultSetType")));
        // 设置是否将下划线映射到驼峰命名
        this.configuration.setMapUnderscoreToCamelCase(this.booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
        // 设置是否安全的RowBounds启用
        this.configuration.setSafeRowBoundsEnabled(this.booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
        // 设置本地缓存范围
        this.configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
        // 设置空值的JdbcType
        this.configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
        // 设置延迟加载触发方法
        this.configuration.setLazyLoadTriggerMethods(this.stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
        // 设置是否启用安全的结果处理器
        this.configuration.setSafeResultHandlerEnabled(this.booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
        // 设置默认的脚本语言
        this.configuration.setDefaultScriptingLanguage(this.resolveClass(props.getProperty("defaultScriptingLanguage")));
        // 设置默认枚举类型处理器
        this.configuration.setDefaultEnumTypeHandler(this.resolveClass(props.getProperty("defaultEnumTypeHandler")));
        // 设置是否在null值上调用setter方法
        this.configuration.setCallSettersOnNulls(this.booleanValueOf(props.getProperty("callSettersOnNulls"), false));
        // 设置是否使用实际参数名称
        this.configuration.setUseActualParamName(this.booleanValueOf(props.getProperty("useActualParamName"), true));
        // 设置为空行返回实例
        this.configuration.setReturnInstanceForEmptyRow(this.booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
        // 设置日志前缀
        this.configuration.setLogPrefix(props.getProperty("logPrefix"));
        // 设置配置工厂类
        this.configuration.setConfigurationFactory(this.resolveClass(props.getProperty("configurationFactory")));
    }

    /**
     * 根据XML配置节点设置环境信息。
     *
     * @param context XML配置节点,包含了环境的配置信息。
     * @throws Exception 如果处理过程中发生错误,则抛出异常。
     */
    private void environmentsElement(XNode context) throws Exception {
        if (context != null) {
            // 获取默认环境
            if (this.environment == null) {
                this.environment = context.getStringAttribute("default");
            }

            // 遍历并处理所有环境配置
            Iterator var2 = context.getChildren().iterator();

            while (var2.hasNext()) {
                XNode child = (XNode) var2.next();
                String id = child.getStringAttribute("id");
                // 处理指定的环境配置
                if (this.isSpecifiedEnvironment(id)) {
                    TransactionFactory txFactory = this.transactionManagerElement(child.evalNode("transactionManager"));
                    DataSourceFactory dsFactory = this.dataSourceElement(child.evalNode("dataSource"));
                    DataSource dataSource = dsFactory.getDataSource();
                    Environment.Builder environmentBuilder = (new Environment.Builder(id)).transactionFactory(txFactory).dataSource(dataSource);
                    this.configuration.setEnvironment(environmentBuilder.build());
                }
            }
        }
    }

    /**
     * 处理数据库ID提供者元素。
     *
     * @param context XML配置节点,包含了数据库ID提供者的配置信息。
     * @throws Exception 如果处理过程中发生错误,则抛出异常。
     */
    private void databaseIdProviderElement(XNode context) throws Exception {
        DatabaseIdProvider databaseIdProvider = null;
        if (context != null) {
            String type = context.getStringAttribute("type");
            if ("VENDOR".equals(type)) {
                type = "DB_VENDOR";
            }
            Properties properties = context.getChildrenAsProperties();
            // 根据类型创建数据库ID提供者实例
            databaseIdProvider = (DatabaseIdProvider) this.resolveClass(type).getDeclaredConstructor().newInstance();
            databaseIdProvider.setProperties(properties);
        }
        Environment environment = this.configuration.getEnvironment();
        if (environment != null && databaseIdProvider != null) {
            // 获取并设置数据库ID
            String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
            this.configuration.setDatabaseId(databaseId);
        }
    }

    /**
     * 处理事务管理器元素。
     *
     * @param context XML配置节点,包含了事务管理器的配置信息。
     * @return 创建的事务管理器实例。
     * @throws Exception 如果处理过程中发生错误,则抛出异常。
     */
    private TransactionFactory transactionManagerElement(XNode context) throws Exception {
        if (context != null) {
            String type = context.getStringAttribute("type");
            Properties props = context.getChildrenAsProperties();
            // 根据类型创建事务管理器实例
            TransactionFactory factory = (TransactionFactory) this.resolveClass(type).getDeclaredConstructor().newInstance();
            factory.setProperties(props);
            return factory;
        } else {
            throw new BuilderException("Environment declaration requires a TransactionFactory.");
        }
    }

    /**
     * 处理数据源工厂元素。
     *
     * @param context XML配置节点,包含了数据源工厂的配置信息。
     * @return 创建的数据源工厂实例。
     * @throws Exception 如果处理过程中发生错误,则抛出异常。
     */
    private DataSourceFactory dataSourceElement(XNode context) throws Exception {
        if (context != null) {
            String type = context.getStringAttribute("type");
            Properties props = context.getChildrenAsProperties();
            // 根据类型创建数据源工厂实例
            DataSourceFactory factory = (DataSourceFactory) this.resolveClass(type).getDeclaredConstructor().newInstance();
            factory.setProperties(props);
            return factory;
        } else {
            throw new BuilderException("Environment declaration requires a DataSourceFactory.");
        }
    }

    /**
     * 处理类型处理器元素。
     *
     * @param parent XML配置节点的父节点,包含了类型处理器的配置信息。
     */
    private void typeHandlerElement(XNode parent) {
        if (parent != null) {
            // 遍历并处理所有类型处理器配置
            Iterator var2 = parent.getChildren().iterator();

            while (var2.hasNext()) {
                XNode child = (XNode) var2.next();
                String typeHandlerPackage;
                // 处理类型处理器包
                if ("package".equals(child.getName())) {
                    typeHandlerPackage = child.getStringAttribute("name");
                    this.typeHandlerRegistry.register(typeHandlerPackage);
                } else {
                    // 处理具体的类型处理器
                    typeHandlerPackage = child.getStringAttribute("javaType");
                    String jdbcTypeName = child.getStringAttribute("jdbcType");
                    String handlerTypeName = child.getStringAttribute("handler");
                    Class<?> javaTypeClass = this.resolveClass(typeHandlerPackage);
                    JdbcType jdbcType = this.resolveJdbcType(jdbcTypeName);
                    Class<?> typeHandlerClass = this.resolveClass(handlerTypeName);
                    if (javaTypeClass != null) {
                        if (jdbcType == null) {
                            this.typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
                        } else {
                            this.typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
                        }
                    } else {
                        this.typeHandlerRegistry.register(typeHandlerClass);
                    }
                }
            }
        }
    }

    /**
 	 * 处理给定的XNode元素,根据子元素的类型(package、resource、url、class),动态地向配置中添加mapper资源。
 	 * 这个方法主要负责解析XML配置文件中与mapper相关的元素,将它们适当地添加到MyBatis的配置中。
	 *
 	 * @param parent XNode对象,代表当前正在处理的XML配置文件中的一个节点。预期为mapper元素的父节点。
 	 * @throws Exception 如果在解析过程中遇到任何错误,将会抛出异常。
 	 */
	private void mapperElement(XNode parent) throws Exception {
    	if (parent != null) {
        	Iterator var2 = parent.getChildren().iterator();
        	while(true) {
            	while(var2.hasNext()) {
                	XNode child = (XNode)var2.next();
                	String resource;
                	// 处理package子元素,将namespace属性作为mapper资源名称添加到配置中
                	if ("package".equals(child.getName())) {
                    	resource = child.getStringAttribute("name");
                    	this.configuration.addMappers(resource);
                	} else {
                    	// 处理resource、url、class子元素,根据不同的情况加载并解析mapper配置
                    	resource = child.getStringAttribute("resource");
                    	String url = child.getStringAttribute("url");
                    	String mapperClass = child.getStringAttribute("class");
                    	XMLMapperBuilder mapperParser;
                    	InputStream inputStream;
                   		// 根据resource、url、mapperClass的组合,决定如何加载mapper配置
                    	if (resource != null && url == null && mapperClass == null) {
                        	ErrorContext.instance().resource(resource);
                        	inputStream = Resources.getResourceAsStream(resource);
                        	mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
                        	mapperParser.parse();
                    	} else if (resource == null && url != null && mapperClass == null) {
                        	ErrorContext.instance().resource(url);
                        	inputStream = Resources.getUrlAsStream(url);
                        	mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
                        	mapperParser.parse();
                    	} else {
                        	// 如果存在不合法的配置组合,抛出异常
                        	if (resource != null || url != null || mapperClass == null) {
                            	throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                        	}

                        	Class<?> mapperInterface = Resources.classForName(mapperClass);
                        	this.configuration.addMapper(mapperInterface);
                    	}
                	}
            	}
            	return;
        	}
    	}
	}


    /**
     * 判断指定的环境是否被指定了。
     *
     * @param id 环境ID。
     * @return 如果指定的环境与当前环境匹配,则返回true;否则返回false。
     */
    private boolean isSpecifiedEnvironment(String id) {
        if (this.environment == null) {
            throw new BuilderException("No environment specified.");
        } else if (id == null) {
            throw new BuilderException("Environment requires an id attribute.");
        } else {
            return this.environment.equals(id);
        }
    }
}

XMLConfigBuilder 继承了Builder,再看一下Builder的源码

package org.apache.ibatis.builder;

// 导入各种依赖
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.mapping.ResultSetType;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeAliasRegistry;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;

/**
 * BaseBuilder类提供了一系列用于构建和解析配置的方法。
 * 它是MyBatis构建过程中的一些基础功能的抽象。
 */
public abstract class BaseBuilder {
    // Configuration实例,用于访问全局配置
    protected final Configuration configuration;
    // TypeAliasRegistry实例,用于注册和解析类型别名
    protected final TypeAliasRegistry typeAliasRegistry;
    // TypeHandlerRegistry实例,用于注册和获取TypeHandler
    protected final TypeHandlerRegistry typeHandlerRegistry;

    /**
     * 构造函数,初始化BaseBuilder实例。
     *
     * @param configuration Configuration对象,提供全局配置。
     */
    public BaseBuilder(Configuration configuration) {
        this.configuration = configuration;
        this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
        this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
    }

    // 提供对Configuration对象的访问
    public Configuration getConfiguration() {
        return this.configuration;
    }

    /**
     * 解析正则表达式。
     *
     * @param regex  待解析的正则表达式字符串。
     * @param defaultValue 如果regex为null,将使用此默认值。
     * @return 编译后的正则表达式模式。
     */
    protected Pattern parseExpression(String regex, String defaultValue) {
        return Pattern.compile(regex == null ? defaultValue : regex);
    }

    /**
     * 将字符串转换为布尔值。
     *
     * @param value 待转换的字符串。
     * @param defaultValue 如果value为null,将使用此默认值。
     * @return 转换后的布尔值。
     */
    protected Boolean booleanValueOf(String value, Boolean defaultValue) {
        return value == null ? defaultValue : Boolean.valueOf(value);
    }

    /**
     * 将字符串转换为整数值。
     *
     * @param value 待转换的字符串。
     * @param defaultValue 如果value为null,将使用此默认值。
     * @return 转换后的整数值。
     */
    protected Integer integerValueOf(String value, Integer defaultValue) {
        return value == null ? defaultValue : Integer.valueOf(value);
    }

    /**
     * 将字符串分割并转换为字符串集。
     *
     * @param value 待分割和转换的字符串。
     * @param defaultValue 如果value为null,将使用此默认值。
     * @return 转换后的字符串集。
     */
    protected Set<String> stringSetValueOf(String value, String defaultValue) {
        value = value == null ? defaultValue : value;
        return new HashSet(Arrays.asList(value.split(",")));
    }

    /**
     * 根据别名解析JdbcType。
     *
     * @param alias JdbcType的别名。
     * @return 解析后的JdbcType枚举。
     * @throws BuilderException 如果别名无法解析为有效的JdbcType。
     */
    protected JdbcType resolveJdbcType(String alias) {
        if (alias == null) {
            return null;
        } else {
            try {
                return JdbcType.valueOf(alias);
            } catch (IllegalArgumentException var3) {
                throw new BuilderException("Error resolving JdbcType. Cause: " + var3, var3);
            }
        }
    }

    /**
     * 根据别名解析ResultSetType。
     *
     * @param alias ResultSetType的别名。
     * @return 解析后的ResultSetType枚举。
     * @throws BuilderException 如果别名无法解析为有效的ResultSetType。
     */
    protected ResultSetType resolveResultSetType(String alias) {
        if (alias == null) {
            return null;
        } else {
            try {
                return ResultSetType.valueOf(alias);
            } catch (IllegalArgumentException var3) {
                throw new BuilderException("Error resolving ResultSetType. Cause: " + var3, var3);
            }
        }
    }

    /**
     * 根据别名解析ParameterMode。
     *
     * @param alias ParameterMode的别名。
     * @return 解析后的ParameterMode枚举。
     * @throws BuilderException 如果别名无法解析为有效的ParameterMode。
     */
    protected ParameterMode resolveParameterMode(String alias) {
        if (alias == null) {
            return null;
        } else {
            try {
                return ParameterMode.valueOf(alias);
            } catch (IllegalArgumentException var3) {
                throw new BuilderException("Error resolving ParameterMode. Cause: " + var3, var3);
            }
        }
    }

    /**
     * 根据别名创建实例。
     *
     * @param alias 类的别名。
     * @return 创建的实例,如果别名无法解析为类则返回null。
     * @throws BuilderException 如果创建实例过程中发生错误。
     */
    protected Object createInstance(String alias) {
        Class<?> clazz = this.resolveClass(alias);
        if (clazz == null) {
            return null;
        } else {
            try {
                return this.resolveClass(alias).getDeclaredConstructor().newInstance();
            } catch (Exception var4) {
                throw new BuilderException("Error creating instance. Cause: " + var4, var4);
            }
        }
    }

    /**
     * 根据别名解析类。
     *
     * @param alias 类的别名。
     * @param <T> 类的类型。
     * @return 解析后的类,如果别名无法解析则返回null。
     * @throws BuilderException 如果解析类过程中发生错误。
     */
    protected <T> Class<? extends T> resolveClass(String alias) {
        if (alias == null) {
            return null;
        } else {
            try {
                return this.resolveAlias(alias);
            } catch (Exception var3) {
                throw new BuilderException("Error resolving class. Cause: " + var3, var3);
            }
        }
    }

    /**
     * 解析TypeHandler。
     *
     * @param javaType Java类型。
     * @param typeHandlerAlias TypeHandler的别名。
     * @return 解析后的TypeHandler实例。
     * @throws BuilderException 如果解析TypeHandler过程中发生错误。
     */
    protected TypeHandler<?> resolveTypeHandler(Class<?> javaType, String typeHandlerAlias) {
        if (typeHandlerAlias == null) {
            return null;
        } else {
            Class<?> type = this.resolveClass(typeHandlerAlias);
            if (type != null && !TypeHandler.class.isAssignableFrom(type)) {
                throw new BuilderException("Type " + type.getName() + " is not a valid TypeHandler because it does not implement TypeHandler interface");
            } else {
                return this.resolveTypeHandler(javaType, type);
            }
        }
    }

    /**
     * 根据Java类型和TypeHandler类型解析TypeHandler。
     *
     * @param javaType Java类型。
     * @param typeHandlerType TypeHandler类型。
     * @return 解析后的TypeHandler实例。
     */
    protected TypeHandler<?> resolveTypeHandler(Class<?> javaType, Class<? extends TypeHandler<?>> typeHandlerType) {
        if (typeHandlerType == null) {
            return null;
        } else {
            TypeHandler<?> handler = this.typeHandlerRegistry.getMappingTypeHandler(typeHandlerType);
            if (handler == null) {
                handler = this.typeHandlerRegistry.getInstance(javaType, typeHandlerType);
            }

            return handler;
        }
    }

    /**
     * 根据别名解析类。
     *
     * @param alias 类的别名。
     * @param <T> 类的类型。
     * @return 解析后的类,如果别名无法解析则返回null。
     */
    protected <T> Class<? extends T> resolveAlias(String alias) {
        return this.typeAliasRegistry.resolveAlias(alias);
    }
}


以typeHandler为例,我们配置的typeHandler都会被注册到typeHandlerRegistry对象中,其构建是在XMLConfigBuilder的父类BaseBuilder中,从BaseBuilder的源码中可以看到,typeHandlerRegistry对象实际上是Configuration单例的一个属性,所以可以通过Configuration单例得到typeHandlerRegistry对象,进而通过解析配置类获取开发者配置注册的typeHandler

构建Configuration

Configuration类是一个和XMLConfigBuilder同样复杂的类,如果想熟悉其源码,可以在如下位置找到,然后让AI解读即可
在这里插入图片描述

在SQLSessionFactory的构建中,Configuration的作用如下:

  • 保存配置文件中的内容,包括基础配置的XML和映射器XML(或者注解)
  • 初始化基础配置,例如MyBatis的别名等等,还有重要的类对象例如插件、映射器、Object工厂、typeHandlers对象等等
  • 提供单例,为后续构建SessionFactory服务提供配置的参数
  • 执行对象的初始化方法

Configuration通过XMLConfigBuilder构建,它会读取所有XML配置信息,然后将它们解析并保存在Configuration单例中,然后对Properties(全局参数)、typeAliases(别名)、Plugins(插件)、objectFactory(对象工厂)、objectWrapperFactory(对象包装工厂)、reflectionFactory(反射工厂)、Settings环境设置)、Environments(数据库环境)、databaseIdProvider(数据库标识)、typeHandlers(类型转换器)、Mappers(映射器)进行初始化,他们都会以类似typeHandler注册的方式被存放到Configuration单例中,供后续使用,其中映射器是尤为重要的内容,它也是MyBatis底层的基础

构建映射器的内部组成

当XMLConfigBuilder解析XML时,会将每一个SQL及其配置的内容都保存起来,通常在MyBatis中一条SQL语句和与其相关的配置信息由三个部分组成,分别是MappedStatementSqlSourceBoundSql,详细说明如下:

  • MappedStatement:是一个类,用于保存一个映射器节点(select/insert/delete/update)的内容,包括配置的SQL、SQL的id、缓存信息、resultMap、parameterType、resultType、languageDriver等内容,它还有一个重要的属性sqlSource。MyBatis通过读取它来获取某条SQL语句配置的所有信息
  • SqlSource:它是MappedStatement的一个属性,是提供BoundSql对象的地方,并且它是一个接口,该接口有几个重要的实现类:DynamicSqlSource、ProviderSqlSource、RawSqlSource、StaticSqlSource。该接口的作用是根据上下文和参数解析生成需要的SQL。该接口定义了一个接口方法getBoundSql(parameterObject),使用它可以得到一个BoundSql对象
  • BoundSql:是一个结果对象,也就是SqlSource通过对SQL和参数的联合解析得到的SQL,BoundSql就是SQL绑定参数的地方,它有3个常用属性:sql、parameterObject和parameterMappings
    在这里插入图片描述
    通过BoundSql的源码可以了解其几个属性的意义:
//
// 这段源代码由IntelliJ IDEA根据.class文件反编译得到
// (使用FernFlower反编译器)
//

package org.apache.ibatis.mapping;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.property.PropertyTokenizer;
import org.apache.ibatis.session.Configuration;

/**
 * BoundSql类封装了SQL语句及其参数的信息。
 * 它包括原始SQL语句、参数映射、参数对象、额外参数以及对这些参数的元数据访问。
 */
public class BoundSql {
    private final String sql; // 原始SQL语句
    private final List<ParameterMapping> parameterMappings; // 参数映射列表
    private final Object parameterObject; // 参数对象
    private final Map<String, Object> additionalParameters; // 额外参数映射
    private final MetaObject metaParameters; // 参数对象的元数据对象,用于动态设置和获取参数值

    /**
     * 构造函数,初始化BoundSql实例。
     *
     * @param configuration MyBatis配置对象
     * @param sql 原始SQL语句
     * @param parameterMappings 参数映射列表
     * @param parameterObject 参数对象
     */
    public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {
        this.sql = sql;
        this.parameterMappings = parameterMappings;
        this.parameterObject = parameterObject;
        this.additionalParameters = new HashMap<>(); // 初始化额外参数映射
        this.metaParameters = configuration.newMetaObject(this.additionalParameters); // 创建元数据对象
    }

    /**
     * 获取原始SQL语句。
     *
     * @return SQL语句字符串
     */
    public String getSql() {
        return this.sql;
    }

    /**
     * 获取参数映射列表。
     *
     * @return 参数映射列表
     */
    public List<ParameterMapping> getParameterMappings() {
        return this.parameterMappings;
    }

    /**
     * 获取参数对象。
     *
     * @return 参数对象
     */
    public Object getParameterObject() {
        return this.parameterObject;
    }

    /**
     * 检查是否存在额外参数。
     *
     * @param name 参数名称
     * @return 如果存在返回true,否则返回false
     */
    public boolean hasAdditionalParameter(String name) {
        String paramName = (new PropertyTokenizer(name)).getName();
        return this.additionalParameters.containsKey(paramName);
    }

    /**
     * 设置额外参数。
     *
     * @param name 参数名称
     * @param value 参数值
     */
    public void setAdditionalParameter(String name, Object value) {
        this.metaParameters.setValue(name, value);
    }

    /**
     * 获取额外参数值。
     *
     * @param name 参数名称
     * @return 参数值
     */
    public Object getAdditionalParameter(String name) {
        return this.metaParameters.getValue(name);
    }
}

BoundSql代表了经过动态解析之后的SQL语句以及相关的参数信息。在MyBatis执行SQL之前,它会将用户编写的动态SQL模板(可能包含#{property}这样的参数占位符)和实际参数值进行绑定,生成具体的SQL字符串以及参数映射关系,以下是BoundSql几个关键属性的详细解释:

  • sql:这是BoundSql中最核心的属性,它是一个字符串,表示经过解析和参数绑定之后的实际SQL语句。这个SQL已经是可直接执行的,其中所有的参数占位符已经被具体的参数值替换。
  • parameterObject:这个属性用来存储执行SQL时需要的参数对象。在动态SQL中,这些参数可能被用来填充到SQL的条件表达式中。它可以是任意类型的对象(包括基本数据类型int、String等等、POJO、Map、@Param注解的参数等等),在传递多个参数时候,如果没有使用@Param注解,则parameterObject会变成Map<String,Object>,其键就是按顺序规划的,值则是传递参数,类似于{“1”:p1, “2”:p2,…},{“param1”:p1,“param2”:p2,…}所以在编写时可以使用#{param1}或者#{1}引用第一个参数,如果使用@Param注解,则parameterObject也会转换成Map<String, Object>。MyBatis会根据需要从中提取参数值。
  • parameterMappings:这是一个列表,包含了SQL中所有参数的映射信息。每个元素通常是一个ParameterMapping对象,记录了参数名、Java类型、JDBC类型等信息。这些信息对于正确地设置PreparedStatement的参数至关重要。
  • resultMapId(如果适用):在某些情况下,BoundSql可能会包含一个引用到外部定义的ResultMap的ID。这个ResultMap定义了如何将查询结果映射到Java对象上。这有助于MyBatis在查询后处理结果集。
  • additionalParameters(如果存在):这个属性可能包含一些额外的参数,这些参数可能不是直接从parameterObject来的,而是解析SQL时动态添加的,例如通过<foreach>标签循环生成的参数列表。

BoundSql的作用在于它隔离了SQL语句的构建逻辑和执行逻辑,使得SQL的动态生成和参数绑定更加灵活和高效,同时也便于调试和维护,因为你可以直接查看生成的SQL语句以及对应的参数信息。在MyBatis的工作流程中,BoundSql通常是通过SqlSource进一步处理后得到的,SqlSource负责根据不同的输入动态生成BoundSql实例。

构建SqlSessionFactory

/**
 * SqlSessionFactoryUtils 是一个用于获取 SqlSessionFactory 和基于该工厂打开 SqlSession 的工具类。
 * 该类使用单例模式确保 SqlSessionFactory 的唯一性,并提供线程安全的获取方式。
 */
package com.ssm.utils;

import java.io.IOException;
import java.io.InputStream;

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

public class SqlSessionFactoryUtils {

    // 用作同步锁的类对象
    private final static Class<SqlSessionFactoryUtils> LOCK = SqlSessionFactoryUtils.class;

    // SqlSessionFactory 单例
    private static SqlSessionFactory sqlSessionFactory = null;

    // 私有构造方法防止实例化
    private SqlSessionFactoryUtils() {
    }

    /**
     * 获取 SqlSessionFactory 实例。
     * 如果尚未初始化,则通过读取 "mybatis-config.xml" 配置文件来创建一个新的实例。
     * 
     * @return SqlSessionFactory 数据库会话工厂实例
     */
    public static SqlSessionFactory getSqlSessionFactory() {
        synchronized (LOCK) {
            if (sqlSessionFactory != null) {
                return sqlSessionFactory;
            }
            String resource = "mybatis-config.xml";
            InputStream inputStream;
            try {
                inputStream = Resources.getResourceAsStream(resource);
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }
            return sqlSessionFactory;
        }
    }
    
    /**
     * 打开一个基于当前 SqlSessionFactory 的 SqlSession。
     * 如果 SqlSessionFactory 尚未初始化,则先调用 getSqlSessionFactory 方法进行初始化。
     * 
     * @return SqlSession 数据库会话实例
     */
    public static SqlSession openSqlSession() {
        if (sqlSessionFactory == null) {
            getSqlSessionFactory();
        }
        return sqlSessionFactory.openSession();
    }
}

再看一下Builder方法的实现代码

/**
 * SqlSessionFactoryBuilder 类用于构建 SqlSessionFactory 实例。
 * 通过不同的构建方法,可以使用 Reader 或 InputStream 以及可选的环境配置和属性来构建 SqlSessionFactory。
 */
package org.apache.ibatis.session;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.Properties;
import org.apache.ibatis.builder.xml.XMLConfigBuilder;
import org.apache.ibatis.exceptions.ExceptionFactory;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;

public class SqlSessionFactoryBuilder {
    /**
     * 构造函数:初始化 SqlSessionFactoryBuilder 实例。
     */
    public SqlSessionFactoryBuilder() {
    }

    /**
     * 使用 Reader 和默认环境及属性构建 SqlSessionFactory。
     *
     * @param reader 配置文件的 Reader
     * @return 构建好的 SqlSessionFactory 实例
     */
    public SqlSessionFactory build(Reader reader) {
        return this.build((Reader)reader, (String)null, (Properties)null);
    }

    /**
     * 使用 Reader 和指定环境构建 SqlSessionFactory,不使用属性。
     *
     * @param reader      配置文件的 Reader
     * @param environment 环境配置
     * @return 构建好的 SqlSessionFactory 实例
     */
    public SqlSessionFactory build(Reader reader, String environment) {
        return this.build((Reader)reader, environment, (Properties)null);
    }

    /**
     * 使用 Reader 和属性构建 SqlSessionFactory,不指定环境。
     *
     * @param reader      配置文件的 Reader
     * @param properties  额外的属性配置
     * @return 构建好的 SqlSessionFactory 实例
     */
    public SqlSessionFactory build(Reader reader, Properties properties) {
        return this.build((Reader)reader, (String)null, properties);
    }

    /**
     * 使用 Reader、指定环境和属性构建 SqlSessionFactory。
     *
     * @param reader      配置文件的 Reader
     * @param environment 环境配置
     * @param properties  额外的属性配置
     * @return 构建好的 SqlSessionFactory 实例
     */
    public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
            // 解析配置文件
            XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
            var5 = this.build(parser.parse());
        } catch (Exception var14) {
            // 构建过程中发生异常
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            // 重置错误上下文并关闭 Reader
            ErrorContext.instance().reset();
            try {
                reader.close();
            } catch (IOException var13) {
                // 忽略关闭 Reader 时的异常
            }
        }

        return var5;
    }

    /**
     * 使用 InputStream 和默认环境及属性构建 SqlSessionFactory。
     *
     * @param inputStream 配置文件的 InputStream
     * @return 构建好的 SqlSessionFactory 实例
     */
    public SqlSessionFactory build(InputStream inputStream) {
        return this.build((InputStream)inputStream, (String)null, (Properties)null);
    }

    /**
     * 使用 InputStream 和指定环境构建 SqlSessionFactory,不使用属性。
     *
     * @param inputStream      配置文件的 InputStream
     * @param environment      环境配置
     * @return 构建好的 SqlSessionFactory 实例
     */
    public SqlSessionFactory build(InputStream inputStream, String environment) {
        return this.build((InputStream)inputStream, environment, (Properties)null);
    }

    /**
     * 使用 InputStream 和属性构建 SqlSessionFactory,不指定环境。
     *
     * @param inputStream      配置文件的 InputStream
     * @param properties       额外的属性配置
     * @return 构建好的 SqlSessionFactory 实例
     */
    public SqlSessionFactory build(InputStream inputStream, Properties properties) {
        return this.build((InputStream)inputStream, (String)null, properties);
    }

    /**
     * 使用 InputStream、指定环境和属性构建 SqlSessionFactory。
     *
     * @param inputStream      配置文件的 InputStream
     * @param environment      环境配置
     * @param properties       额外的属性配置
     * @return 构建好的 SqlSessionFactory 实例
     */
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
            // 解析配置文件
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            var5 = this.build(parser.parse());
        } catch (Exception var14) {
            // 构建过程中发生异常
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            // 重置错误上下文并关闭 InputStream
            ErrorContext.instance().reset();
            try {
                inputStream.close();
            } catch (IOException var13) {
                // 忽略关闭 InputStream 时的异常
            }
        }

        return var5;
    }

    /**
     * 使用 Configuration 对象构建 SqlSessionFactory。
     *
     * @param config Configuration 配置对象
     * @return 构建好的 SqlSessionFactory 实例
     */
    public SqlSessionFactory build(Configuration config) {
        // 使用默认实现创建 SqlSessionFactory
        return new DefaultSqlSessionFactory(config);
    }
}

MyBatis会根据文件流生成Configuration对象,进而构建SqlSessionFactory对象,很显然核心核心在于构建Configuration对象

SqlSession运行过程

映射器的动态代理

获取映射器(Mapper)方法

RoleDao roleDao = sqlSession.getMapper(RoleDao.class);

getMapper是SqlSession的接口方法,代码如下

/**
 * 获取指定类型的映射器实例。
 * 
 * @param var1 要映射的类的Class对象,用于指定返回哪种类型的映射器。
 * @return 返回一个该类型的映射器实例。泛型T确保了返回的映射器类型与传入的类类型一致。
 */
<T> T getMapper(Class<T> var1);

而DefaultSqlSession类和SqlSessionManager都实现了这个该方法

//DefaultSqlSession
/**
 * 根据指定的类型获取映射器实例。
 * 
 * @param type 指定映射器接口的 Class 对象,用于获取对应的映射器实例。
 * @return 返回指定类型 T 的映射器实例。
 */
public <T> T getMapper(Class<T> type) {
    // 通过配置获取指定类型的映射器实例
    return this.configuration.getMapper(type, this);
}

//SqlSessionManager
/**
 * 获取指定类型的Mapper实例。
 * 
 * @param type 指定的Mapper接口类型,通过该类型确定要获取的Mapper实例。
 * @return 返回根据指定类型创建的Mapper实例。
 * @param <T> Mapper接口的类型参数。
 */
public <T> T getMapper(Class<T> type) {
    // 通过配置获取指定类型的Mapper实例
    return this.getConfiguration().getMapper(type, this);
}

很显然它用到了Configuration对象的getMapper方法获取对应的接口对象,再深究一下,看一下Configuration类中该方法的定义

/**
 * 根据给定的类型获取映射器实例。
 * 
 * @param type 映射器接口的Class对象,指定要获取的映射器类型。
 * @param sqlSession 当前的SQL会话实例,用于获取映射器实例。
 * @return 返回指定类型T的映射器实例。
 */
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 通过mapperRegistry获取指定type的映射器实例
    return this.mapperRegistry.getMapper(type, sqlSession);
}

该方法又用到了mapperRegistry对象,再看一下这个源码

//
// MapperRegistry 类提供了管理Mapper接口实例的功能,它能够注册Mapper接口、获取Mapper接口实例,并检查是否已注册某个Mapper接口。
//

package org.apache.ibatis.binding;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.ibatis.builder.annotation.MapperAnnotationBuilder;
import org.apache.ibatis.io.ResolverUtil;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;

public class MapperRegistry {
    // Configuration对象,用于访问MyBatis的配置信息
    private final Configuration config;
    // 用于存储已知Mapper接口及其对应的MapperProxyFactory对象
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();

    /**
     * 构造函数,初始化一个MapperRegistry实例。
     *
     * @param config MyBatis的配置对象。
     */
    public MapperRegistry(Configuration config) {
        this.config = config;
    }

    /**
     * 获取指定Mapper接口的实例。
     *
     * @param type Mapper接口的类型。
     * @param sqlSession 当前的SqlSession实例。
     * @return Mapper接口的实例。
     * @throws BindingException 如果未找到对应的Mapper接口或创建实例时发生错误。
     */
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }

    /**
     * 检查是否已注册指定的Mapper接口。
     *
     * @param type Mapper接口的类型。
     * @return 如果已注册返回true,否则返回false。
     */
    public <T> boolean hasMapper(Class<T> type) {
        return this.knownMappers.containsKey(type);
    }

    /**
     * 注册一个Mapper接口。
     *
     * @param type 要注册的Mapper接口的类型。
     * @throws BindingException 如果接口已注册或不是接口类型。
     */
    public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
            if (this.hasMapper(type)) {
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }

            boolean loadCompleted = false;

            try {
                this.knownMappers.put(type, new MapperProxyFactory(type));
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    this.knownMappers.remove(type);
                }

            }
        }

    }

    /**
     * 获取已注册的所有Mapper接口类型。
     *
     * @return 不可修改的Mapper接口类型集合。
     */
    public Collection<Class<?>> getMappers() {
        return Collections.unmodifiableCollection(this.knownMappers.keySet());
    }

    /**
     * 根据指定的包名和超类型,添加包中所有的Mapper接口。
     *
     * @param packageName 要扫描的包名。
     * @param superType 所有Mapper接口需要继承的超类型。
     */
    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) {
            this.addMapper(mapperClass);
        }
    }

    /**
     * 根据指定的包名,添加包中所有的Mapper接口,超类型默认为Object类。
     *
     * @param packageName 要扫描的包名。
     */
    public void addMappers(String packageName) {
        this.addMappers(packageName, Object.class);
    }
}


getMapper方法中,首先判断是否是否是已经注册过的Mapper,如果不是则会抛异常,如果是则会启用MapperProxyFactory生成一个代理实例,再深追一下源码

package org.apache.ibatis.binding;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.ibatis.session.SqlSession;

/**
 * MapperProxyFactory类用于创建Mapper接口的代理实例。
 * 
 * @param <T> 泛型参数,指定Mapper接口的类型。
 */
public class MapperProxyFactory<T> {
    // Mapper接口的Class对象。
    private final Class<T> mapperInterface;
    // 方法缓存,用于缓存Mapper方法,以Method为键,MapperMethod为值。
    private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();

    /**
     * 构造函数,用于初始化MapperProxyFactory。
     * 
     * @param mapperInterface Mapper接口的Class对象。
     */
    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    /**
     * 获取Mapper接口的Class对象。
     * 
     * @return 返回Mapper接口的Class对象。
     */
    public Class<T> getMapperInterface() {
        return this.mapperInterface;
    }

    /**
     * 获取方法缓存。
     * 
     * @return 返回方法缓存,是一个ConcurrentHashMap,以Method为键,MapperMethod为值。
     */
    public Map<Method, MapperMethod> getMethodCache() {
        return this.methodCache;
    }

    /**
     * 创建一个新的Mapper接口代理实例。
     * 
     * @param mapperProxy MapperProxy实例,代理逻辑的具体实现。
     * @return 返回一个代理的Mapper接口实例。
     */
    protected T newInstance(MapperProxy<T> mapperProxy) {
        // 使用Java动态代理创建一个新的Mapper接口实例。
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

    /**
     * 基于给定的SqlSession创建一个新的Mapper接口代理实例。
     * 
     * @param sqlSession 当前的SqlSession实例,用于执行SQL操作。
     * @return 返回一个代理的Mapper接口实例。
     */
    public T newInstance(SqlSession sqlSession) {
        // 创建一个新的MapperProxy实例,并传入SqlSession和其他必要参数。
        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        // 通过MapperProxy实例创建并返回一个代理的Mapper接口实例。
        return this.newInstance(mapperProxy);
    }
}

代码中可见,Mapper映射通过动态代理实现,从newInstance(SqlSession)方法中,可以看出使用了methodCache,从而提高了性能,代码中我们看到动态代理对接口的绑定,而代理方法则被放到了MapperProxy类中,再看一下该类源码

//
// MapperProxy 类提供动态代理功能,用于拦截对 Mapper 接口的方法调用,
// 并将这些调用转发给相应的 MapperMethod 进行处理。
//

package org.apache.ibatis.binding;

import java.io.Serializable;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
import org.apache.ibatis.reflection.ExceptionUtil;
import org.apache.ibatis.session.SqlSession;

public class MapperProxy<T> implements InvocationHandler, Serializable {
    private static final long serialVersionUID = -6424540398559729838L;
    private static final int ALLOWED_MODES = 15;
    private static final Constructor<MethodHandles.Lookup> lookupConstructor;
    private static final Method privateLookupInMethod;
    // sqlSession 提供与 MyBatis 会话交互的能力。
    private final SqlSession sqlSession;
    // mapperInterface 是代理的 Mapper 接口。
    private final Class<T> mapperInterface;
    // methodCache 用于缓存 Mapper 方法,以提高查找效率。
    private final Map<Method, MapperMethod> methodCache;

    /**
     * 构造函数
     * 
     * @param sqlSession 当前的 SqlSession 实例
     * @param mapperInterface 需要代理的 Mapper 接口
     * @param methodCache Mapper 方法的缓存
     */
    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }

     /**
     * 对代理对象上的方法进行调用。
     * 
     * @param proxy 代理对象,代表被代理的实际对象。
     * @param method 被调用的方法。
     * @param args 方法调用时传入的参数数组。
     * @return 方法执行的结果。
     * @throws Throwable 如果方法执行过程中发生异常,则抛出。
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            // 处理Object类的方法调用
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }

            // 处理默认方法的调用
            if (method.isDefault()) {
                // Java 8及之前版本的默认方法调用实现
                if (privateLookupInMethod == null) {
                    return this.invokeDefaultMethodJava8(proxy, method, args);
                }

                // Java 9及之后版本的默认方法调用实现
                return this.invokeDefaultMethodJava9(proxy, method, args);
            }
        } catch (Throwable var5) {
            // 异常处理,将包装过的异常抛出
            throw ExceptionUtil.unwrapThrowable(var5);
        }

        // 获取并执行映射器方法
        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        return mapperMethod.execute(this.sqlSession, args);
    }


    /**
     * 缓存Mapper方法。如果给定的方法尚未被缓存,则创建一个新的MapperMethod实例并将其添加到缓存中。
     * 
     * @param method 要缓存的Mapper接口方法。
     * @return 缓存中或新创建的MapperMethod实例。
     */
    private MapperMethod cachedMapperMethod(Method method) {
        // 使用方法作为键,如果该方法不在缓存中,则计算并添加一个新的MapperMethod实例
        return (MapperMethod)this.methodCache.computeIfAbsent(method, (k) -> {
            // 创建一个新的MapperMethod实例,与给定的mapper接口方法相关联,并使用当前的SQLSession配置
            return new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
        });
    }



	/**
 	 * 在Java 9及更高版本中,通过私有lookup调用对象的默认方法。
 	 * 
 	 * @param proxy 代理对象,即调用默认方法的对象。
 	 * @param method 要调用的方法对象,该方法属于代理对象的接口。
 	 * @param args 调用方法时传递的参数数组。
 	 * @return 调用方法后的返回值,其类型为Object。
 	 * @throws Throwable 如果方法调用过程中发生异常,则抛出Throwable。
 	 */
	private Object invokeDefaultMethodJava9(Object proxy, Method method, Object[] args) throws Throwable {
    	// 获取方法声明的类
    	Class<?> declaringClass = method.getDeclaringClass();
    	// 使用反射和方法句柄技术调用默认方法
    	return ((MethodHandles.Lookup)privateLookupInMethod.invoke((Object)null, declaringClass, MethodHandles.lookup())).findSpecial(declaringClass, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()), declaringClass).bindTo(proxy).invokeWithArguments(args);
	}


	/**
  	 * 使用Java 8的动态代理技术,调用对象的默认方法。
     *
 	 * @param proxy 代理对象,代表正在调用方法的代理实例。
 	 * @param method 被调用的方法,包含方法的所有信息,如方法名、返回类型、参数类型等。
 	 * @param args 方法调用的参数数组。
 	 * @return 调用方法后的返回结果,其类型依据方法的返回类型而定。
 	 * @throws Throwable 如果方法调用过程中发生异常,则会抛出。
 	 */
	private Object invokeDefaultMethodJava8(Object proxy, Method method, Object[] args) throws Throwable {
    	// 获取方法所属的类
    	Class<?> declaringClass = method.getDeclaringClass();
    	// 使用MethodHandles.Lookup反射调用指定类的方法,并绑定到代理对象上,最后执行方法并传入参数
    	return ((MethodHandles.Lookup)lookupConstructor.newInstance(declaringClass, 15)).unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
	}


    /**
     * 静态初始化块,用于获取私有lookup方法或构造函数,以便后续能够访问私有方法。
     * 这段代码首先尝试获取MethodHandles类中的"privateLookupIn"方法,
     * 如果失败,则尝试获取MethodHandles.Lookup类的特定构造函数。
     * 如果两者都无法获取,则抛出IllegalStateException异常。
     */
    static {
        Method privateLookupIn;
        // 尝试获取"privateLookupIn"方法
        try {
            privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
        } catch (NoSuchMethodException var5) {
            privateLookupIn = null;
        }

        privateLookupInMethod = privateLookupIn;

        Constructor<MethodHandles.Lookup> lookup = null;
        // 如果"privateLookupIn"方法获取失败,尝试获取Lookup类的特定构造函数
        if (privateLookupInMethod == null) {
            try {
                lookup = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, Integer.TYPE);
                lookup.setAccessible(true);
            } catch (NoSuchMethodException var3) {
                // 如果既没有找到"privateLookupIn"方法,也没有找到合适的构造函数,抛出异常
                throw new IllegalStateException("There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.", var3);
            } catch (Throwable var4) {
                lookup = null;
            }
        }

        lookupConstructor = lookup;
    }

}

这里目光聚焦在invoke方法中,最后落到了execute方法,再看一下该方法的相关源码

/**
 * MapperMethod 类用于封装 MyBatis 中映射器接口方法的信息,包括 SQL 命令和方法签名。
 */
public class MapperMethod {
    private final SqlCommand command; // 封装了 SQL 命令信息
    private final MethodSignature method; // 封装了方法签名信息

    /**
     * 构造函数用于初始化一个 MapperMethod 实例。
     * 
     * @param mapperInterface 映射器接口的 Class 对象,用于获取接口信息。
     * @param method 映射器接口中具体的方法,用于获取方法信息。
     * @param config MyBatis 的配置信息,用于配置 SQL 命令和方法签名。
     */
    public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
        this.command = new SqlCommand(config, mapperInterface, method); // 初始化 SQL 命令
        this.method = new MethodSignature(config, mapperInterface, method); // 初始化方法签名
    }

    /**
     * 根据给定的 SqlSession 和参数执行相应的 SQL 命令。
     * 
     * @param sqlSession 提供数据库会话功能的对象。
     * @param args 方法执行时需要的参数数组。
     * @return 执行 SQL 命令后的结果对象。返回的对象类型取决于 SQL 命令的类型和方法的配置。
     */
    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result; // 函数执行结果
        Object param; // SQL 命令参数
        
        // 根据命令类型执行相应的 SQL 操作
        switch (this.command.getType()) {
            case INSERT:
                // 处理插入操作,转换参数并执行插入,返回影响的行数
                param = this.method.convertArgsToSqlCommandParam(args);
                result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
                break;
            case UPDATE:
                // 处理更新操作,转换参数并执行更新,返回影响的行数
                param = this.method.convertArgsToSqlCommandParam(args);
                result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
                break;
            case DELETE:
                // 处理删除操作,转换参数并执行删除,返回影响的行数
                param = this.method.convertArgsToSqlCommandParam(args);
                result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
                break;
            case SELECT:
                // 处理选择操作,根据方法的返回类型执行相应的处理逻辑
                if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                    // 如果方法返回 void 且设置了结果处理器,则执行结果处理,结果为 null
                    this.executeWithResultHandler(sqlSession, args);
                    result = null;
                } else if (this.method.returnsMany()) {
                    // 如果方法返回多个结果,则执行并返回多个结果
                    result = this.executeForMany(sqlSession, args);
                } else if (this.method.returnsMap()) {
                    // 如果方法返回一个 Map,则执行并返回该 Map
                    result = this.executeForMap(sqlSession, args);
                } else if (this.method.returnsCursor()) {
                    // 如果方法返回一个游标,则执行并返回该游标
                    result = this.executeForCursor(sqlSession, args);
                } else {
                    // 其他情况,执行选择并可能对结果进行 Optional 包装
                    param = this.method.convertArgsToSqlCommandParam(args);
                    result = sqlSession.selectOne(this.command.getName(), param);
                    if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
                        result = Optional.ofNullable(result);
                    }
                }
                break;
            case FLUSH:
                // 处理刷新操作,执行刷新语句并返回结果
                result = sqlSession.flushStatements();
                break;
            default:
                // 如果遇到未知的执行方法,抛出异常
                throw new BindingException("Unknown execution method for: " + this.command.getName());
        }

        // 检查结果是否为 null,并且方法的返回类型是否为原始类型。如果是,则抛出异常
        if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
            throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
        } else {
            return result;
        }
    }
	......
}
    /**
     * 对于给定的SqlSession和参数数组,执行SQL命令,并根据方法的返回类型处理和返回结果。
     * 
     * @param sqlSession 当前的SqlSession,用于执行SQL命令。
     * @param args 参数数组,用于构建SQL命令的参数。
     * @return 根据方法的返回类型处理后的结果。如果方法期望的返回类型与实际查询结果的类型不匹配,
     *         将尝试转换结果类型以匹配期望的返回类型。
     * @param <E> 泛型参数,指示结果列表的元素类型。
     */
    private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
        // 将传入的参数数组转换为SQL命令所需的参数
        Object param = this.method.convertArgsToSqlCommandParam(args);
        List result;
        
        // 检查方法是否使用了RowBounds来限制结果集
        if (this.method.hasRowBounds()) {
            // 从参数数组中提取RowBounds对象
            RowBounds rowBounds = this.method.extractRowBounds(args);
            // 使用RowBounds执行查询,并返回结果列表
            result = sqlSession.selectList(this.command.getName(), param, rowBounds);
        } else {
            // 无限制查询,返回结果列表
            result = sqlSession.selectList(this.command.getName(), param);
        }

        // 检查实际返回类型是否与方法期望的返回类型匹配
        if (!this.method.getReturnType().isAssignableFrom(result.getClass())) {
            // 如果不匹配,尝试将结果转换为方法期望的类型(数组或集合)
            return this.method.getReturnType().isArray() ? this.convertToArray(result) : this.convertToDeclaredCollection(sqlSession.getConfiguration(), result);
        } else {
            // 如果匹配,直接返回查询结果
            return result;
        }
    }

这两个方法都定义在MapperMethod类中,如此就知道MyBatis为什么只用Mapper接口便能运行了,因为Mapper的XML文件的命名空间对应的是这个接口的全限定名,而方法对应SQL的id,这样MyBatis就可以根据全路径和方法名,将其和代理对象绑定起来,通过动态代理技术,让这个接口运行起来,在逻辑中采用命令模式,最后使用SqlSession接口的方法使得它能够执行对应的SQL,如此也实现了接口编程

SqlSession运行原理

实际上SqlSession使通过Executor、StatementHandler、ParameterHandler和ResultSetHandler完成对数据库操作和结果返回的
在这里插入图片描述

  • Executor:执行器,由它调度StatementHandler、ParameterHandler、ResultSetHandler等执行对应的SQL
  • StatementHandler:数据库会话处理器,相当于JDBC的Statement(PreparedStatement)执行操作,许多重要的插件都是通过拦截它实现的
  • ParameterHandler:SQL参数处理器
  • ResultSetHandler:结果集处理器
Executor

Executor是一个执行器,SqlSession其实是个门面,实际干活的是Executor,它是一个执行Java和数据库交互的对象,MyBatis中有3种执行器,可以在配置文件的settings元素中进行配置,Executor的代码定义在Configuration类中,如下所示

    /**
     * 创建一个新的Executor实例,根据提供的Transaction对象和默认的ExecutorType。
     * 
     * @param transaction 事务对象,用于执行操作。
     * @return 返回一个初始化后的Executor实例。
     */
    public Executor newExecutor(Transaction transaction) {
        return this.newExecutor(transaction, this.defaultExecutorType);
    }

    /**
     * 创建一个新的Executor实例,根据提供的Transaction对象和指定的ExecutorType。
     * 
     * @param transaction 事务对象,用于执行操作。
     * @param executorType 执行器类型,指定Executor的行为。
     * @return 返回一个根据指定执行器类型初始化后的Executor实例。
     */
    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        // 设置默认的执行器类型,如果未指定
        executorType = executorType == null ? this.defaultExecutorType : executorType;
        // 确保executorType不为空,若为空则默认为SIMPLE类型
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;

        Object executor;
        // 根据executorType创建相应的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);
        }

        // 如果启用了缓存,包装executor为CachingExecutor
        if (this.cacheEnabled) {
            executor = new CachingExecutor((Executor)executor);
        }

        // 通过拦截器链对executor进行插件化处理
        Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
        return executor;
    }

最后插件化处理的代码,就是植入插件的代码,它将会构建一层层的动态代理对象拦截Executor的执行,在调度真实的Executor方法前后执行插件的代码

再看一下如果类型是SIMPLE的doQuery代码

    /**
     * 执行查询操作,并返回查询结果列表。
     * 
     * @param ms MappedStatement,代表一个SQL映射语句
     * @param parameter 查询参数
     * @param rowBounds 数据库查询的行范围限制
     * @param resultHandler 结果处理器,用于自定义结果集处理
     * @param boundSql 绑定的SQL对象,包含实际执行的SQL语句和参数
     * @return 返回查询结果列表
     * @throws SQLException 如果执行SQL语句时发生错误,则抛出SQLException
     */
    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;

        List var9;
        try {
            // 创建StatementHandler,用于处理SQL语句
            Configuration configuration = ms.getConfiguration();
            StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            // 准备SQL语句
            stmt = this.prepareStatement(handler, ms.getStatementLog());
            // 执行查询,并处理结果
            var9 = handler.query(stmt, resultHandler);
        } finally {
            // 无论查询成功与否,最后都关闭Statement资源
            this.closeStatement(stmt);
        }

        return var9;
    }

    /**
     * 执行查询操作,并返回一个游标(Cursor),用于逐行处理结果。
     * 
     * @param ms MappedStatement,代表一个SQL映射语句
     * @param parameter 查询参数
     * @param rowBounds 数据库查询的行范围限制
     * @param boundSql 绑定的SQL对象,包含实际执行的SQL语句和参数
     * @return 返回一个游标对象,用于逐行处理查询结果
     * @throws SQLException 如果执行SQL语句时发生错误,则抛出SQLException
     */
    protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
        // 创建StatementHandler,用于处理SQL语句
        Configuration configuration = ms.getConfiguration();
        StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, (ResultHandler)null, boundSql);
        // 准备SQL语句并执行查询,返回游标
        Statement stmt = this.prepareStatement(handler, ms.getStatementLog());
        Cursor<E> cursor = handler.queryCursor(stmt);
        // 确保Statement在游标使用完毕后关闭
        stmt.closeOnCompletion();
        return cursor;
    }
    /**
     * 准备SQL语句。
     * 
     * @param handler Statement处理器
     * @param statementLog SQL语句日志
     * @return 返回准备好的Statement对象
     * @throws SQLException 如果准备语句失败,则抛出SQLException
     */
    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        // 获取数据库连接
        Connection connection = this.getConnection(statementLog);
        // 准备SQL语句并设置参数
        Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
        handler.parameterize(stmt);
        return stmt;
    }

代码说明看注释即可,不难看出实际上其焦点还是在StatementHandler对象上

StatementHandler

StatementHandler的构建方法代码也是在Configuration类里实现的

    /**
     * 创建一个新的StatementHandler实例。
     * 这个方法首先创建一个RoutingStatementHandler实例,然后通过拦截器链对它进行插件化处理。
     * 
     * @param executor 执行器,用于执行SQL语句。
     * @param mappedStatement 映射语句对象,包含了SQL语句的配置信息。
     * @param parameterObject 参数对象,用于SQL语句的参数绑定。
     * @param rowBounds 行边界对象,用于指定查询的起始行和行数。
     * @param resultHandler 结果处理器,用于处理查询结果。
     * @param boundSql 绑定的SQL对象,包含了最终将要执行的SQL语句及其参数。
     * @return 经过插件化处理的StatementHandler实例。
     */
    public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        // 创建RoutingStatementHandler实例
        StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
        
        // 通过拦截器链对StatementHandler进行插件化处理
        statementHandler = (StatementHandler)this.interceptorChain.pluginAll(statementHandler);
        
        return statementHandler;
    }

代码中不难看出,实际构建的对象是RoutingStatementHandler对象,它实现了StatementHandler接口,代码如下

/**
 * RoutingStatementHandler类,用于根据MappedStatement的类型选择合适的StatementHandler。
 * 该类实现了StatementHandler接口,将具体的处理逻辑委托给不同的StatementHandler实现。
 *
 * @param executor 执行器,用于执行SQL
 * @param ms MappedStatement,包含了一个SQL语句的所有配置信息
 * @param parameter 参数对象,用于SQL语句的参数绑定
 * @param rowBounds 行限制,用于指定查询的结果集起始位置和行数
 * @param resultHandler 结果处理器,用于处理查询结果
 * @param boundSql 绑定的SQL对象,包含实际执行的SQL语句和参数信息
 */
public class RoutingStatementHandler implements StatementHandler {
    private final StatementHandler delegate;

    /**
     * 构造函数,根据MappedStatement的类型选择合适的StatementHandler。
     * 
     * @throws ExecutorException 如果MappedStatement的类型未知
     */
    public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        // 根据不同的语句类型选择不同的StatementHandler实现
        switch (ms.getStatementType()) {
            case STATEMENT:
                // 对于普通SQL语句,使用SimpleStatementHandler
                this.delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                break;
            case PREPARED:
                // 对于预编译SQL语句,使用PreparedStatementHandler
                this.delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                break;
            case CALLABLE:
                // 对于存储过程调用,使用CallableStatementHandler
                this.delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                break;
            default:
                // 如果出现未知的语句类型,抛出异常
                throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
        }
    }
 ......

RoutingStatementHandler通过组合模式根据类型找到对应的具体StatementHandler来执行逻辑,在MyBatis中于Executor一样,RoutingStatementHandler分为3种,如代码中所示他们对应JDBC的Statement、PreparedStatement(预编译处理)、CallableStatement(存储过程处理)

RoutingStatementHandler中定义了一个对象的代理delegate,它是一个StatementHandler接口对象,然后构造方法根据类型构建对应的StatementHandler对象,delegate的作用是给三个不同的StatementHandler接口实现类提供一个统一且简易的代理,便于外部使用

以PreparedStatementHandler为例,Executor执行查询时候会调用StatementHandler的prepare、parameterize和query方法,其中PreparedStatementHandler的prepare方法来自其父类BaseStatementHandler,源码如下:

    /**
     * 准备一个Statement对象。
     * 
     * @param connection 用于准备语句的数据库连接。
     * @param transactionTimeout 事务超时时间,可能为null。
     * @return 配置好的Statement对象。
     * @throws SQLException 如果准备语句时发生数据库错误。
     * @throws ExecutorException 如果准备语句过程中发生非SQLException异常。
     */
    public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
        // 设置当前错误上下文的SQL语句。
        ErrorContext.instance().sql(this.boundSql.getSql());
        Statement statement = null;

        try {
            // 实例化一个Statement对象。
            statement = this.instantiateStatement(connection);
            // 设置Statement的超时时间。
            this.setStatementTimeout(statement, transactionTimeout);
            // 设置Statement的获取大小。
            this.setFetchSize(statement);
            return statement;
        } catch (SQLException var5) {
            // 在发生SQLException时关闭Statement,并重新抛出SQLException。
            this.closeStatement(statement);
            throw var5;
        } catch (Exception var6) {
            // 在发生其他异常时关闭Statement,并抛出ExecutorException。
            this.closeStatement(statement);
            throw new ExecutorException("Error preparing statement.  Cause: " + var6, var6);
        }
    }

parameterize和query源码如下

    /**
     * 将参数设置到给定的PreparedStatement中。
     * 这个方法通过调用parameterHandler的setParameters方法,来设置Statement中的参数。
     * 
     * @param statement 需要设置参数的Statement对象,这里预期为PreparedStatement类型。
     * @throws SQLException 如果在设置参数过程中发生SQL异常,则抛出SQLException。
     */
    public void parameterize(Statement statement) throws SQLException {
        // 将参数设置到PreparedStatement中
        this.parameterHandler.setParameters((PreparedStatement)statement);
    }

/**
 * 执行查询操作,并通过结果处理器处理结果。
 * 
 * @param statement 提供一个已经准备好的SQL语句,用于执行查询。
 * @param resultHandler 一个结果处理器,用于根据查询结果生成特定的返回值。
 * @return 返回一个列表,包含通过结果处理器处理后的结果集。
 * @throws SQLException 如果执行SQL语句或处理结果集时发生错误,则抛出SQLException。
 */
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    // 将Statement转换为PreparedStatement,尽管此处没有显式地使用参数statement的特性
    PreparedStatement ps = (PreparedStatement)statement;
    ps.execute(); // 执行查询语句
    // 使用结果集处理器处理查询结果,并返回处理后的结果
    return this.resultSetHandler.handleResultSets(ps);
}

在执行SQL之前,参数和SQL都被prepare方法预编译,参数在parameterize方法中已经进行了设置,所以只要执行SQL再返回就行了,因此我们看到了ResultSetHandler对结果的封装和返回

不难看出,查询SQL的执行过程,Executor调用StatemenHandler的prepare方法预编译SQL,同时设置一些基本运行参数,然后Executor调用StatementHandler的parameterize方法启用ParameterHandler设置参数,完成预编译,然后MyBatis会使用ResultSetHandler封装结果并返回

ParameterHandler

该接口代码如下

/**
 * 参数处理器接口,用于处理SQL语句执行时的参数设置。
 * 提供了将参数对象设置到PreparedStatement中的能力。
 */
public interface ParameterHandler {

    /**
     * 获取参数对象。
     * 
     * @return 参数对象,其类型依赖于具体实现。
     */
    Object getParameterObject();

    /**
     * 将参数设置到给定的PreparedStatement中。
     * 
     * @param var1 预编译的SQL语句对象,参数将被设置到这个PreparedStatement对象中。
     * @throws SQLException 如果设置参数时发生SQL异常。
     */
    void setParameters(PreparedStatement var1) throws SQLException;
}

该接口有个实现类,代码如下

package org.apache.ibatis.scripting.defaults;

// 导入java.sql和org.apache.ibatis相关包

/**
 * 默认参数处理器实现类,用于处理SQL语句执行时的参数设置。
 */
public class DefaultParameterHandler implements ParameterHandler {
    // 类型处理器注册表、映射语句、参数对象、绑定的SQL和配置对象
    private final TypeHandlerRegistry typeHandlerRegistry;
    private final MappedStatement mappedStatement;
    private final Object parameterObject;
    private final BoundSql boundSql;
    private final Configuration configuration;

    /**
     * 构造函数,初始化参数处理器。
     *
     * @param mappedStatement 映射语句对象,包含执行SQL所需的所有配置信息。
     * @param parameterObject 执行SQL语句时需要的参数对象。
     * @param boundSql 绑定的SQL对象,包含实际要执行的SQL和参数信息。
     */
    public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
        this.mappedStatement = mappedStatement;
        this.configuration = mappedStatement.getConfiguration();
        this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
        this.parameterObject = parameterObject;
        this.boundSql = boundSql;
    }

    /**
     * 获取参数对象。
     *
     * @return 执行SQL语句时需要的参数对象。
     */
    public Object getParameterObject() {
        return this.parameterObject;
    }

    /**
     * 设置PreparedStatement的参数。
     *
     * @param ps PreparedStatement对象,用于执行SQL语句。
     * @throws TypeException 如果类型转换失败。
     */
    public void setParameters(PreparedStatement ps) {
        // 初始化错误上下文
        ErrorContext.instance().activity("setting parameters").object(this.mappedStatement.getParameterMap().getId());
        // 获取参数映射列表
        List<ParameterMapping> parameterMappings = this.boundSql.getParameterMappings();
        if (parameterMappings != null) {
            for(int i = 0; i < parameterMappings.size(); ++i) {
                ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);
                // 排除输出参数
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    String propertyName = parameterMapping.getProperty();
                    Object value;
                    // 优先从额外参数中获取值,然后从参数对象中获取
                    if (this.boundSql.hasAdditionalParameter(propertyName)) {
                        value = this.boundSql.getAdditionalParameter(propertyName);
                    } else if (this.parameterObject == null) {
                        value = null;
                    } else if (this.typeHandlerRegistry.hasTypeHandler(this.parameterObject.getClass())) {
                        value = this.parameterObject;
                    } else {
                        MetaObject metaObject = this.configuration.newMetaObject(this.parameterObject);
                        value = metaObject.getValue(propertyName);
                    }

                    TypeHandler typeHandler = parameterMapping.getTypeHandler();
                    JdbcType jdbcType = parameterMapping.getJdbcType();
                    // 如果值为null且未指定JdbcType,则使用配置中的默认JdbcType
                    if (value == null && jdbcType == null) {
                        jdbcType = this.configuration.getJdbcTypeForNull();
                    }

                    try {
                        // 设置参数
                        typeHandler.setParameter(ps, i + 1, value, jdbcType);
                    } catch (SQLException | TypeException var10) {
                        // 抛出类型异常
                        throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + var10, var10);
                    }
                }
            }
        }

    }
}

从代码中不难看出,设置SQL的参数是从parameterObject对象中获取的,然后使用typeHandler转换参数

ResultSetHandler

ResultSetHandler是结果集处理器,其接口源码如下

/**
 * ResultSetHandler 接口定义了处理 JDBC 结果集、游标结果集以及输出参数的方法。
 */
import java.sql.CallableStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import org.apache.ibatis.cursor.Cursor;

public interface ResultSetHandler {
    /**
     * 处理 JDBC 结果集,将其转换为 List 形式。
     * 
     * @param var1 Statement 对象,用于执行 SQL 语句并获取结果集。
     * @return 返回一个元素类型为 E 的 List 对象,包含从结果集中提取的数据。
     * @throws SQLException 如果处理结果集时发生 SQL 错误。
     */
    <E> List<E> handleResultSets(Statement var1) throws SQLException;

    /**
     * 处理 JDBC 游标结果集,将其封装成 Cursor 形式,支持懒加载。
     * 
     * @param var1 Statement 对象,用于执行 SQL 语句并获取游标结果集。
     * @return 返回一个元素类型为 E 的 Cursor 对象,包含从游标结果集中提取的数据。
     * @throws SQLException 如果处理游标结果集时发生 SQL 错误。
     */
    <E> Cursor<E> handleCursorResultSets(Statement var1) throws SQLException;

    /**
     * 处理存储过程的输出参数。
     * 
     * @param var1 CallableStatement 对象,用于执行包含输出参数的存储过程。
     * @throws SQLException 如果处理输出参数时发生 SQL 错误。
     */
    void handleOutputParameters(CallableStatement var1) throws SQLException;
}

该接口有个很复杂的实现类DefaultResultSetHandler,其中handleResultSets的具体实现方法入下:

/**
 * 处理多个结果集。
 * 
 * @param stmt Statement对象,用于执行SQL语句并获取结果集。
 * @return 返回处理后的结果集列表。
 * @throws SQLException 如果处理过程中发生SQL异常。
 */
public List<Object> handleResultSets(Statement stmt) throws SQLException {
    // 初始化错误上下文并记录当前活动及对象ID
    ErrorContext.instance().activity("handling results").object(this.mappedStatement.getId());
    List<Object> multipleResults = new ArrayList();
    int resultSetCount = 0;
    
    // 获取第一个结果集
    ResultSetWrapper rsw = this.getFirstResultSet(stmt);
    // 获取所有结果映射
    List<ResultMap> resultMaps = this.mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    // 验证结果映射数量是否与结果集数量匹配
    this.validateResultMapsCount(rsw, resultMapCount);

    // 处理所有预期的结果集
    while(rsw != null && resultMapCount > resultSetCount) {
        ResultMap resultMap = (ResultMap)resultMaps.get(resultSetCount);
        // 使用结果映射处理结果集,并将结果添加到结果列表中
        this.handleResultSet(rsw, resultMap, multipleResults, (ResultMapping)null);
        rsw = this.getNextResultSet(stmt);
        // 清理处理结果集后的资源
        this.cleanUpAfterHandlingResultSet();
        ++resultSetCount;
    }

    // 处理额外的结果集(如果存在)
    String[] resultSets = this.mappedStatement.getResultSets();
    if (resultSets != null) {
        while(rsw != null && resultSetCount < resultSets.length) {
            ResultMapping parentMapping = (ResultMapping)this.nextResultMaps.get(resultSets[resultSetCount]);
            if (parentMapping != null) {
                // 获取嵌套结果映射ID并处理相应的结果集
                String nestedResultMapId = parentMapping.getNestedResultMapId();
                ResultMap resultMap = this.configuration.getResultMap(nestedResultMapId);
                this.handleResultSet(rsw, resultMap, (List)null, parentMapping);
            }

            rsw = this.getNextResultSet(stmt);
            // 清理处理结果集后的资源
            this.cleanUpAfterHandlingResultSet();
            ++resultSetCount;
        }
    }

    // 如果结果列表只包含单个结果,则将其折叠为单个结果并返回
    return this.collapseSingleResultList(multipleResults);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/588974.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LT6911GX HDMI2.1 至四端口 MIPI/LVDS,带音频 龙迅方案

1. 描述LT6911GX 是一款面向 VR / 显示应用的高性能 HDMI2.1 至 MIPI 或 LVDS 芯片。HDCP RX作为HDCP中继器的上游&#xff0c;可以与其他芯片的HDCP TX配合使用&#xff0c;实现中继器功能。对于 HDMI2.1 输入&#xff0c;LT6911GX 可配置为 3/4 通道。自适应均衡功能使其适合…

vue3+vite+js 实现移动端,PC端响应式布局

目前使用的是vue3vite&#xff0c;没有使用ts 纯移动端|PC端 这种适用于只适用一个端的情况 方法&#xff1a;amfe-flexible postcss-pxtorem相结合 ① 执行以下两个命令 npm i -S amfe-flexible npm install postcss-pxtorem --save-dev② main.js文件引用 import amfe-f…

FreeRTOS信号量

信号量简介 def 1&#xff1a; 信号量是一种解决问题的机制&#xff0c;可以实现共享资源的访问 信号量浅显理解例子&#xff1a; 空车位&#xff1a; 信号量资源&#xff08;计数值&#xff09; 让出占用车位&#xff1a; 释放信号量&#xff08;计数值&#xff09; 占用车…

LT6911UXB HDMI2.0 至四端口 MIPI DSI/CSI,带音频 龙迅方案

1. 描述LT6911UXB 是一款高性能 HDMI2.0 至 MIPI DSI/CSI 转换器&#xff0c;适用于 VR、智能手机和显示应用。HDMI2.0 输入支持高达 6Gbps 的数据速率&#xff0c;可为4k60Hz视频提供足够的带宽。此外&#xff0c;数据解密还支持 HDCP2.2。对于 MIPI DSI / CSI 输出&#xff0…

jvm 马士兵 01

01.JVM是什么 JVM是一个跨平台的标准 JVM只识别class文件&#xff0c;符合JVM规范的class文件都可以被识别

知乎广告开户流程,知乎广告的优势是什么?

社交媒体平台不仅是用户获取知识、分享见解的场所&#xff0c;更是品牌展示、产品推广的重要舞台。知乎作为国内知名的知识分享社区&#xff0c;以其高质量的内容生态和庞大的用户基础&#xff0c;成为了众多企业进行广告投放的优选之地。云衔科技通过其专业服务&#xff0c;助…

数字身份管理:Facebook如何利用区块链技术?

随着数字化进程的加速&#xff0c;个人身份管理已成为一个关键议题。在这方面&#xff0c;区块链技术正在逐渐展现其巨大潜力。作为全球最大的社交媒体平台&#xff0c;Facebook也在积极探索和应用区块链技术来改进其数字身份管理系统。本文将深入探讨Facebook如何利用区块链技…

<Linux> 权限

目录 权限人员相对于文件来说的分类更改权限文件的拥有者与所属组 权限 权限是操作系统用来限制对资源访问的机制&#xff0c;权限一般分为读、写、执行。系统中的每个文件都拥有特定的权限、所属用户及所属组&#xff0c;通过这样的机制来限制哪些用户、哪些组可以对特定文件…

Node私库Verdaccio使用记录,包的构建,推送和拉取

Node私库Verdaccio使用记录&#xff0c;包的构建&#xff0c;推送和拉取 Verdaccio是一个轻量级的私有npm代理注册中心&#xff0c;它可以帮助你在本地搭建一个npm仓库&#xff0c;非常适合企业内部使用。通过使用Verdaccio&#xff0c;你可以控制和缓存依赖包&#xff0c;提高…

政安晨:【Keras机器学习示例演绎】(二十七)—— 利用 NNCLR 进行自我监督对比学习

目录 简介 自我监督学习 对比学习 NNCLR 设置 超参数 加载数据集 增强 准备扩增模块 编码器结构 用于对比预训练的 NNCLR 模型 预训练 NNCLR 政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: TensorFlow与Keras机器学习实战 希望…

DRF限流组件源码分析

DRF限流组件源码分析 开发过程中&#xff0c;如果某个接口不想让用户访问过于频繁&#xff0c;可以使用限流的机制 限流&#xff0c;限制用户访问频率&#xff0c;例如&#xff1a;用户1分钟最多访问100次 或者 短信验证码一天每天可以发送50次&#xff0c; 防止盗刷。 对于…

Spring - 7 ( 13000 字 Spring 入门级教程 )

一&#xff1a;Spring Boot 日志 1.1 日志概述 日志对我们来说并不陌生&#xff0c;我们可以通过打印日志来发现和定位问题, 或者根据日志来分析程序的运行过程&#xff0c;但随着项目的复杂度提升, 我们对日志的打印也有了更高的需求, 而不仅仅是定位排查问题 比如有时需要…

【LDAP】LDAP 和 AD 介绍及使用 LDAP 操作 AD 域

LDAP 和 AD 介绍及使用 LDAP 操作 AD 域 1.LDAP入门1.1 定义1.2 目录结构1.3 命名格式 2.AD 入门2.1 AD 定义2.2 作用2.3 AD 域结构常用对象2.3.1 域&#xff08;Domain&#xff09;2.3.2 组织单位&#xff08;Organization Unit&#xff09;2.3.3 群组&#xff08;Group&#…

服务器数据恢复—多块磁盘离线导致阵列瘫痪,上层lun不可用的数据恢复案例

服务器存储数据恢复环境&#xff1a; 某品牌MSA2000存储&#xff0c;该存储中有一组由8块SAS硬盘&#xff08;其中有一块热备盘&#xff09;组建的RAID5阵列&#xff0c;raid5阵列上层划分了6个lun&#xff0c;均分配给HP-Unix小型机使用&#xff0c;主要数据为oracle数据库和O…

Mac 上安装多版本的 JDK 且实现 自由切换

背景 当前电脑上已经安装了 jdk8; 现在再安装 jdk17。 期望 完成 jdk17 的安装&#xff0c;并且完成 环境变量 的配置&#xff0c;实现自由切换。 前置补充知识 jdk 的安装路径 可以通过查看以下目录中的内容&#xff0c;确认当前已经安装的 jdk 版本。 cd /Library/Java/Java…

解决WordPress无法强制转换https问题

原因&#xff1a;我在用cs的时候&#xff0c;突然老鸟校园网突然断了&#xff0c;客户端cs连不上了&#xff0c;进程也杀不死&#xff0c;cpu占用100%&#xff0c;只能重启&#xff0c;但是重启后我的blog网站打不开了 开始以为是Nginx的问题&#xff0c;重启它说配置出了问题…

基于Springboot的在线博客网站

基于SpringbootVue的在线博客网站的设计与实现 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringbootMybatis工具&#xff1a;IDEA、Maven、Navicat 系统展示 用户登录 首页 博客标签 博客分类 博客列表 图库相册 后台登录 后台首页 用户管理 博客标…

Word文件导出为PDF

Word文件导出为PDF 方法一、使用Word自带另存为PDF功能 打开需要转换为PDF格式的Word文件&#xff0c;依次点击【文件】➡【另存为】➡选择文件保存类型为.PDF 使用这种方法导出的PDF可能存在Word中书签丢失的情况&#xff0c;在导出界面点击&#xff0c;选项进入详细设置 勾…

算法系列--BFS解决拓扑排序

&#x1f495;"请努力活下去"&#x1f495; 作者&#xff1a;Lvzi 文章主要内容&#xff1a;算法系列–算法系列–BFS解决拓扑排序 大家好,今天为大家带来的是算法系列--BFS解决拓扑排序 前言:什么是拓扑排序 拓扑排序–解决有顺序的排序问题(要做事情的先后顺序) …

Vulntarget-a 打靶练习

关于环境配置&#xff0c;这里就不在附上图片和说明了&#xff0c;网上一大堆&#xff0c;这里只针对自己练习&#xff0c;做一个记录。 外网信息收集 利用arpscan工具&#xff0c;扫描了当前局域网中都存在哪些主机&#xff1a; 正常来说我们不应该使用arpscan&#xff0c;而是…
最新文章