Mybatis框架源码笔记(十一)之Spring整合mybatis演示与整合原理解析

news2024/11/24 17:05:53

1 Spring框架整合Mybatis示例

1.1 创建演示项目

在这里插入图片描述

1.2 项目目录结构

在这里插入图片描述

1.3 依赖配置pom.xml文件

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.kkarma</groupId>
  <artifactId>spring-mybatis-app</artifactId>
  <version>1.0.0</version>

  <name>spring-mybatis-app</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.3.26</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-orm -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-orm</artifactId>
      <version>5.3.26</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.3.26</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.3.26</version>
    </dependency>
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.2.15</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>2.1.0</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.24</version>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.11-SNAPSHOT</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.30</version>
    </dependency>

  </dependencies>

  <build>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-jar-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
        <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
        <plugin>
          <artifactId>maven-site-plugin</artifactId>
          <version>3.7.1</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

1.4 项目配置文件

1.4.1 数据库连接配置文件

druid.driver=com.mysql.cj.jdbc.Driver
druid.url=jdbc:mysql://10.10.3.95:3306/sys-library?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone\
  =Asia/Shanghai
druid.userName=adsion
druid.password=MTIzNDU2
# 初始化连接数
druid.pool.init=3
# 高峰期过后,保留连接吃的个数
druid.pool.minIdle=5
# 高峰期,最大能创建连接的个数
druid.pool.MaxActive=20
# 等待的时间
durid.pool.timeout=60

1.4.2 Mybatis框架全局配置文件

<?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>
        <!--  二级缓存开启, 默认为true -->
        <setting name="cacheEnabled" value="true" />
        <setting name="localCacheScope" value="STATEMENT"/>
    </settings>

</configuration>

1.4.3 Spring框架配置文件

<?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:Context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- 通过配置文件方式注册bean -->
    <bean id="myBook01" class="com.kkarma.pojo.LibBook" >
        <property name="bookId" value="6"></property>
        <property name="bookIndexNo" value="xxooooxx"></property>
        <property name="bookName" value="java高级程序设计"></property>
        <property name="bookAuthor" value="张三"></property>
        <property name="bookPublisher" value="清华大学出版社"></property>
        <property name="bookCateId" value="1"></property>
        <property name="bookStock" value="2"></property>
    </bean>

    <!--声明使用注解配置-->
    <Context:annotation-config />

    <!--声明Spring工厂注解的扫描范围-->
    <Context:component-scan base-package="com.kkarma"/>

    <!--引用外部文件-->
    <Context:property-placeholder location="db.properties"/>

    <!--配置DruidDataSources-->
    <bean id="DruidDataSources" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${druid.driver}"/>
        <property name="url" value="${druid.url}"/>
        <property name="username" value="${druid.userName}"/>
        <property name="password" value="${druid.password}"/>
        <property name="initialSize" value="${druid.pool.init}"/>
        <property name="minIdle" value="${druid.pool.minIdle}"/>
        <property name="maxActive" value="${druid.pool.MaxActive}"/>
        <property name="maxWait" value="${durid.pool.timeout}"/>
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--配置数据源-->
        <property name="dataSource" ref="DruidDataSources"/>
        <!--配置mapper的路径-->
        <property name="mapperLocations" value="classpath*:mappers/**/*Mapper.xml">
        </property>
        <!--配置需要定义别名的实体类的包-->
        <property name="typeAliasesPackage" value="com.kkarma.pojo"/>
        <!--配置需要mybatis的主配置文件-->
        <property name="configLocation" value="mybatis-config.xml"/>
    </bean>

    <!-- 配置MapperScannerConfigurer -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" >
<!--        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>-->
        <property name="basePackage" value="com.kkarma.mapper"/>
    </bean>

    <!-- 将spring事务管理配置给spring -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="DruidDataSources"/>
    </bean>

    <!-- 通过Spring jdbc提供的<tx>标签声明事物的管理策略,并给事务设置隔离级别以及传播机制 -->
    <tx:advice id="MyAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="Insert*" isolation="REPEATABLE_READ" propagation="REQUIRED"/>
            <tx:method name="Update*" isolation="REPEATABLE_READ" propagation="REQUIRED"/>
            <tx:method name="Delete*" isolation="REPEATABLE_READ" propagation="REQUIRED"/>
            <tx:method name="Query*" isolation="REPEATABLE_READ" propagation="SUPPORTS"/>
        </tx:attributes>
    </tx:advice>

    <!--将事务管理以Aop配置,应用于ServiceI方法(ServiceImp)-->
    <aop:config>
        <aop:pointcut id="MyManager" expression="execution(* com.kkarma.service.*.*(..))"/>
        <aop:advisor advice-ref="MyAdvice" pointcut-ref="MyManager" />
    </aop:config>
</beans>

1.4.4 **Mapper.xml文件

在这里插入图片描述

1.4.5 Mapper接口

package com.kkarma.mapper;

import com.kkarma.pojo.LibBook;

import java.util.List;

/**
 * @Author: karma
 * @Date: 2023/3/17 0017 - 03 - 17 - 10:18
 * @Description: com.kkarma.mapper
 * @version: 1.0
 */
public interface LibBookMapper {

    List<LibBook> selectAllBook();
}

1.4.6 实体类

package com.kkarma.pojo;

import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.io.Serializable;

/**
 * @Author: karma
 * @Date: 2023/3/17 0017 - 03 - 17 - 10:09
 * @Description: com.kkarma.pojo
 * @version: 1.0
 */
@Data
@NoArgsConstructor
@ToString
public class LibBook implements Serializable {

    private Long bookId;

    private String bookIndexNo;

    private String bookName;

    private String bookAuthor;

    private String bookDescription;

    private String bookPublisher;

    private Integer bookCateId;

    private Integer bookStock;
}

1.5 测试

package com.kkarma;

