Mybatis フレームワークのソース コード ノート (11) Spring 統合 mybatis のデモンストレーションと統合原理の分析

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 マッパーインターフェイス
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 Mybatis と Spring フレームワークの統合原理の分析

以前に 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();

Mybatis フレームワークと統合された Spring フレームワークを比較する

public class AppTest 
{
    
    
    @Autowired
    private LibBookMapper bookMapper;

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

比較して、多くの操作が簡略化されているかどうかを確認します。SqlSession对象的创建过程Mapper动态代理对象的创建过程 全体がなくなり、mybatis-springこの操作を簡素化するにはどうすればよいでしょうか?このプロセスを一緒に分析しましょう。

2.1 SqlSessionFactoryBean

まず、sqlSessionFactory の Bean 宣言を定義する Spring の構成ファイル applicationContext.xml を見てみましょう。ここに画像の説明を挿入します
作成されるオブジェクトは 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 クラスのソース コードを見ると、SqlSessionFactoryBuilderSqlSessionFactoryBean のメンバー属性として使用されていることがわかります。オブジェクトの作成時にクラスが作成されます。
ここに画像の説明を挿入します

2.3 SqlSessionFactory オブジェクトの作成が完了

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方法を見てみましょう

このメソッドは実際に次の 2 つのことを行います。

  • Mybatis グローバル構成ファイルの分析を完了し、構成オブジェクトを取得します。
  • 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> {
    
    
    } 

SqlSessionFactoryBeanFactoryBean インターフェースを実装していることがわかります。
変換する方法がたくさんあることは誰もが知っています。 Bean オブジェクトは Spring コンテナに挿入されます。实现FactoryBeanインターフェイスは getObject メソッドをオーバーライドします。就是其中一种, 这里就是利用该方法完成的SqlSessionFactory对象的注入。 看看SqlSessionFactoryBean< a i=7> getObject` メソッドの実装

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

SqlSessionFactory オブジェクトの作成は、afterPropertiesSet() メソッドで完了します。
ここに画像の説明を挿入します
これで、Spring コンテナへの SqlSessionFactory の注入が完了しました。

2.5 Mapper インターフェイス オブジェクトの作成が完了し、Spring コンテナに挿入されたのはいつですか?

2.5.1 Spring設定ファイルのMapperScannerConfigurerの設定

エントリは引き続き構成ファイルからのものですapplicationContext.xmlapplicationContext.xml ファイルでは、Bean を MapperScannerConfigurer
として構成します。マッパー層の基本スキャンパス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インターフェイスは、マッパー レイヤーによって定義された基本パッケージから再帰的に取得され、 として登録されます。MapperFactoryBean

MapperScannerConfigurerBeanDefinitionRegistryPostProcessor インターフェースを実装している場合、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.4ClassPathMapperScanner类

ClassPathMapperScanner类ClassPathBeanDefinitionScanner クラスのサブクラスであり、主な目的は、basePackage、annotationClass、または markerInterface を通じて Bean を Spring コンテナに登録することです。

ソースコード内のこのクラスのアノテーション情報は次のとおりです。

basePackage、annotationClass、または
markerInterface 経由でマッパーを登録する ClassPathBeanDefinitionScanner。 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;
    }

すべての Mapper インターフェイスの BeanDefinition 情報は、親クラスの doScan メソッドを呼び出すことによって取得されます。
ここに画像の説明を挿入します

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());
      }

    }
  }

ここに画像の説明を挿入します

上記のメソッドにより、マッパー レイヤー インターフェイスの Bean オブジェクトはすべて MapperFactoryBean を通じて作成され、マッパー レイヤーの動的プロキシ オブジェクトも MapperFactoryBean行って終了します。

2.6 MapperFactoryBean はどのようにしてプロキシ オブジェクトを作成し、Spring コンテナに挿入しますか?

まず のクラス図を見て、このクラスが インターフェイスを実装していることと、 インターフェイス。 でそれぞれ行われる論理処理を見てみましょうMapperFactoryBean
ここに画像の説明を挿入します
FactoryBeanInitializingBean
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() メソッドは、sqlSessionTemplate の挿入が完了したかどうかのみを決定します。このSqlSessionDaoSupportSqlSessionDaoSupportMapperFactoryBean

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 フレームワークのマッパー レジスタに登録されています。
ここに画像の説明を挿入します

ここに画像の説明を挿入します

ここに画像の説明を挿入します

ここに画像の説明を挿入します

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

Spring フレームワークが FactoryBean インターフェイスを実装するすべてのクラスをスキャンして Bean オブジェクトを登録すると、getObject() メソッドが実行され、Mybatis フレームワークを介して Mybatis フレームワークの動的プロキシが実行されます。このパターンはマッパー インターフェイスのプロキシ オブジェクトを作成し、それをホスト用の Spring コンテナに挿入します。
ここに画像の説明を挿入します
この時点で、Mybatis フレームワークのコア オブジェクトは Spring コンテナに登録されています。使用する場合は、呼び出し側のクラスに注入する必要があり、対応するメソッドを使用して追加することができます。 、データベースを削除、変更、クエリします。

3 InitializingBean インターフェースに関する知識の拡張

Spring フレームワークには、InitializingBean インターフェイスまたは 通过在XML配置文件中添加init-method を実装する 2 つの方法で Bean を初期化できます。これら 2 つの方法は同時に使用できます。

実装InitializingBean インターフェイスはafterPropertiesSet メソッドを直接呼び出します。これは、リフレクションを通じて init-method で指定されたメソッドを呼び出すよりも効率的ですが、 init-method メソッド Spring への依存を排除​​します。

afterPropertiesSet メソッドの呼び出し時にエラーが発生した場合、init-method で指定されたメソッドは呼び出されません。

InitializingBean は、Spring フレームワークによって提供される拡張インターフェイスです。InitializingBean インターフェイスは、属性の初期化後の処理メソッドを Bean に提供します。afterPropertiesSet メソッドは 1 つだけあります。すべての継承 このインターフェースのクラスは、Bean のプロパティが初期化された後にこのメソッドを実行します。

これを利用することで、中小規模システムの業務開発においてデータベースの読み書き分離などの業務運用を実現するなど、多くの機能を完結させることができます。

おすすめ

転載: blog.csdn.net/qq_41865652/article/details/129932614