mybatis源码分析—加载篇

一、Spring+Mybatis的典型配置

1.1、pom文件引入依赖

<!-- mybatis -->
<dependency>
   <groupId>org.mybatis</groupId>
   <artifactId>mybatis</artifactId>
   <version>${mybatis.version}</version>
</dependency>
<dependency>
   <groupId>org.mybatis</groupId>
   <artifactId>mybatis-spring</artifactId>
   <version>${mybatis-spring.version}</version>
</dependency>
<!-- mybatis end -->

1.2、配置文件

 mybatis-config.xml

<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" 
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
   <settings>
      <!-- 全局映射器启用缓存 -->
      <setting name="cacheEnabled" value="true" />
      <!-- 查询时,关闭关联对象即时加载以提高性能 -->
      <setting name="lazyLoadingEnabled" value="true" />
      <!-- 设置关联对象加载的形态,此处为按需加载字段(加载字段由SQL指定),不会加载关联表的所有字段,以提高性能 -->
      <setting name="aggressiveLazyLoading" value="true" />
      <!-- 对于未知的SQL查询,允许返回不同的结果集以达到通用的效果 -->
      <setting name="multipleResultSetsEnabled" value="true" />
      <!-- 允许使用列标签代替列名 -->
      <setting name="useColumnLabel" value="true" />
      <!-- 允许使用自定义的主键值(比如由程序生成的UUID 32位编码作为键值),数据表的PK生成策略将被覆盖 -->
      <setting name="useGeneratedKeys" value="true" />
      <!-- 给予被嵌套的resultMap以字段-属性的映射支持 -->
      <setting name="autoMappingBehavior" value="FULL" />
      <!-- 对于批量更新操作缓存SQL以提高性能 -->
      <setting name="defaultExecutorType" value="REUSE" />
      <!-- 数据库超过25000秒仍未响应则超时 -->
      <setting name="defaultStatementTimeout" value="25000" />
   </settings>
 
   <!-- 指定别名扫描包路径,mapper文件中可直接用别名替换全路径限定名 -->
   <typeAliases>
      <!-- typeAlias必须要放在package前 -->
      <!--<typeAlias type="com.**.**.wechat.**.User" alias="user"/>-->
      <package name="com.**.**.wechat.po"/>
   </typeAliases>
 
   <plugins>
      <!-- 分页插件,PageHelper实现分页需要在调用dao之前调用PageHelper#startPage()方法 -->
      <plugin interceptor="com.github.pagehelper.PageHelper">
         <property name="dialect" value="mysql" />
         <!-- 该参数默认为false,设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用,和startPage中的pageNum效果一样 -->
         <property name="offsetAsPageNum" value="true" />
         <!-- 该参数默认为false,设置为true时,使用RowBounds分页会进行count查询 -->
         <property name="rowBoundsWithCount" value="true" />
      </plugin>
   </plugins>
</configuration>

 applicationContext-mybatis.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/tx
                        http://www.springframework.org/schema/tx/spring-tx.xsd">
 
    <bean id="statFilter" class="com.alibaba.druid.filter.logging.Slf4jLogFilter">
        <property name="statementExecutableSqlLogEnable" value="false"/>
        <property name="dataSourceLogEnabled" value="false"/>
    </bean>
 
    <bean id="logFilter" class="com.alibaba.druid.filter.stat.StatFilter">
        <property name="slowSqlMillis" value="50"/>
        <property name="logSlowSql" value="false"/>
        <property name="mergeSql" value="true"/>
    </bean>
 
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
          init-method="init" destroy-method="close">
        <property name="driverClassName" value="${datasource.driverClassName}"/>
        <property name="url" value="${datasource.url}"/>
        <property name="username" value="${datasource.username}"/>
        <property name="password" value="${datasource.password}"/>
        <property name="maxActive" value="${datasource.maxPoolSize}"/>
        <property name="initialSize" value="${datasource.initialPoolSize}"/>
        <property name="maxWait" value="${datasource.maxWait}"/>
        <property name="minIdle" value="${datasource.minIdle}"/>
 
        <property name="timeBetweenEvictionRunsMillis" value="60000"/>
        <property name="minEvictableIdleTimeMillis" value="300000"/>
        <property name="validationQuery" value="SELECT 'x'"/>
        <property name="testWhileIdle" value="true"/>
        <property name="testOnBorrow" value="false"/>
        <property name="testOnReturn" value="false"/>
        <property name="poolPreparedStatements" value="true"/>
        <property name="proxyFilters">
            <list>
                <ref bean="statFilter"/>
                <ref bean="logFilter"/>
            </list>
        </property>
    </bean>
 
    <bean id="dbcpDataSource" class="org.apache.commons.dbcp.BasicDataSource"
          destroy-method="close">
        <property name="driverClassName" value="${datasource.driverClassName}"/>
        <property name="url" value="${datasource.url}"/>
        <property name="username" value="${datasource.username}"/>
        <property name="password" value="${datasource.password}"/>
        <property name="maxActive" value="${datasource.maxPoolSize}"/>
        <property name="maxIdle" value="${datasource.maxIdle}"/>
        <property name="minIdle" value="${datasource.minIdle}"/>
    </bean>
 
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!-- mybatis配置文件路径 -->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!-- mapper.xml文件扫描路径 -->
        <property name="mapperLocations" value="classpath:mybatis/*.xml"/>
        <!-- 指定扫描单个类型别名 -->
        <property name="typeAliases" value="com.**.**.wechat.pojo.User" />
        <!-- 指定需要扫描的类型别名包路径,这样在mapper.xml其它需要使用实体类(Entity)的时候就可以直接使用别名而不是全限定类名 -->
        <property name="typeAliasesPackage" value="com.**.**.wechat.pojo"/>
        <!-- 指定需要扫描类型的父类,如果不传默认为Object.class -->
        <property name="typeAliasesSuperType" value="com.**.**.wechat.pojo.BaseDomain"/>
    </bean>
 
    <!-- mapper.java文件扫描路径 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.**.**.wechat.dao"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>
 
    <!-- 事务管理 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- 支持事务注解 -->
    <tx:annotation-driven/>
