Notas del código fuente del marco Mybatis (11) Demostración de integración de Spring y análisis del principio de integración de Mybatis

1 Ejemplo de Mybatis de integración de Spring Framework

1.1 Crear un proyecto de demostración

Insertar descripción de la imagen aquí

1.2 Estructura del directorio del proyecto

Insertar descripción de la imagen aquí

1.3 Archivo pom.xml de configuración de dependencia

<?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 Archivo de configuración del proyecto

1.4.1 Archivo de configuración de conexión a la base de datos
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 Archivo de configuración global del marco 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 Archivo de configuración del marco 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 **Archivo Mapper.xml

Insertar descripción de la imagen aquí

1.4.5 Interfaz del asignador
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 Clase de entidad
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 Pruebas

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

Insertar descripción de la imagen aquí

2 Análisis del principio de integración de Mybatis con Spring framework

Veamos cómo se escribió el código de prueba si antes solo usábamos el marco 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();

Compare el marco de primavera integrado con el marco Mybatis

public class AppTest 
{
    
    
    @Autowired
    private LibBookMapper bookMapper;

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

Compare y descubra si se han simplificado muchas operaciones. Las SqlSession对象的创建过程 y Mapper动态代理对象的创建过程 han desaparecido, luego mybatis-spring ¿Cómo lograr la simplificación de esta operación?Analicemos este proceso juntos.

2.1 SqlSessionFactoryBean

Primero, echemos un vistazo al archivo de configuración de Spring applicationContext.xml, que define la declaración del bean de sqlSessionFactory.Insertar descripción de la imagen aquí
El objeto creado es SqlSessionFactoryBean objeto , Como se muestra arriba El contenido central del marco Mybatis se ha discutido en detalle. Todos deberíamos saber que en Mybatis todos creamos objetos a través de SqlSessionFactoryBuilder, y luego pasar Creación de objetosObjeto para obtener conexión a la base de datos y otras operaciones. SqlSessionFactorySqlSessionFactorySqlSession

Al conocer el significado después de ver el nombre, SqlSessionFactoryBeanla determinación del objeto también está estrechamente relacionada con la creaciónSqlSessionFactory. Mira el código fuente

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

}

Veamos el código fuente con las siguientes preguntas:

  • ¿Cuándo se completó la creación del objetoSqlSessionFactoryBuilder?
  • ¿Cuándo se completó la creación del objetoSqlSessionFactory?
  • ¿Cuándo se crea e inyecta el objeto proxy dinámico de la capa Mapper en el contenedor Spring para su administración?

2.2 ¿Cuándo se completó la creación del objetoSqlSessionFactoryBuilder?

A través del código fuente de la clase SqlSessionFactoryBean, podemos ver que SqlSessionFactoryBuilder se utiliza como atributo miembro de SqlSessionFactoryBean. clase cuando se crea el objeto.
Insertar descripción de la imagen aquí

2.3 ¿Cuándo se completó la creación del y se inyectó en el contenedor Spring? SqlSessionFactory

SqlSessionFactoryBeanLa clase implementa laInitializingBean interfaz e implementa el métodoafterPropertiesSet, entonces esta clase debe haber implementado algún código lógico de inicialización en el método, juntos Verificar este método

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

Echemos un vistazo a estebuildSqlSessionFactorymétodo

Este método en realidad hace las dos cosas siguientes:

  • Complete el análisis del archivo de configuración global de Mybatis y obtenga el objeto de Configuración
  • Llame a sqlSessionFactoryBuilder.build (targetConfiguration) para crear un objeto 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¿Cómo se inyectan los objetos en el contenedor Spring?

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