import static org.junit.Assert.assertTrue;

import com.kkarma.mapper.LibBookMapper;
import com.kkarma.pojo.LibBook;
import com.kkarma.service.ILibBookService;
import lombok.AllArgsConstructor;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

/**
 * Unit test for simple App.
 */
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
@RunWith(value = SpringJUnit4ClassRunner.class)
public class AppTest 
{
    @Autowired
    // private ILibBookService bookService;
    private LibBookMapper bookMapper;

    /**
     * Rigorous Test :-)
     */
    @Test
    public void queryBooksTest()
    {
        List<LibBook> libBooks = bookMapper.selectAllBook();
        libBooks.forEach( System.out::println);
    }
}

在这里插入图片描述

2 Spring框架整合Mybatis原理分析

看看, 之前如果我们单独只用Mybatis框架的时候, 测试代码是怎么写的

1) 创建一个SqlSessionFactoryBuilder对象:

SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();	
 
2) 创建一个SqlSessionFactory对象:

SqlSessionFactory factory = factoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml")); 

3) 创建一个SqlSession对象:

SqlSession sqlSession = factory.openSession();

4) 获取Mapper层指定接口的动态代理对象:

LibBookMapper mapper = sqlSession.getMapper(LibBookMapper.class);
 
5) 调用接口方法获取返回结果即可:

 List<LibBook> list = mapper.selectAllBook();

对比一下spring框架集成Mybatis框架之后

public class AppTest 
{
    @Autowired
    private LibBookMapper bookMapper;

    @Test
    public void queryBooksTest()
    {
        List<LibBook> libBooks = bookMapper.selectAllBook();
    }
}

对比一下,发现是不是很多操作就被简化了, 整个SqlSession对象的创建过程Mapper动态代理对象的创建过程都没有了, 那么mybatis-spring到底是怎么实现这个操作的简化的呢, 下面我们一起来分析一下这个过程。

2.1 SqlSessionFactoryBean

首先我们看看Spring的配置文件applicationContext.xml, 其中定义了sqlSessionFactory的bean声明, 在这里插入图片描述
创建的对象是SqlSessionFactoryBean对象,前面已经详细说过Mybatis框架的核心内容了,我们应该都知道在Mybatis中我们都是通过SqlSessionFactoryBuilder创建SqlSessionFactory对象,然后再通过SqlSessionFactory对象创建SqlSession对象,从而获取数据库连接等等操作。

见名知意,SqlSessionFactoryBean对象很定也是跟创建SqlSessionFactory紧密相关的。到源码里面去看看