</beans>

二、加载过程

 根据上面的配置可以看出,加载过程主要可以分为两步,一是通过MapperScannerConfigurer进行mapper包路径的扫描并生成BeanDefinition,二是SqlSessionFactoryBean读取xml配置构建SqlSessionFactory。

2.1、扫描mapper包路径

2.1.1、MapperScannerConfigurer

 MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,它的作用是通过扫描器ClassPathMapperScanner自动扫描basePackage路径下的mapper接口,依次生成BeanDefinition并注册,basePackage可以是以多种分隔符(,; \t\n)串联起来的多个包路径。MapperScannerConfigurer中postProcessBeanDefinitionRegistry方法的实现如下:

 MapperScannerConfigurer实现的postProcessBeanDefinitionRegistry接口

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
  if (this.processPropertyPlaceHolders) {
    processPropertyPlaceHolders();
  }
 
  ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
  scanner.setAddToConfig(this.addToConfig);
  scanner.setAnnotationClass(this.annotationClass);
  scanner.setMarkerInterface(this.markerInterface);
  scanner.setSqlSessionFactory(this.sqlSessionFactory);
  scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
  scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
  scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
  scanner.setResourceLoader(this.applicationContext);
  scanner.setBeanNameGenerator(this.nameGenerator);
 
  // 注册Filter
  scanner.registerFilters();
  // 扫描包路径并注册BeanDefinition
  scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
2.1.2、ClassPathMapperScanner

 ClassPathMapperScanner继承了ClassPathBeanDefinitionScanner,并扩展了它的doScan()接口,主要方法有如下几个:

 ClassPathMapperScanner的主要方法

// 注册过滤器,用于判断哪些类需要被注册,哪些需要被排除
public void registerFilters() {
    boolean acceptAllInterfaces = true;
    if (this.annotationClass != null) {
        this.addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
        acceptAllInterfaces = false;
    }
 
    if (this.markerInterface != null) {
        this.addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
            protected boolean matchClassName(String className) {
                return false;
            }
        });
        acceptAllInterfaces = false;
    }
 
    // 默认所有的类都需要扫描和注册
    if (acceptAllInterfaces) {
        this.addIncludeFilter((metadataReader, metadataReaderFactory) -> {
            return true;
        });
    }
 
    // 排除package-info.java文件
    this.addExcludeFilter((metadataReader, metadataReaderFactory) -> {
        String className = metadataReader.getClassMetadata().getClassName();
        return className.endsWith("package-info");
    });
}
 
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    // 调用父类ClassPathBeanDefinitionScanner的doScan方法获得BeanDefinitionHolder集合
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
    if (beanDefinitions.isEmpty()) {
    } else {
        this.processBeanDefinitions(beanDefinitions);
    }
 
    return beanDefinitions;
}
 
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    Iterator var3 = beanDefinitions.iterator();
 
    while(var3.hasNext()) {
        BeanDefinitionHolder holder = (BeanDefinitionHolder)var3.next();
        GenericBeanDefinition definition = (GenericBeanDefinition)holder.getBeanDefinition();
        String beanClassName = definition.getBeanClassName();
 
        definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
        // 设置MapperFactoryBean,用于获取实例
        definition.setBeanClass(this.mapperFactoryBean.getClass());
        definition.getPropertyValues().add("addToConfig", this.addToConfig);
        boolean explicitFactoryUsed = false;sqlSessionFactoryBeanName
 
        // 如果sqlSessionFactoryBeanName的名字不为空,则在Spring容器中查询
        if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
            definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
            explicitFactoryUsed = true;
        // 如果sqlSessionFactory不为null,则设置definition的sqlSessionFactory
        } else if (this.sqlSessionFactory != null) {
            definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
            explicitFactoryUsed = true;
        }
 
        if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
            definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
            explicitFactoryUsed = true;
        } else if (this.sqlSessionTemplate != null) {
            definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
            explicitFactoryUsed = true;
        }
 
        if (!explicitFactoryUsed) {
            definition.setAutowireMode(2);
        }
    }
}