Podemos ver queSqlSessionFactoryBean implementa la interfaz FactoryBean,
Todos sabemos que hay muchas formas de convertir Beans El objeto se inyecta en el contenedor Spring, 实现FactoryBeanLa interfaz anula el método getObject就是其中一种, 这里就是利用该方法完成的SqlSessionFactory对象的注入。 看看SqlSessionFactoryBean< a i=7> Implementación del método getObject`

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


Insertar descripción de la imagen aquí
La creación del objeto SqlSessionFactory se completa en el método Esto completa la inyección de SqlSessionFactory en el contenedor Spring.

2.5 ¿Cuándo se completó y se inyectó en el contenedor Spring la creación del objeto de interfaz Mapper

2.5.1 Configuración de MapperScannerConfigurer en el archivo de configuración de Spring

La entrada aún es del archivo de configuraciónapplicationContext.xml. En el archivo applicationContext.xml configuramos un Bean como MapperScannerConfigurer
aquí Configuramos el paquete base de la ruta de escaneo básica de la capa del mapeador
Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí

2.5.2 MapperScannerConfigurer类

Echa un vistazo La información de anotación de la clase MapperScannerConfigurer, la traduje para ti, apenas puedo explicar lo que significa, solo échale un vistazo

/**
 * 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)来完成这个功能.

Estoy usando . Preste atención al problema de la versión. mybatis-spring2.1.0版本

Mire el diagrama de clases UML
Insertar descripción de la imagen aquí
La información de anotación en la clase MapperScannerConfigurer nos dice claramente que esta clase implementa BeanDefinitionRegistryPostProcessorLas interfaces se recuperarán recursivamente del paquete base definido por la capa Mapper y registrado comoMapperFactoryBean

MapperScannerConfigurerSi implementa la interfaz BeanDefinitionRegistryPostProcessor, entonces debe haber implementado postProcessBeanDefinitionRegistry.

2.5.3 MapperScannerConfigurerObjetivopostProcessBeanDefinitionRegistryMétodo**
  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类 es una subclase de la clase ClassPathBeanDefinitionScanner. Su objetivo principal es registrar beans en el contenedor Spring a través de basePackage, annotationClass o MarkerInterface.

La información de anotación para esta clase en el código fuente es la siguiente:

Un ClassPathBeanDefinitionScanner que registra un asignador a través de basePackage, annotationClass o
MarkerInterface. Si se especifican annotationClass y/o MarkerInterface, solo se buscará el tipo especificado (la búsqueda en todas las interfaces estará deshabilitada). Esta funcionalidad era anteriormente una clase privada de MapperScannerConfigurer pero se incluyó en la versión 1.2.0.

2.5.4.1 método de escaneo ()

Verscanner.scan()Método

Lo que realmente se llama es el método de escaneo de la clase ClassPathBeanDefinitionScanner
Insertar descripción de la imagen aquí

2.5.4.2 Método doScan()

Método doScan(), debido a que la clase ClassPathMapperScanner anula este método, lo que se llama aquí es el ClassPathMapperScanner =3> método, y el método de la clase principal se llama en el método 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;
  }

clase principal método doScan() de ClassPathBeanDefinitionScanner clase

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

La información BeanDefinition de todas las interfaces Mapper se obtiene llamando al método doScan de la clase principal.
Insertar descripción de la imagen aquí

2.5.4.3 método ProcessBeanDefinitions()

Continúe la ejecución a continuación processBeanDefinitions(beanDefinitions), luego observe este método

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

    }
  }

Insertar descripción de la imagen aquí

Con el método anterior, podemos saber que todos los objetos Bean de la interfaz de la capa Mapper se crean a través de MapperFactoryBean, y los objetos proxy dinámicos de la capa Mapper también deben usar MapperFactoryBeanVe y termina.

2.6 ¿Cómo crea MapperFactoryBean

Primero mire el diagrama de clases de y descubra que esta clase implementa la interfaz y también implementa , echemos un vistazo al procesamiento lógico realizado en y respectivamente.MapperFactoryBean
Insertar descripción de la imagen aquí
FactoryBeanInitializingBean
getObject()afterPropertiesSet()

2.6.1 MapperFactoryBeandadoSqlSessionFactoryBean是如何关联的
2.6.1.1 MapperFactoryBeanobjetivoafterPropertiesSetmétodo

MapperFactoryBeanDaoSupportLa clase

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

Este métodocheckDaoConfig() en la clase SqlSessionDaoSupport solo determina si se completa la inyección de sqlSessionTemplate. En SqlSessionDaoSupport Reescrito en subclases de la claseMapperFactoryBean

MapperFactoryBeanmétodo checkDaoConfig en clase

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

Después de inyectar MapperFactoryBean en el contenedor, el método heredado de la clase principal en la clase MapperFactoryBean se ejecuta automáticamente y la interfaz se asigna mediante el objeto MapperFactoryBean actual está registrado en el registro del asignador del marco Mybatis. afterPropertiesSet
Insertar descripción de la imagen aquí

Insertar descripción de la imagen aquí

Insertar descripción de la imagen aquí

Insertar descripción de la imagen aquí

2.6.1 MapperFactoryBeanobjetivogetObject()método
  @Override
  public T getObject() throws Exception {
    
    
    return getSqlSession().getMapper(this.mapperInterface);
  }

Cuando el marco Spring escanea todas las clases que implementan la interfaz FactoryBean para registrar objetos Bean, se ejecutará el método getObject () y el proxy dinámico del marco Mybatis a través del marco Mybatis. será llamado El patrón crea un objeto proxy de la interfaz del mapeador y lo inyecta en el contenedor Spring para alojarlo.
Insertar descripción de la imagen aquí
En este punto, los objetos principales en el marco Mybatis se han registrado en el contenedor Spring. Cuando se usan, deben inyectarse en la clase que llama y se pueden usar los métodos correspondientes para agregar. , eliminar, modificar y consultar la base de datos.

3 Conocimiento ampliado sobre la interfaz InitializingBean

El marco Spring proporciona dos formas de inicializar beans, implementando la interfaz InitializingBean o 通过在XML配置文件中添加init-method. Estos dos métodos se pueden utilizar al mismo tiempo.

La interfaz de implementación InitializingBean es llamar directamente al métodoafterPropertiesSet, que es más eficiente que llamar al método especificado por el método init a través de la reflexión, pero el método init-method Elimina la dependencia de Spring.

Si se produce un error al llamar al método afterPropertiesSet, no se llamará al método especificado por init-method.

InitializingBean es una interfaz extendida proporcionada por el marco Spring. La interfaz InitializingBean proporciona al bean un método de procesamiento después de la inicialización del atributo. Tiene solo un método afterPropertiesSet. Toda herencia La clase de esta interfaz ejecutará este método después de que se inicialicen las propiedades del bean.

Podemos usar esto para completar muchas funciones, como realizar operaciones comerciales como la separación de lectura y escritura en la base de datos en el desarrollo comercial de sistemas pequeños y medianos.

Supongo que te gusta

Origin blog.csdn.net/qq_41865652/article/details/129932614
Recomendado
Clasificación