public class SqlSessionFactoryBean
    implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {

  private static final Logger LOGGER = LoggerFactory.getLogger(SqlSessionFactoryBean.class);

  private static final ResourcePatternResolver RESOURCE_PATTERN_RESOLVER = new PathMatchingResourcePatternResolver();
  private static final MetadataReaderFactory METADATA_READER_FACTORY = new CachingMetadataReaderFactory();

  private Resource configLocation;

  private Configuration configuration;

  private Resource[] mapperLocations;

  private DataSource dataSource;

  private TransactionFactory transactionFactory;

  private Properties configurationProperties;

  /** 在创建SqlSessionFactoryBean的时候,已经创建了sqlSessionFactoryBuilder对象 */
  private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();

  private SqlSessionFactory sqlSessionFactory;

  // EnvironmentAware requires spring 3.1
  private String environment = SqlSessionFactoryBean.class.getSimpleName();

  private boolean failFast;

  private Interceptor[] plugins;

  private TypeHandler<?>[] typeHandlers;

  private String typeHandlersPackage;

  @SuppressWarnings("rawtypes")
  private Class<? extends TypeHandler> defaultEnumTypeHandler;

  private Class<?>[] typeAliases;

  private String typeAliasesPackage;

  private Class<?> typeAliasesSuperType;

  private LanguageDriver[] scriptingLanguageDrivers;

  private Class<? extends LanguageDriver> defaultScriptingLanguageDriver;

  // issue #19. No default provider.
  private DatabaseIdProvider databaseIdProvider;

  private Class<? extends VFS> vfs;

  private Cache cache;

  private ObjectFactory objectFactory;

  private ObjectWrapperFactory objectWrapperFactory;

  public void setObjectFactory(ObjectFactory objectFactory) {
    this.objectFactory = objectFactory;
  }

  public void setObjectWrapperFactory(ObjectWrapperFactory objectWrapperFactory) {
    this.objectWrapperFactory = objectWrapperFactory;
  }

  public DatabaseIdProvider getDatabaseIdProvider() {
    return databaseIdProvider;
  }

  public void setDatabaseIdProvider(DatabaseIdProvider databaseIdProvider) {
    this.databaseIdProvider = databaseIdProvider;
  }

  public Class<? extends VFS> getVfs() {
    return this.vfs;
  }

  public void setVfs(Class<? extends VFS> vfs) {
    this.vfs = vfs;
  }

  public Cache getCache() {
    return this.cache;
  }

  public void setCache(Cache cache) {
    this.cache = cache;
  }

  public void setPlugins(Interceptor... plugins) {
    this.plugins = plugins;
  }

  public void setTypeAliasesPackage(String typeAliasesPackage) {
    this.typeAliasesPackage = typeAliasesPackage;
  }

  public void setTypeAliasesSuperType(Class<?> typeAliasesSuperType) {
    this.typeAliasesSuperType = typeAliasesSuperType;
  }

  public void setTypeHandlersPackage(String typeHandlersPackage) {
    this.typeHandlersPackage = typeHandlersPackage;
  }

  public void setTypeHandlers(TypeHandler<?>... typeHandlers) {
    this.typeHandlers = typeHandlers;
  }

  public void setDefaultEnumTypeHandler(
      @SuppressWarnings("rawtypes") Class<? extends TypeHandler> defaultEnumTypeHandler) {
    this.defaultEnumTypeHandler = defaultEnumTypeHandler;
  }

  public void setTypeAliases(Class<?>... typeAliases) {
    this.typeAliases = typeAliases;
  }

  public void setFailFast(boolean failFast) {
    this.failFast = failFast;
  }

  public void setConfigLocation(Resource configLocation) {
    this.configLocation = configLocation;
  }

  public void setConfiguration(Configuration configuration) {
    this.configuration = configuration;
  }

  public void setMapperLocations(Resource... mapperLocations) {
    this.mapperLocations = mapperLocations;
  }
  
  public void setConfigurationProperties(Properties sqlSessionFactoryProperties) {
    this.configurationProperties = sqlSessionFactoryProperties;
  }

  public void setDataSource(DataSource dataSource) {
    if (dataSource instanceof TransactionAwareDataSourceProxy) {
      this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();
    } else {
      this.dataSource = dataSource;
    }
  }
  
  public void setSqlSessionFactoryBuilder(SqlSessionFactoryBuilder sqlSessionFactoryBuilder) {
    this.sqlSessionFactoryBuilder = sqlSessionFactoryBuilder;
  }

  public void setTransactionFactory(TransactionFactory transactionFactory) {
    this.transactionFactory = transactionFactory;
  }


  public void setEnvironment(String environment) {
    this.environment = environment;
  }

  public void setScriptingLanguageDrivers(LanguageDriver... scriptingLanguageDrivers) {
    this.scriptingLanguageDrivers = scriptingLanguageDrivers;
  }

  public void setDefaultScriptingLanguageDriver(Class<? extends LanguageDriver> defaultScriptingLanguageDriver) {
    this.defaultScriptingLanguageDriver = defaultScriptingLanguageDriver;
  }


  @Override
  public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
        "Property 'configuration' and 'configLocation' can not specified with together");

    this.sqlSessionFactory = buildSqlSessionFactory();
  }

  protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

    final Configuration targetConfiguration;

    XMLConfigBuilder xmlConfigBuilder = null;
    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);
      }
    } else if (this.configLocation != null) {
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      targetConfiguration = xmlConfigBuilder.getConfiguration();
    } else {
      LOGGER.debug(
          () -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
      targetConfiguration = new Configuration();
      Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
    }

    Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
    Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
    Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);

    if (hasLength(this.typeAliasesPackage)) {
      scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
          .filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
          .filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
    }

    if (!isEmpty(this.typeAliases)) {
      Stream.of(this.typeAliases).forEach(typeAlias -> {
        targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
        LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
      });
    }

    if (!isEmpty(this.plugins)) {
      Stream.of(this.plugins).forEach(plugin -> {
        targetConfiguration.addInterceptor(plugin);
        LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
      });
    }

    if (hasLength(this.typeHandlersPackage)) {
      scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
          .filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
          .forEach(targetConfiguration.getTypeHandlerRegistry()::register);
    }

    if (!isEmpty(this.typeHandlers)) {
      Stream.of(this.typeHandlers).forEach(typeHandler -> {
        targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
        LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
      });
    }

    targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler);

    if (!isEmpty(this.scriptingLanguageDrivers)) {
      Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
        targetConfiguration.getLanguageRegistry().register(languageDriver);
        LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
      });
    }
    Optional.ofNullable(this.defaultScriptingLanguageDriver)
        .ifPresent(targetConfiguration::setDefaultScriptingLanguage);

    if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls
      try {
        targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
      } catch (SQLException e) {
        throw new IOException("Failed getting a databaseId", e);
      }
    }

    Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);

    if (xmlConfigBuilder != null) {
      try {
        xmlConfigBuilder.parse();
        LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
      } catch (Exception ex) {
        throw new IOException("Failed to parse config resource: " + this.configLocation, ex);
      } finally {
        ErrorContext.instance().reset();
      }
    }

    targetConfiguration.setEnvironment(new Environment(this.environment,
        this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
        this.dataSource));

    if (this.mapperLocations != null) {
      if (this.mapperLocations.length == 0) {
        LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
      } else {
        for (Resource mapperLocation : this.mapperLocations) {
          if (mapperLocation == null) {
            continue;
          }
          try {
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
            xmlMapperBuilder.parse();
          } catch (Exception e) {
            throw new IOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
          } finally {
            ErrorContext.instance().reset();
          }
          LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
    }

    return this.sqlSessionFactoryBuilder.build(targetConfiguration);
  }

  @Override
  public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      afterPropertiesSet();
    }

    return this.sqlSessionFactory;
  }

  @Override
  public Class<? extends SqlSessionFactory> getObjectType() {
    return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isSingleton() {
    return true;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void onApplicationEvent(ApplicationEvent event) {
    if (failFast && event instanceof ContextRefreshedEvent) {
      // fail-fast -> check all statements are completed
      this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
    }
  }

  private Set<Class<?>> scanClasses(String packagePatterns, Class<?> assignableType) throws IOException {
    Set<Class<?>> classes = new HashSet<>();
    String[] packagePatternArray = tokenizeToStringArray(packagePatterns,
        ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
    for (String packagePattern : packagePatternArray) {
      Resource[] resources = RESOURCE_PATTERN_RESOLVER.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
          + ClassUtils.convertClassNameToResourcePath(packagePattern) + "/**/*.class");
      for (Resource resource : resources) {
        try {
          ClassMetadata classMetadata = METADATA_READER_FACTORY.getMetadataReader(resource).getClassMetadata();
          Class<?> clazz = Resources.classForName(classMetadata.getClassName());
          if (assignableType == null || assignableType.isAssignableFrom(clazz)) {
            classes.add(clazz);
          }
        } catch (Throwable e) {
          LOGGER.warn(() -> "Cannot load the '" + resource + "'. Cause by " + e.toString());
        }
      }
    }
    return classes;
  }

}

还是带着以下问题来看源码:

  • 什么时候完成的SqlSessionFactoryBuilder对象的创建?
  • 什么时候完成的SqlSessionFactory对象的创建?
  • Mapper层的动态代理对象是何时创建并注入到Spring容器进行管理的?

2.2 什么时候完成的SqlSessionFactoryBuilder对象的创建?

通过SqlSessionFactoryBean类源码我们可以看到,SqlSessionFactoryBuilder作为SqlSessionFactoryBean类的成员属性在对象创建的时候会进行创建。
在这里插入图片描述

2.3 什么时候完成的SqlSessionFactory对象的创建并注入Spring容器的?

SqlSessionFactoryBean类实现InitializingBean接口,实现了afterPropertiesSet方法,那么这个类肯定在该方法中实现了某些初始化的逻辑代码, 一块儿看看这个方法

  @Override
  public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
        "Property 'configuration' and 'configLocation' can not specified with together");
	// 对SqlSessionFactory对象进行赋值,治理是通过buildSqlSessionFactory实现了SqlSessionFactory对象的创建
    this.sqlSessionFactory = buildSqlSessionFactory();
  }