2.2、构建SqlSessionFactory

2.2.1、SqlSessionFactoryBean

 SqlSessionFactoryBean实现了FactoryBean接口,泛型参数为SqlSessionFactory,说明它的最终目的是构建并生成SqlSessionFactory,下面看下为了得到SqlSessionFactory,内部都做了哪些事情。

 SqlSessionFactoryBean构建SqlSessionFactory的过程

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
    XMLConfigBuilder xmlConfigBuilder = null;
    Configuration targetConfiguration;
 
    // configuration可通过配置传入,如果没有且configLocation不为空,则会借助config构建器XMLConfigBuilder新生成一个
    if (this.configuration != null) {
        targetConfiguration = this.configuration;
        if (targetConfiguration.getVariables() == null) {
            targetConfiguration.setVariables(this.configurationProperties);
        } else if (this.configurationProperties != null) {
            targetConfiguration.getVariables().putAll(this.configurationProperties);
        }
 
    // 这里是我们传入的mybatis-config.xml路径
    } else if (this.configLocation != null) {
        xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties);
        targetConfiguration = xmlConfigBuilder.getConfiguration();
    } else {
        targetConfiguration = new Configuration();
        Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
    }
 
    // 下面的objectFactory、objectWrapperFactory和vfs都是既可以支持配置传入,也可以采用默认实现
    Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
    Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
    Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
 
    // 如果传入的类型别名包路径不为空,则通过TypeAliasRegistry.registerAliases(String, Class<?>)方法保存类型与别名的映射到Map中
    // 该Map在构造方法中已经添加了常见的基本类型(int)和封装类型(Integer)的映射
    // 别名默认是Class的SimpleName,用户也可以通过在Class上增加注解@Alias指定
    String[] typeHandlersPackageArray;
    if (StringUtils.hasLength(this.typeAliasesPackage)) {
        typeHandlersPackageArray = StringUtils.tokenizeToStringArray(this.typeAliasesPackage, ",; \t\n");   // 默认使用",; \t\n"进行split
        Stream.of(typeHandlersPackageArray).forEach((packageToScan) -> {                                 // 如果未指定超类,默认使用Object.class
            targetConfiguration.getTypeAliasRegistry().registerAliases(packageToScan, this.typeAliasesSuperType == null ? Object.class : this.typeAliasesSuperType);
        });
    }
 
    // 指定单个类型别名
    if (!ObjectUtils.isEmpty(this.typeAliases)) {
        Stream.of(this.typeAliases).forEach((typeAlias) -> {
            targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
        });
    }
 
    // 如果配置了插件,这里添加到Configuration的interceptorChain中;同时也可以在mybatis-config.xml文件中添加插件进行拦截
    if (!ObjectUtils.isEmpty(this.plugins)) {
        Stream.of(this.plugins).forEach((plugin) -> {
            targetConfiguration.addInterceptor(plugin);
        });
    }
 
    // 如果类型处理器包路径不为空,则会扫描包下实现了TypeHandler的类并注册到TypeHandlerRegistry的Map中;TypeHandler的作用是负责java数据类型和jdbc数据类型之间的映射和转换
    if (StringUtils.hasLength(this.typeHandlersPackage)) {
        typeHandlersPackageArray = StringUtils.tokenizeToStringArray(this.typeHandlersPackage, ",; \t\n");
        Stream.of(typeHandlersPackageArray).forEach((packageToScan) -> {
            targetConfiguration.getTypeHandlerRegistry().register(packageToScan);
        });
    }
 
    // 手动指定会覆盖默认映射
    if (!ObjectUtils.isEmpty(this.typeHandlers)) {
        Stream.of(this.typeHandlers).forEach((typeHandler) -> {
            targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
        });
    }
 
    if (this.databaseIdProvider != null) {
        try {
            targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
        } catch (SQLException var23) {
            throw new NestedIOException("Failed getting a databaseId", var23);
        }
    }
 
    Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);
    if (xmlConfigBuilder != null) {
        try {
            // 解析mybatis-config.xml文件入口
            xmlConfigBuilder.parse();
        } catch (Exception var21) {
            throw new NestedIOException("Failed to parse config resource: " + this.configLocation, var21);
        } finally {
            ErrorContext.instance().reset();
        }
    }
 
    targetConfiguration.setEnvironment(new Environment(this.environment, (TransactionFactory)(this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory), this.dataSource));
 
    // 扫描mapperLocations包下的mapper.xml文件并依次解析
    if (!ObjectUtils.isEmpty(this.mapperLocations)) {
        Resource[] var24 = this.mapperLocations;
        int var4 = var24.length;
 
        for(int var5 = 0; var5 < var4; ++var5) {
            Resource mapperLocation = var24[var5];
            if (mapperLocation != null) {
                try {
                    // 解析mapper.xml文件入口
                    XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
                    xmlMapperBuilder.parse();
                } catch (Exception var19) {
                    throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", var19);
                } finally {
                    ErrorContext.instance().reset();
                }
            }
        }
    }
 
    // 通过Configuration创建sqlSessionFactory的默认实现DefaultSqlSessionFactory并返回
    // 除了通过xml之外,SqlSessionFactoryBuilder还提供了通过java代码和任意输入流(InputStream)的方式构建
    return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
2.2.2、XMLConfigBuilder解析config配置

 下面一起看下config.xml的解析过程

 XMLConfigBuilder解析过程

public Configuration parse() {
  if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  // 解析从根节点configuration开始
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;
}
 
private void parseConfiguration(XNode root) {
  try {
    // 读取properties配置
    propertiesElement(root.evalNode("properties"));
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    loadCustomVfs(settings);
    loadCustomLogImpl(settings);
 
    // 读取类型别名配置,该参数也可以在SqlSessionFactoryBean配置中进行设置,注意这两个地方的读取顺序,先SqlSessionFactoryBean中后config中,后面会覆盖前面的
    typeAliasesElement(root.evalNode("typeAliases"));
    // 同样可以设置拦截插件
    pluginElement(root.evalNode("plugins"));
 
    objectFactoryElement(root.evalNode("objectFactory"));
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
 
    // 读取并设置setting配置,包括是否开启全局换存、是否延迟加载、主键生成策略、超时时间等
    settingsElement(settings);
    environmentsElement(root.evalNode("environments"));
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
 
    // 同SqlSessionFactoryBean中的typeHandlers配置
    typeHandlerElement(root.evalNode("typeHandlers"));
    // 在config中也可以设置mapper文件扫描路径,一般推荐配置在SqlSessionFactoryBean中,两者实际上最终都是通过XMLMapperBuilder进行解析和构建
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}
2.2.3、XMLMapperBuilder解析mapper.xml文件

 XMLMapperBuilder解析过程

public void parse() {
  // 一个mapper文件只加载一次,resource为全路径文件名
  if (!configuration.isResourceLoaded(resource)) {
    // 解析mapper文件入口
    configurationElement(parser.evalNode("/mapper"));
    configuration.addLoadedResource(resource);
    // 为mapper绑定namespace,namespace是<mapper>标签中的namespace值
    bindMapperForNamespace();
  }
 
  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
}
 
private void configurationElement(XNode context) {
  try {
    // 每个mapper文件都需要指定命名空间
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.equals("")) {
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
    builderAssistant.setCurrentNamespace(namespace);
 
    // 对其他命名空间缓存配置的引用
    cacheRefElement(context.evalNode("cache-ref"));
    // 对当前命名空间的缓存配置
    cacheElement(context.evalNode("cache"));
     
    // 引用外部parameterMap的已经被废弃的方法
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    // 解析resultMap,用来描述如何从数据库结果集中来加载对象
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    // 定义可以被重用的sql代码段
    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);
  }
}

 上面的L-37是解析sql的主要过程,方法内部是通过XMLStatementBuilder#parseStatementNode()构造每个执行节点对应的MappedStatement,并将其添加到Configuration的Map<String, MappedStatement> mappedStatements属性中,内部比较重要的步骤是sqlSource的获取,这部分逻辑可以参考《mybatis源码分析—sql动态解析》篇。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值