再来看看那这个buildSqlSessionFactory方法

这个方法其实就干了下面两件事

  • 完成Mybatis全局配置文件的解析, 得到Configuration对象
  • 调用sqlSessionFactoryBuilder.build(targetConfiguration)创建SqlSessionFactory对象;
  protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

    final Configuration targetConfiguration;

    XMLConfigBuilder xmlConfigBuilder = null;
    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);
      }
    } else if (this.configLocation != null) {
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      targetConfiguration = xmlConfigBuilder.getConfiguration();
    } else {
      LOGGER.debug(
          () -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
      targetConfiguration = new Configuration();
      Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
    }

    Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
    Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
    Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);

    if (hasLength(this.typeAliasesPackage)) {
      scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
          .filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
          .filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
    }

    if (!isEmpty(this.typeAliases)) {
      Stream.of(this.typeAliases).forEach(typeAlias -> {
        targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
        LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
      });
    }

    if (!isEmpty(this.plugins)) {
      Stream.of(this.plugins).forEach(plugin -> {
        targetConfiguration.addInterceptor(plugin);
        LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
      });
    }

    if (hasLength(this.typeHandlersPackage)) {
      scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
          .filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
          .forEach(targetConfiguration.getTypeHandlerRegistry()::register);
    }

    if (!isEmpty(this.typeHandlers)) {
      Stream.of(this.typeHandlers).forEach(typeHandler -> {
        targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
        LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
      });
    }

    targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler);

    if (!isEmpty(this.scriptingLanguageDrivers)) {
      Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
        targetConfiguration.getLanguageRegistry().register(languageDriver);
        LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
      });
    }
    Optional.ofNullable(this.defaultScriptingLanguageDriver)
        .ifPresent(targetConfiguration::setDefaultScriptingLanguage);

    if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls
      try {
        targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
      } catch (SQLException e) {
        throw new IOException("Failed getting a databaseId", e);
      }
    }

    Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);

    if (xmlConfigBuilder != null) {
      try {
        xmlConfigBuilder.parse();
        LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
      } catch (Exception ex) {
        throw new IOException("Failed to parse config resource: " + this.configLocation, ex);
      } finally {
        ErrorContext.instance().reset();
      }
    }

    targetConfiguration.setEnvironment(new Environment(this.environment,
        this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
        this.dataSource));

    if (this.mapperLocations != null) {
      if (this.mapperLocations.length == 0) {
        LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
      } else {
        for (Resource mapperLocation : this.mapperLocations) {
          if (mapperLocation == null) {
            continue;
          }
          try {
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
            xmlMapperBuilder.parse();
          } catch (Exception e) {
            throw new IOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
          } finally {
            ErrorContext.instance().reset();
          }
          LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
    }
	// 当前方法之前的代码其实就是在完成Mybatis全局配置文件的解析, 得到Configuration对象
	// 然后调用sqlSessionFactoryBuilder.build(targetConfiguration)创建SqlSessionFactory对象;
    return this.sqlSessionFactoryBuilder.build(targetConfiguration);
  }

2.4 SqlSessionFactory对象是怎么注入Spring容器的?

public class SqlSessionFactoryBean
    implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
    } 

我们可以看到SqlSessionFactoryBean实现了FactoryBean接口,
我们都知道有很多种方法可以将Bean对象注入到Spring容器中, 实现FactoryBean接口重写getObject方法就是其中一种, 这里就是利用该方法完成的SqlSessionFactory对象的注入。 看看SqlSessionFactoryBeangetObject`方法的实现

  @Override
  public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      afterPropertiesSet();
    }

afterPropertiesSet()方法中完成了SqlSessionFactory对象的创建。
在这里插入图片描述
这样就完成了SqlSessionFactory注入Spring容器。

2.5 什么时候完成的Mapper接口对象的创建并注入Spring容器的?

2.5.1 spring配置文件中关于MapperScannerConfigurer的配置

还是从配置文件applicationContext.xml文件为入口,在applicationContext.xml文件中我们配置了一个Bean为MapperScannerConfigurer
这里我们配置了mapper层的基础扫描路径basePackage
在这里插入图片描述
在这里插入图片描述

2.5.2 MapperScannerConfigurer类

看看MapperScannerConfigurer类的注释信息, 我给大家翻译了一下, 勉强能说明是什么意思凑合看一下

/**
 * BeanDefinitionRegistryPostProcessor that searches recursively starting from a base package for interfaces and
 * registers them as {@code MapperFactoryBean}. Note that only interfaces with at least one method will be registered;
 * concrete classes will be ignored.
 * BeanDefinitionRegistryPostProcessor,会从Mapper层定义的base包开始递归检索接口并将它们注册为MapperFactoryBean。
 * 请注意,将仅注册至少具有一个方法的接口;具体实现类将被忽略。
 * <p>
 * This class was a {code BeanFactoryPostProcessor} until 1.0.1 version. It changed to
 * {@code BeanDefinitionRegistryPostProcessor} in 1.0.2. See https://jira.springsource.org/browse/SPR-8269 for the
 * details.
 * 直到mybatis-spring的1.0.1版本之前,当前类都使用的是实现BPP接口(BeanFactoryPostProcessor)来完成这个功能的,
 * 但是在1.0.2版本之后, 我们对其进行了变更, 当前类使用的是实现BFPP(BeanDefinitionRegistryPostProcessor)来完成这个功能.
 * <p>
 * The {@code basePackage} property can contain more than one package name, separated by either commas or semicolons.
 * <p>
 * basePackage这个属性可包含多个基础包路径名称,使用逗号或分号分隔
 * This class supports filtering the mappers created by either specifying a marker interface or an annotation. The
 * {@code annotationClass} property specifies an annotation to search for. The {@code markerInterface} property
 * specifies a parent interface to search for. If both properties are specified, mappers are added for interfaces that
 * match <em>either</em> criteria. By default, these two properties are null, so all interfaces in the given
 * {@code basePackage} are added as mappers.
 * 此类支持筛选通过指定标记接口或注释创建的映射器。属性annotationClass指定要搜索的注释。markerInterface属性指定要搜索的父接口。
 * 如果同时指定了这两个属性,则会为与任一条件匹配的接口添加映射器。
 * 默认情况下,这两个属性为 null,因此给定 basePackage中的所有接口都添加为Mapper映射器。
 * <p>
 * This configurer enables autowire for all the beans that it creates so that they are automatically autowired with the
 * proper {@code SqlSessionFactory} or {@code SqlSessionTemplate}. If there is more than one {@code SqlSessionFactory}
 * in the application, however, autowiring cannot be used. In this case you must explicitly specify either an
 * {@code SqlSessionFactory} or an {@code SqlSessionTemplate} to use via the <em>bean name</em> properties. Bean names
 * are used rather than actual objects because Spring does not initialize property placeholders until after this class
 * is processed.
 * 此配置器为其创建的所有 bean 启用自动装配,以便它们自动装配到正确的SqlSessionFactory或SqlSessionTemplate。
 * 但是,如果应用程序中有多个SqlSessionFactory,则无法使用自动装配。
 * 在这种情况下,您必须显式指定SqlSessionFactory或SqlSessionTemplate以通过 Bean Name属性使用。
 * 使用 Bean Name而不是实际对象,因为 Spring 在处理此类之前不会初始化属性占位符。
 * <p>
 * Passing in an actual object which may require placeholders (i.e. DB user password) will fail. Using bean names defers
 * actual object creation until later in the startup process, after all placeholder substitution is completed. However,
 * note that this configurer does support property placeholders of its <em>own</em> properties. The
 * <code>basePackage</code> and bean name properties all support <code>${property}</code> style substitution.
 * 传入可能需要占位符的实际对象(即数据库用户密码)会失败。
 * 使用 Bean Name会将实际的对象创建推迟到启动过程的稍后阶段,在所有占位符替换完成后。
 * 但是,请注意,此配置器确实支持其自己的属性的属性占位符。basePackage  Bean name 属性都支持 ${property} 样式替换。
 * <p>
 * Configuration sample:
 * 配置示例
 * 之前在applicationContext.xml我为什么之后可以这样配置, 就是通过这里的注释说明
 * <pre class="code">
 * {@code
 *   <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
 *       <property name="basePackage" value="org.mybatis.spring.sample.mapper" />
 *       <!-- optional unless there are multiple session factories defined -->
 *       <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
 *   </bean>
 * }
 * </pre>
 *
 * @author Hunter Presnall
 * @author Eduardo Macarron
 *
 * @see MapperFactoryBean
 * @see ClassPathMapperScanner
 */

注意:

 直到mybatis-spring的1.0.1版本之前,当前类都使用的是实现BPP接口(BeanFactoryPostProcessor)来完成这个功能的. 
但是在1.0.2版本之后, 我们对其进行了变更, 当前类使用的是实现BFPP(BeanDefinitionRegistryPostProcessor)来完成这个功能.

我这里使用的是mybatis-spring2.1.0版本, 请注意版本问题。

看一下UML类图
在这里插入图片描述
MapperScannerConfigurer类的注释信息明确告诉我们了,这个类实现了BeanDefinitionRegistryPostProcessor接口,会从Mapper层定义的base包开始递归检索接口并将它们注册为MapperFactoryBean

MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,那么他必然实现了postProcessBeanDefinitionRegistry

2.5.3 MapperScannerConfigurerpostProcessBeanDefinitionRegistry方法**

  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }
    // ClassPathBeanDefinitionScanner类的子类,就是通过 basePackage、annotationClass 或 markerInterface 注册Bean到Spring容器中用的。
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    // 下面一堆set方法就是设置ClassPathMapperScanner或从父类ClassPathBeanDefinitionScanner继承的属性值的, 这没啥好说的
    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);
    scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
    if (StringUtils.hasText(lazyInitialization)) {
      scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
    }
    if (StringUtils.hasText(defaultScope)) {
      scanner.setDefaultScope(defaultScope);
    }
    scanner.registerFilters();
    // 核心代码就是这一行,扫描我们配置的包路径下的所有Mapper接口并注册成MapperFactoryBean对象
    // 这里我们配置的basePackage可能有多个,类似这样basePackage = "com.xxxx.mapper,com.xxxx.dao"
    // StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)的作用就是将
    // basePackage = "com.xxxx.mapper,com.xxxx.dao"分割成 String[]{"com.xxxx.mapper", "com.xxxx.dao"}数组
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

2.5.4 ClassPathMapperScanner类

ClassPathMapperScanner类ClassPathBeanDefinitionScanner类的子类, 主要用途就是通过 basePackage、annotationClass 或 markerInterface 注册Bean到Spring容器中。

源码中对于该类的注释信息如下:

一个 ClassPathBeanDefinitionScanner,它通过 basePackage、annotationClass 或
markerInterface 注册映射器。如果指定了 annotationClass 和/或
markerInterface,则仅搜索指定的类型(将禁用搜索所有接口)。 此功能以前是 MapperScannerConfigurer
的私有类,但在 1.2.0 版中被分解。

2.5.4.1 scan()方法

看看scanner.scan()方法

实际调用的是ClassPathBeanDefinitionScanner类的scan方法
在这里插入图片描述

2.5.4.2 doScan()方法

doScan()方法, 因为ClassPathMapperScanner类对该方法进行了重写, 这里调用的是ClassPathMapperScanner类的doScan()方法, 又在doScan()方法调用了父类的doScan()方法

  /**
   * Calls the parent search that will search and register all the candidates. Then the registered objects are post
   * processed to set them as MapperFactoryBeans
   */
  @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

    if (beanDefinitions.isEmpty()) {
      LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
          + "' package. Please check your configuration.");
    } else {
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }

父类ClassPathBeanDefinitionScanner类的doScan()方法

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Assert.notEmpty(basePackages, "At least one base package must be specified");
        Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet();
        String[] var3 = basePackages;
        int var4 = basePackages.length;

        // 扫描basePackage路径下的java类文件,先全部转为Resource,然后再判断拿出符合条件的bd
        for(int var5 = 0; var5 < var4; ++var5) {
            String basePackage = var3[var5];
            Set<BeanDefinition> candidates = this.findCandidateComponents(basePackage);
            Iterator var8 = candidates.iterator();

            while(var8.hasNext()) {
                BeanDefinition candidate = (BeanDefinition)var8.next();
				// 解析scope属性
                ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
                candidate.setScope(scopeMetadata.getScopeName());

				// 获取beanName
				// 先判断注解上有没有显示设置beanName
				// 没有的话,就以类名小写为beanName
                String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);

				// 如果这个类是AbstractBeanDefinition类型
				// 则为他设置默认值,比如lazy/init/destroy
				// 通过扫描出来的bd是ScannedGenericBeanDefinition,实现了AbstractBeanDefinition
                if (candidate instanceof AbstractBeanDefinition) {
                    this.postProcessBeanDefinition((AbstractBeanDefinition)candidate, beanName);
                }
				// 如果这个类是AnnotatedBeanDefinition类型
				// 处理加了注解的类
				// 把常用注解设置到AnnotationBeanDefinition中
                if (candidate instanceof AnnotatedBeanDefinition) {
                    AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition)candidate);
                }

                if (this.checkCandidate(beanName, candidate)) {
                    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                    definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                    beanDefinitions.add(definitionHolder);
                    this.registerBeanDefinition(definitionHolder, this.registry);
                }
            }
        }
        return beanDefinitions;
    }

通过父类doScan方法的调用获取到了所有的Mapper的接口的BeanDefinition信息。
在这里插入图片描述

2.5.4.3 processBeanDefinitions()方法

下面继续执行 processBeanDefinitions(beanDefinitions), 接着看看这个方法

  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    AbstractBeanDefinition definition;
    BeanDefinitionRegistry registry = getRegistry();
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (AbstractBeanDefinition) holder.getBeanDefinition();
      boolean scopedProxy = false;
      if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) {
        definition = (AbstractBeanDefinition) Optional
            .ofNullable(((RootBeanDefinition) definition).getDecoratedDefinition())
            .map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> new IllegalStateException(
                "The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]"));
        scopedProxy = true;
      }
      String beanClassName = definition.getBeanClassName();
      LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
          + "' mapperInterface");

      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      // 设置BeanDefinition的构造函数参数值为当前BeanDefinition的类名,也就是***Mapper接口的类名
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
      try {
        // for spring-native
        definition.getPropertyValues().add("mapperInterface", Resources.classForName(beanClassName));
      } catch (ClassNotFoundException ignore) {
        // ignore
      }
	  // 设置BeanDefinition的beanClass为MapperFactoryBean.class
	  // 这里一修改, 就相当于在创建Bean对象的时候都是通过MapperFactoryBean来创建的
      definition.setBeanClass(this.mapperFactoryBeanClass);

      // 添加属性addToConfig为true, 因为ClassPathMapperScanner类的addToConfig属性默认为true
      definition.getPropertyValues().add("addToConfig", this.addToConfig);

      // Attribute for MockitoPostProcessor
      // https://github.com/mybatis/spring-boot-starter/issues/475
      definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName);

      boolean explicitFactoryUsed = false;
      if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
        definition.getPropertyValues().add("sqlSessionFactory",
            new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionFactory != null) {
        definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
        explicitFactoryUsed = true;
      }

      if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
        if (explicitFactoryUsed) {
          LOGGER.warn(
              () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate",
            new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionTemplate != null) {
        if (explicitFactoryUsed) {
          LOGGER.warn(
              () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
        explicitFactoryUsed = true;
      }

      if (!explicitFactoryUsed) {
        LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        // 设置属性按类型自动注入
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }

      definition.setLazyInit(lazyInitialization);

      if (scopedProxy) {
        continue;
      }

      if (ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope()) && defaultScope != null) {
        definition.setScope(defaultScope);
      }

      if (!definition.isSingleton()) {
        BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true);
        if (registry.containsBeanDefinition(proxyHolder.getBeanName())) {
          registry.removeBeanDefinition(proxyHolder.getBeanName());
        }
        registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition());
      }

    }
  }

在这里插入图片描述

有上述方法我们可以知道,Mapper层接口的Bean对象都是通过MapperFactoryBean来创建的, mapper层的动态代理对象肯定也是借助MapperFactoryBean去完成了.

2.6 MapperFactoryBean是如何完成代理对象创建并注入Spring容器的?

首先看下 MapperFactoryBean的类图
在这里插入图片描述
发现这个类即实现了FactoryBean接口, 也实现了InitializingBean接口, 那就一起来看看分别在
getObject()afterPropertiesSet()中都做了什么逻辑处理

2.6.1 MapperFactoryBeanSqlSessionFactoryBean是如何关联的

2.6.1.1 MapperFactoryBeanafterPropertiesSet方法

MapperFactoryBean类没有该方法, 肯定是在它的父类中实现的,在DaoSupport类中可以找到该方法

public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
		// Let abstract subclasses check their configuration.
		// 本类中的该方法是个抽象的方法,这里肯定调用的是子类的方法(使用了模板方法模式)
		checkDaoConfig();

		// Let concrete implementations initialize themselves.
		try {
			initDao();
		}
		catch (Exception ex) {
			throw new BeanInitializationException("Initialization of DAO failed", ex);
		}
	}

这个checkDaoConfig()方法在SqlSessionDaoSupport类中紧紧只是进行了sqlSessionTemplate是否注入完成的判断, 在SqlSessionDaoSupport类的子类MapperFactoryBean中又进行了重写

MapperFactoryBean类中的checkDaoConfig方法

  @Override
  protected void checkDaoConfig() {
    // 调用了父类的该方法检测sqlSessionTemplate是否已经注入
    super.checkDaoConfig();
    // 检查mapperInterface属性是否已经填充
    notNull(this.mapperInterface, "Property 'mapperInterface' is required");
    // 这里就是调用父类的sqlSessionTemplate对象的getConfiguration方法获取Configuration对象
    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        // 这里不就是Mybatis框架里面的addMapper方法么, 到这是不是就明白了。
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }

在MapperFactoryBean注入到容器之后,MapperFactoryBean类中继承自父类的的afterPropertiesSet方法被自动执行, 当前MapperFactoryBean对象映射的接口就被注册到Mybatis框架的mapper注册器中了。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2.6.1 MapperFactoryBeangetObject()方法

  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

在Spring框架扫描所有实现FactoryBean接口的类进行Bean对象的注册时, 这个getObject()方法会被执行,会调用Mybatis框架的通过Mybatis框架的动态代理模式创建mapper接口的代理对象注入到Spring容器中进行托管。
在这里插入图片描述
到此,Mybatis框架中的核心对象都注册到Spring容器中, 使用的时候是需要注入到调用的类中, 就可以使用对应的方法实现对数据库的增删改查了。

3 关于InitializingBean接口的拓展知识

Spring框架为bean提供了两种初始化bean的方式,实现InitializingBean接口或者通过在XML配置文件中添加init-method的方式,这两种方式可以同时使用。

实现InitializingBean接口是直接调用afterPropertiesSet方法,比通过反射调用init-method指定的方法效率要高一点,但是init-method方式消除了对spring的依赖。

如果调用afterPropertiesSet方法时出错,则不调用init-method指定的方法。

InitializingBean是Spring框架提供的拓展接口,InitializingBean接口为bean提供了属性初始化后的处理方法,它只有一个afterPropertiesSet方法,凡是继承该接口的类,在bean的属性初始化后都会执行该方法。

我们可以利用这个来完成很多功能, 比如在中小型系统业务开发中实现数据库的读写分离等业务操作。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

d2l机器翻译数据集

着重讲解一下机器翻译数据集的文件处理&#xff0c;以及最终返回了什么 目录 1.载入文件 2.文本预处理 3.词表 4.集成封装 5.总结返回值 1.载入文件 #save d2l.DATA_HUB[fra-eng] (d2l.DATA_URL fra-eng.zip,94646ad1522d915e7b0f9296181140edcf86a4f5) #save def read…

AJAX-0基础第一天入门

AJAX-第一天入门 学习目标 掌握 axios 相关参数&#xff0c;从服务器获取并解析展示数据掌握接口文档的查看和使用掌握在浏览器的 network 面板中查看请求和响应的内容了解请求和响应报文的组成部分 01.AJAX 概念和 axios 使用 目标 了解 AJAX 概念并掌握 axios 库基本使用…

生信刷题之ROSALIND——Part 2

目录 1、Counting Point MutationsProblemSample DatasetSample OutputCodeOutput 2、Mendels First LawProblemSample DatasetSample OutputCodeOutput 3、Translating RNA into ProteinProblemSample DatasetSample OutputCodeOutput 4、Finding a Motif in DNAProblemSample…

C/C++每日一练(20230415)

目录 1. 交错字符串 &#x1f31f;&#x1f31f; 2. 最短回文串 &#x1f31f;&#x1f31f; 3. 分段函数计算 ※ &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 1. 交错字符串 给定…

【数据库】MySQL的增删改查

目录 1.CRUD 2.新增数据 2.1单行数据全列插入 2.2多行数据指定列插入 3.查询数据 3.1全列查询 3.2指定列查询 3.3查询字段为表达式 3.4别名 3.5去重DISTINCT 3.6排序ORDER BY 3.7 条件查询&#xff1a;WHERE 3.8 分页查询&#xff1a;LIMIT 4.修改数据 5.删除数…

Flutter开发中的线程与事件队列,如何实现高效优化?

Flutter 中线程简要介绍&#xff1a; 主 UI 线程&#xff1a; Flutter 的主 UI 线程通常称为 “UI Isolate”&#xff0c;它是单线程的&#xff0c;负责处理用户界面的渲染和响应用户输入。在主 UI 线程中运行的代码主要包括 Flutter 应用的 UI 组件构建、布局、绘制等操作&a…

基于DSP+FPGA的机载雷达伺服控制系统(二)电源仿真

板级电源分配网络的分析与仿真在硬件电路设计中&#xff0c;电源系统的设计是关键步骤之一&#xff0c;良好的电源系统为电路板 上各种信号的传输提供了保障。本章将研究电源完整性的相关问题&#xff0c;并提出一系列改 进电源质量的措施。 3.1 电源完整性 电源完整性&#xf…

7.思维题(0x3f:从周赛中学算法 2022下)

来自0x3f【从周赛中学算法 - 2022 年周赛题目总结&#xff08;下篇&#xff09;】&#xff1a;https://leetcode.cn/circle/discuss/WR1MJP/ 包含贪心、脑筋急转弯等&#xff0c;挑选一些比较有趣的题目。 注&#xff1a;常见于周赛第二题&#xff08;约占 21%&#xff09;、第…

【Linux】进程间通信 -- System V共享内存

前言 本篇博客介绍第二种进程间通信的方式 – System V System V 有三种方式&#xff1a; 共享内存 消息队列 信号量 本篇博客对于系统调用的函数&#xff0c;会进行一定的封装 文章目录 前言一. System V 共享内存二. 共享内存的原理三. 共享内存的创建四. 共享内存的查看和删…

MinGW MinGW-W64介绍

Table of content 0 Preface/Foreword 1 MinGW-w64 1.1 使用MinGW-w64的原因 1.2 MinGW-w64使用场景 1.3 官网 2 GCC & LLVM 2.1 编译器构成 2.2 GCC 2.3 LLVM 2.3.1 Clang 0 Preface/Foreword MInGW全称为&#xff1a;Minimalist GNU on Windows.将经典的开源C…

Unity VFX -- (4)创建burst粒子效果

如果用户成功达成某个目标&#xff0c;我们可以使用一个爆裂的礼花来激励用户。如果角色挥舞刀剑&#xff0c;我们可以做出剑气来增加气势。如果角色落到地面上&#xff0c;我们可以在脚部做出飞舞的灰尘来增加表现力。这些都可以视为burst粒子效果。 下面是一些burst粒子效果&…

JVM学习(六):类加载子系统

目录 〇、前言 一、类加载子系统 1.1 内存结构概述 1.2 类加载器及类加载过程概述 1.2.1 类加载器 1.2.2 类加载过程 1.3 类加载过程一&#xff1a;Loading 1.3.1 加载过程 1.3.2 加载类的方式 1.4 类加载过程二&#xff1a;Linking 1.4.1 验证(Verify) 1.4.…

贯穿设计模式第八话--设计原则总结篇

&#x1f973;&#x1f973;&#x1f973; 茫茫人海千千万万&#xff0c;感谢这一刻你看到了我的文章&#xff0c;感谢观赏&#xff0c;大家好呀&#xff0c;我是最爱吃鱼罐头&#xff0c;大家可以叫鱼罐头呦~&#x1f973;&#x1f973;&#x1f973; 从今天开始&#xff0c;将…

在uos上编译opencv

作者&#xff1a;朱金灿 来源&#xff1a;clever101的专栏 为什么大多数人学不会人工智能编程&#xff1f;>>> 下载源码并创建build文件夹 系统环境为操作系统为&#xff1a;UnionTech OS Server 20 Enterprise&#xff0c;处理器为: 华为鲲鹏处理器&#xff08;ar…

C++11(上)

目录 1&#xff1a;列表初始化 2&#xff1a;std::initializer_list 3:变量类型推导 3.1:auto推导类型 3.2:decltype 3.3:nullptr 4:范围for 5:STL新增容器和容器新增接口 5.1:array 6:左值引用和右值引用 6.1:左值 6.2:右值 6.3:左值引用 6.4:右值引用 6.5:左值…

python常用库之time库

目录 一、前言time库中的常用函数 二、time()函数三、localtime()和gmtime()函数四、strftime() 、asctime()、mktime()函数&#xff08;一&#xff09;strftime()函数&#xff08;二&#xff09;asctime()函数&#xff08;三&#xff09;mktime()函数 五、ctime()函数六、stri…

【2023最新】超详细图文保姆级教程:App开发新手入门(5)

上文回顾&#xff0c;我们已经完成了一个应用的真机调试&#xff0c;本章我们来了解一下如何引入YonBuilder移动开发的&#xff08;原生&#xff09;移动插件, 并利用移动插件完成一个简单的视频播放器。 8. 「移动插件」的使用 8.1 什么是 「移动插件」&#xff1f; 用通俗…

TensorFlow Lite,ML Kit 和 Flutter 移动深度学习:1~5

原文&#xff1a;Mobile Deep Learning with TensorFlow Lite, ML Kit and Flutter 协议&#xff1a;CC BY-NC-SA 4.0 译者&#xff1a;飞龙 本文来自【ApacheCN 深度学习 译文集】&#xff0c;采用译后编辑&#xff08;MTPE&#xff09;流程来尽可能提升效率。 不要担心自己的…

【高危】Apache Spark 权限提升漏洞(CVE-2023-22946)

漏洞描述 Apache Spark 是一款支持非循环数据流和内存计算的大规模数据处理引擎。 使用 spark-submit 脚本在集群中启动任务可以通过指定proxy-user参数限制运行用户。在 Apache Spark 受影响版本中&#xff0c;攻击者通过指定自定义的classpath&#xff0c;则可以覆盖该配置…

2023最新面试题-Java-3

IO流 1. java 中 IO 流分为几种? 按照流的流向分&#xff0c;可以分为输入流和输出流&#xff1b;按照操作单元划分&#xff0c;可以划分为字节流和字符流&#xff1b;按照流 的角色划分为节点流和处理流。 Java Io 流共涉及 40 多个类&#xff0c;这些类看上去很杂乱&…