六.吃透Mybatis源码-面试官问我Spring是怎么整合Mybatis的

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
云数据库 RDS MySQL Serverless,价值2615元额度,1个月
简介: 我们在项目中都是使用Spring整合Mybatis进行数据操作,而不会直接使用SqlSession去操作数据库,因为这样操作会显得特别的麻烦。Spring整合Mybatis之后,Spring对Mybatis的核心进行了封装和适配,让我们用起来更加简单。本篇文章将带你了解Spring整合Mybatis的核心原理,从此再也不用担心面试官问我“Spring是如何整合Mybatis的?”

前言

我们在项目中都是使用Spring整合Mybatis进行数据操作,而不会直接使用SqlSession去操作数据库,因为这样操作会显得特别的麻烦。Spring整合Mybatis之后,Spring对Mybatis的核心进行了封装和适配,让我们用起来更加简单。本篇文章将带你了解Spring整合Mybatis的核心原理,从此再也不用担心面试官问我“Spring是如何整合Mybatis的?”

在这里插入图片描述

Spring整合Mybatis案例

第一步,导入需要的依赖包括:驱动包,mybatis包;Spring包;测试包

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>4.3.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>4.3.0.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.3.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>4.3.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

第二步:编写实体类,mapper映射器,mapper.xml 文件 ,这里省略

第三步:编写数据库配置文件

#mysql
mysql.driver=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/test?useSSL=false
mysql.username=root
mysql.password=admin

第四步:编写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:p="http://www.springframework.org/schema/p"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       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-4.3.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">

    <!--1 引入属性文件,在配置中占位使用 -->
    <context:property-placeholder location="classpath*:db.properties" />

    <!--2 配置数据源 -->
    <bean id="datasource" class="org.apache.ibatis.datasource.pooled.PooledDataSource">
        <!--驱动类名 -->
        <property name="driver" value="${mysql.driver}" />
        <!-- url -->
        <property name="url" value="${mysql.url}" />
        <!-- 用户名 -->
        <property name="username" value="${mysql.username}" />
        <!-- 密码 -->
        <property name="password" value="${mysql.password}" />
    </bean>

    <!--3 会话工厂bean sqlSessionFactoryBean -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 数据源 -->
        <property name="dataSource" ref="datasource"></property>
        <!-- 别名 -->
        <property name="typeAliasesPackage" value="cn.whale.domian"></property>
        <!-- sql映射文件路径 -->
        <property name="mapperLocations" value="classpath*:mapper/*Mapper.xml"></property>
<!--        <property name="configLocation" value="classpath*:mybatis-config.xml"></property>-->
<!--        <property name="plugins"></property>-->
    </bean>

    <!--4 自动扫描对象关系映射 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 指定要自动扫描接口的基础包,实现接口 -->
        <property name="basePackage" value="cn.whale.mapper"></property>
    </bean>

    <!--5 声明式事务管理 -->
    <!--定义事物管理器,由spring管理事务 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="datasource"></property>
    </bean>
    <!--支持注解驱动的事务管理,指定事务管理器 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>

</beans>

第五步:编写测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringTest {
   
   
    //注入Mapper接口
    @Autowired
    private StudentMapper studentMapper ;

    @Test
    public void test(){
   
   
        Student student = studentMapper.selectById(1L);
        System.out.println(student);
    }
}

Spring整合Mybatis和单纯使用Mybatis做了哪些改变?

  1. 除了增加了Spring的包以外,增加了一个比较关键的依赖mybatis-spring , 它是Spring整合Mybatis核心的一个包
  2. SqlSessionFactory不在手动创建,而是在Spirng的配置文件中通过注册一个SqlSessionFactoryBean来创建
  3. 以前mybatis-config.xml中配置的DataSource 数据源 和 事务管理 现在在Spring的配置文件中配置了

Spring只是对Mybatis做了整合或者包装,简单点理解就是整合Spring之后只是改变了对Mybatis的配置方式和使用方式,其本质还是使用的是Mybatis的核心,那么我们思考一下,之前在使用Mybatis的时候有几个核心的东西

  1. SqlSessionFactory的创建
  2. SqlSession的创建
  3. MapperPorxy的创建
  4. 事务的管理

那么理解Spring是如何整合Mybatis的就是理解上面的几个核心在Spring中是如何创建和工作的。

SqlSessionFactory的创建

在之前的Mybatis案例中我们是通过 SqlSessionFactoryBuilder.buider(inputStream) 来加载Mybatis配置文件和创建SqlSessionFactory的,在Spring中不一样了,我们在Spring的配置文件中 配置了一个Bean SqlSessionFactoryBean ,如下

<!--3 会话工厂bean sqlSessionFactoryBean -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!-- 数据源 -->
    <property name="dataSource" ref="datasource"></property>
    <!-- 别名 -->
    <property name="typeAliasesPackage" value="cn.whale.domian"></property>
    <!-- sql映射文件路径 -->
    <property name="mapperLocations" value="classpath*:mapper/*Mapper.xml"></property>
<!--        mybatis配置文件-->
<!--        <property name="configLocation" value="classpath*:mybatis-config.xml"></property>-->
<!--        插件配置-->
<!--        <property name="plugins"></property>-->
</bean>

那么我们源码分析的入口就是它了,下面是SqlSessionFactoryBean的继承体系图
在这里插入图片描述
首先SqlSessionFactoryBean是一个FactoryBean的子类,它提供了一个 getObject 来返回SqlSessionFactory 的实例,这个是Spring创建Bean的一种方式,相信使用过Spring的童靴都清楚。

SqlSessionFactoryBean实现类一个InitializingBean接口,该接口提供了一个 afterPropertiesSet 方法,当Bean在初始化的时候会触发afterPropertiesSet方法调用。也就是说 SqlSessionFactoryBean在Bean初始化的过程中会调用其afterPropertiesSet方法。下面是SqlSessionFactoryBean的源码

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

  private static final Logger LOGGER = LoggerFactory.getLogger(SqlSessionFactoryBean.class);
  //mybatis-config.xml 配置文件
  private Resource configLocation;
  //Mybatis的configuration对象
  private Configuration configuration;
  //mapper.xml 映射文件
  private Resource[] mapperLocations;
  //数据源
  private DataSource dataSource;
  //mybatis的事务工厂
  private TransactionFactory transactionFactory;
  //配置sqlSessionFactory Properties
  private Properties configurationProperties;
  //mybatis的SqlSessionFactory的构造器
  private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
  //mybatis的SqlSession工厂
  private SqlSessionFactory sqlSessionFactory;

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

  ...省略...

  //初始化SqlSessionFactoryBean
  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
    this.sqlSessionFactory = buildSqlSessionFactory();
  }


  ...省略...
  //获取对象,通过FactroyBean来实例化
  @Override
  public SqlSessionFactory getObject() throws Exception {
   
   
    if (this.sqlSessionFactory == null) {
   
   
      //如果sqlSessionFactory 是空的就触发afterPropertiesSet方法
      afterPropertiesSet();
    }

    return this.sqlSessionFactory;
  }

SqlSessionFactoryBean 是Mybatis-Spring这个jar包提供的。里面使用到了Mybatis的一些核心类,比如:Configuration , SqlSessionFactory。 在 afterPropertiesSet 方法中 调用 buildSqlSessionFactory() 去buid sqlSessionFactory 。

你可能会问,afterPropertiesSet 和 getObject 这两个方法又什么关系呢?谁先执行呢? afterPropertiesSet 是对 SqlSessionFactoryBean的初始化 ,而getObject 方法是用来创建 SqlSessionFactory 的。这里其实有有俩个Bean,一个是 SqlSessionFactoryBean ,一个是 SqlSessionFactory ,所以是先创建SqlSessionFactoryBean,然后执行afterPropertiesSet 初始化,最后调用getObject 来创建SqlSessionFactory 。所以是afterPropertiesSet 先执行。

下面是org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory 的源码

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
   
   
    //Mybatis的配置类
    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);
      }
    //1.判断是否有配置configLocation,如果有就创建一个XMLConfigBuilder来解析mybatis-config.xml配置文件
    } 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);
    }
   //如果this.objectFactory不为空,就把this.objectFactory设置给 targetConfiguration
    Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
    Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
    Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
    //2.处理别名
    if (hasLength(this.typeAliasesPackage)) {
   
   
      String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
      Stream.of(typeAliasPackageArray).forEach(packageToScan -> {
   
   
       //别名还是注册到Configuration#typeAliasRegistry 中
        targetConfiguration.getTypeAliasRegistry().registerAliases(packageToScan,
            typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
        LOGGER.debug(() -> "Scanned package: '" + packageToScan + "' for aliases");
      });
    }

    if (!isEmpty(this.typeAliases)) {
   
   
      Stream.of(this.typeAliases).forEach(typeAlias -> {
   
   
        targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
        LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
      });
    }
   //3.处理插件 ,还是通过 Configuration.addInterceptor 来注册插件
    if (!isEmpty(this.plugins)) {
   
   
      Stream.of(this.plugins).forEach(plugin -> {
   
   
        targetConfiguration.addInterceptor(plugin);
        LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
      });
    }
     //4.处理插件 ,处理typeHandlersPackage,还是注册到Configuration#TypeHandlerRegistry
    if (hasLength(this.typeHandlersPackage)) {
   
   
      String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
      Stream.of(typeHandlersPackageArray).forEach(packageToScan -> {
   
   
        targetConfiguration.getTypeHandlerRegistry().register(packageToScan);
        LOGGER.debug(() -> "Scanned package: '" + packageToScan + "' for type handlers");
      });
    }

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

    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 NestedIOException("Failed getting a databaseId", e);
      }
    }
     //5.处理缓存
    Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);

     //6.如果有配置configLocaltion ,开始解析xmlConfigBuilder,解析mybtis-config.xml
    if (xmlConfigBuilder != null) {
   
   
      try {
   
   
        xmlConfigBuilder.parse();
        LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
      } catch (Exception ex) {
   
   
        throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
      } finally {
   
   
        ErrorContext.instance().reset();
      }
    }
    //7.创建 Environment 把 SpringManagedTransactionFactory 和 dataSource保存进去
    //如果有指定transactionFactory 就使用指定的,否则就使用SpringManagedTransactionFactory
    targetConfiguration.setEnvironment(new Environment(this.environment,
        this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
        this.dataSource));
    //8.如果mapperLocations不为空,就解析mapper.xml
    if (!isEmpty(this.mapperLocations)) {
   
   
      for (Resource mapperLocation : this.mapperLocations) {
   
   
        if (mapperLocation == null) {
   
   
          continue;
        }

        try {
   
   
        //创建XMLMapperBuilder解析器
          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
              //开始解析mapper.xml
          xmlMapperBuilder.parse();
        } catch (Exception e) {
   
   
          throw new NestedIOException("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 or no matching resources found");
    }
   //9.执行sqlSessionFactoryBuilder.build来创建SqlSessionFactory
    return this.sqlSessionFactoryBuilder.build(targetConfiguration);
  }

这个方法有点长,但是看起来还是比较轻松,在其中我们看到了很多之前看过的代码,总结一下

  1. 如果在xml中定义SqlSessionFactoryBean的时候有指定 configLocation 属性如:<property name="configLocation" value="classpath*:mybatis-config.xml">,就会创建一个 XMLConfigBuilder 来解析mybatis-config.xml配置文件。
  2. 然后判断了objectFactory和objectWrapperFactory,如果有不为空会加入到targetConfiguration中
  3. 如果配置了 typeAliasesPackage ,就会扫描对应的包,把相关的类加入Configurationd TypeAliasRegistry中
  4. 然后判断是否配置了pluns,如果有就解析并注册到Configuration的InterceptorChain
  5. 如果就配置TypeHandlerRegistry ,就注册到Configuration的TypeHandlerRegistry
  6. 如果有没有配置 transactionFactory 就会使用Spring的SpringManagedTransactionFactory事务工厂,和dataSource一起封装到environment对象中,把Environment设置给Configuration
  7. 如果配置了 mapperLocations 属性,就创建一个 XMLMapperBuilder 来解析mapper.xml
  8. 最后通过 sqlSessionFactoryBuilder.build 来构建sqlSessionFactory,默认实现是DefaultSqlSessionFactory

好吧看到上面的这些步骤你应该很熟悉的,在之间分析Mybatis的时候都有分析到上面这些类。具体的每个类的注册步骤就不去看了,因为之间是看过的,这里重点给搭建看一下 SpringManagedTransactionFactory 事务工厂。源码如下

public class SpringManagedTransactionFactory implements TransactionFactory {
   
   

  /**
   * {@inheritDoc}
   */
  @Override
  public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
   
   
    return new SpringManagedTransaction(dataSource);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Transaction newTransaction(Connection conn) {
   
   
    throw new UnsupportedOperationException("New Spring transactions require a DataSource");
  }

SpringManagedTransactionFactory 实现了TransactionFactory 接口,复写了newTransaction方法,该方法是用来创建Transaction 事务对象的,它使用的是SpringManagedTransaction作为事务对象,下面是SpringManagedTransaction的源码

public class SpringManagedTransaction implements Transaction {
   
   

  private static final Logger LOGGER = LoggerFactory.getLogger(SpringManagedTransaction.class);
  //数据源
  private final DataSource dataSource;
  //JDBC的链接对象
  private Connection connection;

  private boolean isConnectionTransactional;
  //自动提交事务
  private boolean autoCommit;

  public SpringManagedTransaction(DataSource dataSource) {
   
   
    notNull(dataSource, "No DataSource specified");
    this.dataSource = dataSource;
  }

  /**
   * {@inheritDoc}
   */
  //获取连接
  @Override
  public Connection getConnection() throws SQLException {
   
   
    if (this.connection == null) {
   
   
      openConnection();
    }
    return this.connection;
  }

  //打开connection
  private void openConnection() throws SQLException {
   
   
    this.connection = DataSourceUtils.getConnection(this.dataSource);
    this.autoCommit = this.connection.getAutoCommit();
    this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);

    LOGGER.debug(() ->
        "JDBC Connection ["
            + this.connection
            + "] will"
            + (this.isConnectionTransactional ? " " : " not ")
            + "be managed by Spring");
  }

  /**
   * {@inheritDoc}
   */
  @Override
  //提交事务,使用的是connection.commit
  public void commit() throws SQLException {
   
   
    if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
   
   
      LOGGER.debug(() -> "Committing JDBC Connection [" + this.connection + "]");
      this.connection.commit();
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  //回滚事务
  public void rollback() throws SQLException {
   
   
    if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
   
   
      LOGGER.debug(() -> "Rolling back JDBC Connection [" + this.connection + "]");
      this.connection.rollback();
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  //关闭链接
  public void close() {
   
   
    DataSourceUtils.releaseConnection(this.connection, this.dataSource);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Integer getTimeout() {
   
   
    ConnectionHolder holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
    if (holder != null && holder.hasTimeout()) {
   
   
      return holder.getTimeToLiveInSeconds();
    } 
    return null;
  }

}

SpringManagedTransaction对事务的操作还是使用的是JDBC的connection 事务方法 来完成的,Spring只是做了一个封装而已。

MapperScannerConfigurer Mapper扫描

在Spring配置文件中我们除了要配置SqlSesionFactoryBean而外还配置了一个 MapperScannerConfigurer

<!--4 自动扫描mapper映射 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
   <!-- 指定要自动扫描接口的基础包,实现接口 -->
   <property name="basePackage" value="cn.whale.mapper"></property>
</bean>

MapperScannerConfigurer的作用是实现Mapper映射器的自动扫描,先看一下这个类的继承体系
在这里插入图片描述
首先 MapperScannerConfigurer 实现了InitializingBean复写了afterPropertiesSet方法,在该方法中只是对basePackage属性做了断言,如果basePackage为空则会抛出异常

  public void afterPropertiesSet() throws Exception {
   
   
    notNull(this.basePackage, "Property 'basePackage' is required");
  }

其次MapperScannerConfigurer 实现BeanDefinitionRegistryPostProcessor ,而BeanDefinitionRegistryPostProcessor 又继承了 BeanFactoryPostProcessor 接口,该接口是一个BeanFactory的后置处理器,它提供了一个 postProcessBeanFactory方法。该方法会在BeanFactory初始化之后被调用,这时所有的bean定义已经保存加载到beanFactory,但是bean的实例还未创建,我们可以通过该方法来定制和修改BeanFactory的内容,如覆盖或添加属性。

BeanDefinitionRegistryPostProcessor是对BeanFactoryPostProcessor的扩展,它提供了一个 postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) 方法,该方法是在Spring注册完BeanDefinition,实例化Bean之前被调用,它允许我们使用BeanDefinitionRegistry对我们自定义Bean进行注册。

这里我要稍微解释一下什么是BeanDefinition ,在Spring启动的时候,我们在Spring的配置文件中配置的<bean /> 都会被Spring封装成BeanDefinition,注册到IOC容器中。然后Spring会对单利的Bean进行实例化,也就是找到单利的非惰性加载的Bean,根据它的BeanDefinition来创建真正的实例

下面是MapperScannerConfigurer #postProcessBeanDefinitionRegistry方法的源码

@Override
//通过  BeanDefinitionRegistry  可以往Spring容器中注册Bean
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
   
   
    if (this.processPropertyPlaceHolders) {
   
   
      processPropertyPlaceHolders();
    }
    //Mapper扫描器
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.setAddToConfig(this.addToConfig);
    scanner.setAnnotationClass(this.annotationClass);
    scanner.setMarkerInterface(this.markerInterface);
    scanner.setSqlSessionFactory(this.sqlSessionFactory);
    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    scanner.setResourceLoader(this.applicationContext);
    scanner.setBeanNameGenerator(this.nameGenerator);
    scanner.registerFilters();
    //扫描Mapper
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

该方法中创建了一个 ClassPathMapperScanner ,扫描Mapper接口是通过ClassPathMapperScanner #Scan方法完成的

  public int scan(String... basePackages) {
   
   
      int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
      //扫描指定的包路径下的mapper
      this.doScan(basePackages);
      if (this.includeAnnotationConfig) {
   
   
          AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
      }

      return this.registry.getBeanDefinitionCount() - beanCountAtScanStart;
  }

  @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
   
   
   //[重要] 1.调用父类扫描指定包下的Mapper,然后封装成BeanDefinition通过 BeanDefinitionRegistry注册到Spring容器中
    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 {
   
   
      //【重要】2.处理Mapper的BeanDefinition
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }

ClassPathMapperScanner #scan方法首先是调用父类去扫描 basePackages 下的Mapper接口,然后会封装成 BeanDefinition ,通过BeanDefinitionRegistry注册到Spring容器中 。

紧接着就是调用org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions对Mapper的BeanDefinition做处理

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
   
   
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
   
   
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      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
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
      //【重要】bean 的实际类是 MapperFactoryBean
      definition.setBeanClass(this.mapperFactoryBean.getClass());
      ...省略...

注意注意,这行代码:definition.setBeanClass(this.mapperFactoryBean.getClass()); ,它把我们的Mapper接口的class修改为MapperFactoryBean 。也就是说Mapper接口实例创建是通过 FactoryBean 的方式来完成的。为什么这么做,我们待会儿来解释。

这里稍作总结:首先 MapperScannerConfigurer 实现了 BeanDefinitionRegistryPostProcessor ,在Spring注册完Bean之后,就会触发MapperScannerConfigurer #postProcessBeanDefinitionRegistry 方法的执行,在该方法中通过 ClassPathMapperScanner #scan 来扫描 指定的 basePackages包下的Mapper接口,然后封装成 BeanDefinition 注册到IOC的容器中。

接着会把Mapper的BeanDefinition的class指定为MapperFactoryBean 。也就是说Mapper是通过MapperFactoryBean来创建的。

上面一系列流程只是扫描和注册了Mapper接口,封装成MapperFactoryBean,紧接着Spring就会通过MapperFactoryBean 来创建Mapper的实例。下面是MapperFactoryBean 的源码

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
   
   

  private Class<T> mapperInterface;

  private boolean addToConfig = true;

  public MapperFactoryBean() {
   
   
    //intentionally empty 
  }

  public MapperFactoryBean(Class<T> mapperInterface) {
   
   
    this.mapperInterface = mapperInterface;
  }


  @Override
  public T getObject() throws Exception {
   
   
    //还是通过sqlSessionl.getMapper("mapper接口的class")
    return getSqlSession().getMapper(this.mapperInterface);
  }

  public SqlSession getSqlSession() {
   
   
   //注意,这里不是使用的是DefaultSqlSession,而是使用sqlSessionTemplate
    return this.sqlSessionTemplate;
  }
  ...省略...

这个类里面透露出2个重要的信息

  1. MapperFactoryBean实现了FactoryBean,提供了getObject方法来返回Mapper的实例,使用的还是getSqlSession().getMapper(this.mapperInterface)来创建Mapper的代理。但是它用的是sqlSessionTemplate的getMapper方法。
  2. MapperFactoryBean继承了SqlSessionDaoSupport

SqlSessionDaoSupport 是什么?下面是 org.mybatis.spring.support.SqlSessionDaoSupport的源码

public abstract class SqlSessionDaoSupport extends DaoSupport {
   
   

  //Spring提供的SqlSesion
  private SqlSessionTemplate sqlSessionTemplate;

  /**
   * Set MyBatis SqlSessionFactory to be used by this DAO.
   * Will automatically create SqlSessionTemplate for the given SqlSessionFactory.
   *
   * @param sqlSessionFactory a factory of SqlSession
   */
  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
   
   
    if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
   
   
    //创建SqlSessionTemplate
      this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
    }
  }

  protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
   
   
      //创建SqlSessionTemplate
    return new SqlSessionTemplate(sqlSessionFactory);
  }

SqlSessionDaoSupport是mybatis-spring提供的一个Dao支持,其中包含了一个很关键的东西 SqlSessionTemplate,注意:在整合了Spring之后不在使用SqlSesion了,而是使用SqlSessionTemplate,这是为什么,我们后面会说到。我们重点看一下 setSqlSessionFactory 方法,在该方法中创建了SqlSessionTemplate,下面是SqlSessionTemplate的构造器 org.mybatis.spring.SqlSessionTemplate#SqlSessionTemplate , 最终会通过该构造器来创建SqlSessionTemplate

/**
线程安全的交给Spring管理的, SqlSession与 Spring 事务管理一起工作,以确保实际使用的 SqlSession 是与当前 Spring 事务相关联的。 
此外,它还管理会话生命周期,包括根据 Spring 事务配置根据需要关闭、提交或回滚会话。
 * Thread safe, Spring managed, {@code SqlSession} that works with Spring
 * transaction management to ensure that that the actual SqlSession used is the
 * one associated with the current Spring transaction. In addition, it manages
 * the session life-cycle, including closing, committing or rolling back the
 * session as necessary based on the Spring transaction configuration.
 * <p>
 * The template needs a SqlSessionFactory to create SqlSessions, passed as a
 * constructor argument. It also can be constructed indicating the executor type
 * to be used, if not, the default executor type, defined in the session factory
 * will be used.
 * <p>
 * This template converts MyBatis PersistenceExceptions into unchecked
 * DataAccessExceptions, using, by default, a {@code MyBatisExceptionTranslator}.
 * <p>
    因为 SqlSessionTemplate 是线程安全的,所以单个实例可以被所有 DAO 共享; 
    这样做还应该节省少量内存。 这种模式可以在 Spring 配置文件中使用,如下所示:
   <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
   <constructor-arg ref="sqlSessionFactory" />
 </bean>
 * Because SqlSessionTemplate is thread safe, a single instance can be shared
 * by all DAOs; there should also be a small memory savings by doing this. This
 * pattern can be used in Spring configuration files as follows:
 *
 * <pre class="code">
 * {@code
 * <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
 *   <constructor-arg ref="sqlSessionFactory" />
 * </bean>
 * }
 * </pre>
 *
 * @author Putthiphong Boonphong
 * @author Hunter Presnall
 * @author Eduardo Macarron
 *
 * @see SqlSessionFactory
 * @see MyBatisExceptionTranslator
 */
public class SqlSessionTemplate implements SqlSession, DisposableBean {
   
   
  //SqlSession工厂
  private final SqlSessionFactory sqlSessionFactory;
  //执行器类型
  private final ExecutorType executorType;
  //SqlSession代理对象
  private final SqlSession sqlSessionProxy;

  private final PersistenceExceptionTranslator exceptionTranslator;

  public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {
   
   

    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    //使用Proxy.newProxyInstance 为SqlSession创建代理
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] {
   
    SqlSession.class },
        new SqlSessionInterceptor());
  }

SqlSessionTemplate的构造器中使用JDK动态代理为SqlSession创建了一个代理类SqlSessionInterceptor作为SqlSession的拦截器它实现了InvocationHandler接口,复写了invoke方法,当SqlSessionTemplate的方法被调用就会触发SqlSessionInterceptor#invoke的执行(JDK动态代理)。

这里我特意把 SqlSessionTemplate 的注释截出来,上面有这样一句话:“SqlSessionTemplate” 是Spring管理的线程安全的SqlSession,它与 Spring 事务管理一起工作,以确保实际使用的 SqlSession 是与当前 Spring 事务相关联的。 此外,它还管理会话生命周期,包括根据 Spring 事务配置根据需要关闭、提交或回滚会话。

也就是说在Spirng整合了Mybatis之后,不是直接使用SqlSession,而是使用SqlSessionTemplate 来管理SqlSession的生命周期,它是线程安全的,并且和事务一起工作的。那是不是说我们之前在Mybatis中使用的SqlSession的默认实现DefaultSqlSession 是线程不安全的呢?请看:

/**
 * DefaultSqlSession  实现于 SqlSession 这个类是线程不安全的
 * The default implementation for {@link SqlSession}.
 * Note that this class is not Thread-Safe.
 *
 * @author Clinton Begin
 */
public class DefaultSqlSession implements SqlSession {
   
   

果然在DefaultSqlSession 中有解释,DefaultSqlSession 它并不是一个线程安全的类。那么我们就要思考了,如果只是在Mybatis的开发中,我们每次请求都需要创建一个DefaultSqlSession ,这没什么问题,但是在Spring中只有一个SqlSessionTemplate,也就是说它是单利的,那么它怎么保证线程安全?别急慢慢往下看。

总结一下:在整合Mybatis的时候,我们需要定义一个,MapperScannerConfigurer,指定一个basePackage扫描包实现Mapper的自动扫描, MapperScannerConfigurer实现BeanDefinitionRegistryPostProcessor一个BeanFactory后置处理器 ,在Spring启动的过程中会触发postProcessBeanDefinitionRegistry方法的执行。该方法会扫描basePackages包下的Mpaper,然后把Mapper封装成一个一个的MapperFactoryBean。也就是说Mapper是通过MapperFactoryBen来创建的。

MapperFactoryBean继承了SqlSessionDaoSupport 其中提供了一个SqlSessionTemplate来管理SqlSesion,而在SqlSessionTemplate中是通过动态代理来创建的SqlSesion。也就是说在SqlSessionTemplate中管理了SqlSesion的代理类。

因为我们SqlSesson的默认实现DefaultSqlSession是线程不安全的,所以Spring使用了SqlSessionTemplate一个线程安全的类来管理DefaultSqlSession,那么它是怎么做的呢。

通过SqlSessionTemplate管理SqlSession

下面是SqlSessionInterceptor的源代码,因为用到了JDK动态代理,当SqlSessionTemplate的方法被调用(比如:studentMapper.selectById就会触发SqlSessionTemplate)就会触发SqlSessionInterceptor#invoke的执行它是SqlSessionTemplate中的一个内部类,见:org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor#invoke 源码如下


private class SqlSessionInterceptor implements InvocationHandler {
   
   
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   
   

      //1.创建一个 SqlSession,默认实现是DefaultSqlSession,底层一样是通过 SqlSessionFactory#openSession
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
   
   
        //2.执行方法,比如:DefaultSqlSession#selectOne
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
   
   
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          //3.提交
          sqlSession.commit(true);
        }
        //返回结果
        return result;
      } catch (Throwable t) {
   
   
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
   
   
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          //4.关闭sqlSession
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
   
   
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
   
   
        if (sqlSession != null) {
   
   
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

这里做了四个事情

  1. 调用getSqlSession方法得到SqlSession,底层一样会通过SqlSessionFactory#openSession,默认还是得到的DefaultSqlSession
  2. method.invoke 执行DefaultSqlSession的方法
  3. sqlSession.commit 提交事务
  4. closeSqlSession关闭资源

这里重点看getSqlSession方法是如何创建SqlSession的,源码如下 见:org.mybatis.spring.SqlSessionUtils#getSqlSession

  public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
   
   
    //空值判断
    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
    //从 TransactionSynchronizationManager 事务同步管理器 中拿到SqlSession的助手类
    //其实这里是从缓存中去那SqlSessionHolder ,该holder中有SqlSession
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
    //拿到SqlSession
    SqlSession session = sessionHolder(executorType, holder);
    //如果缓存中有SqlSession,就直接返回了
    if (session != null) {
   
   
      return session;
    }

    LOGGER.debug(() -> "Creating a new SqlSession");
    //如果缓存中没有 sqlSession 为空,就通过sessionFactory创建一个
    session = sessionFactory.openSession(executorType);
    //这里会把SqlSession交给SqlSessionHolder ,然后缓存到 TransactionSynchronizationManager中的一个 ThreadLocal<Map<Object, Object>> resources中
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
  }

上面是从TransactionSynchronizationManager中去拿SqlSession,我们可以认为是从缓存中获取,如果没有的话就会使用 sessionFactory.openSession(executorType) 创建一个新的,然后把SqlSession缓存到 TransactionSynchronizationManager。我们看一下 TransactionSynchronizationManager.getResource的源码

public abstract class TransactionSynchronizationManager {
   
   
    private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);
    //重点在这,通过一个ThreadLocal来缓存
    private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources");

  public static Object getResource(Object key) {
   
   
        //拿到key
        Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
        //拿到selSessionHolder
        Object value = doGetResource(actualKey);
        if (value != null && logger.isTraceEnabled()) {
   
   
            logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
        }

        return value;
    }

  private static Object doGetResource(Object actualKey) {
   
   
       //从Resources(一个ThreadLocal)中获取Map
        Map<Object, Object> map = (Map)resources.get();
        if (map == null) {
   
   
            return null;
        } else {
   
   
            //拿到SelSessionHolder ,selSessionHolder里面是真正的SqlSession
            Object value = map.get(actualKey);
            if (value instanceof ResourceHolder && ((ResourceHolder)value).isVoid()) {
   
   
                map.remove(actualKey);
                if (map.isEmpty()) {
   
   
                    resources.remove();
                }

                value = null;
            }

            return value;
        }
    }
 //缓存SqlSession
 private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
   
   
     ...省略了部分代码...
     //把SqlSession封装到SqlSessionHolder
     holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
     //重点在这,缓存SqlSessionHalder
     TransactionSynchronizationManager.bindResource(sessionFactory, holder);
     ...省略了部分代码...
  }

  public static void bindResource(Object key, Object value) throws IllegalStateException {
   
   
        Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
        Assert.notNull(value, "Value must not be null");
        //拿到ThreadLocal中的map
        Map<Object, Object> map = (Map)resources.get();
        if (map == null) {
   
   
            map = new HashMap();
            resources.set(map);
        }
        //最终把SqlSession 是缓存到了ThreadLocal中
        Object oldValue = ((Map)map).put(actualKey, value);

真相大白了,在 TransactionSynchronizationManager 中有个 ThreadLocal<Map<Object, Object>> resources我们的SqlSession就是被封装成SelSessionHolder 然后缓存到该ThreadLocal中,所以回答上面那个问题,SqlSessionTemplate是如何保证线程安全?答案是:通过一个ThreadLocal来缓存SqlSesion来保证线程安全,如果ThreadLocal没有SqlSession就会通过SqlSessionFactory#openSession创建一个,再缓存到ThreadLocal中。 这一切的动作都是交给SqlSessionTemplate来管理的。所以它是线程安全的

加餐MapperScan

我们在注册mapper的时候除了在xml中可以使用 MapperScannerConfigurer 方式,也可以使用注解 @MapperScan(basePackages = "cn.whale.mapper")去指定。那这两种方式有什么区别呢?我们来看一下MapperScan的源码

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
   
   

  /**
   * Alias for the {@link #basePackages()} attribute. Allows for more concise
   * annotation declarations e.g.:
   * {@code @EnableMyBatisMapperScanner("org.my.pkg")} instead of {@code
   * @EnableMyBatisMapperScanner(basePackages= "org.my.pkg"})}.
   */
  String[] value() default {
   
   };

  /**
   * Base packages to scan for MyBatis interfaces. Note that only interfaces
   * with at least one method will be registered; concrete classes will be
   * ignored.
   */
  String[] basePackages() default {
   
   };

在MapperScan上有个 @Import(MapperScannerRegistrar.class),看名字大概意思是 MappersScanner的注册器,源码如下

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
   
   
  //资源加载器
  private ResourceLoader resourceLoader;

  /**
   * {@inheritDoc}
   */
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
   
   
    //拿到MapperScan注解的属性
    AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
     //mapper扫描器,专门用来扫描Mapper接口
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

    // this check is needed in Spring 3.1
    if (resourceLoader != null) {
   
   
      scanner.setResourceLoader(resourceLoader);
    }
   ...省略部分代码...
    List<String> basePackages = new ArrayList<String>();
    //拿到MapperScan注解的value属性,即:basePackage ,如:cn.whale.mapper
    for (String pkg : annoAttrs.getStringArray("value")) {
   
   
      if (StringUtils.hasText(pkg)) {
   
   
        basePackages.add(pkg);
      }
    }
    for (String pkg : annoAttrs.getStringArray("basePackages")) {
   
   
      if (StringUtils.hasText(pkg)) {
   
   
        basePackages.add(pkg);
      }
    }
    for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
   
   
      basePackages.add(ClassUtils.getPackageName(clazz));
    }
    scanner.registerFilters();
    //调用ClassPathMapperScanner扫描
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }

org.mybatis.spring.mapper.ClassPathMapperScanner#doScan

  @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
   
   
    //扫描basePackages包下的mapper,封装成BeanDefinition
    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 {
   
   
      //把Mapper编程MapperFactoryBean , 关联sqlSessionFactory,关联sqlSessionFactory,sqlSessionTemplate等属性。
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }

好吧看到这我们就不用往下看了,因为和MapperScannerConfigurer做的事情是一样的呢,所以MapperScan注解方式使用的是MapperScannerRegistrar 来注册Mapper,它也实现了ImportBeanDefinitionRegistrar,它和MapperScannerConfigurer一样都是用到了ClassPathMapperScanner 扫描Mapper。

总结

SqlSessionFactory的创建

在Sping中通过SqlSessionFactoryBean来创建SqlSessionFactory,它实现了InitializingBean,在Bean初始化的时候会触发afterPropertiesSet 方法去构建sqlSessionFactory

在SqlSessionFactoryBean中会涉及到configLocation(Mybatis核心配置文件,Spring中往往不用配置);mapperLocations(mapper.xml);typeAliasesPackage(别名),Transaction,DataSource等的解析和注册。底层也是走mybatis的解析流程,最终也会形成一个全局的configuration。 解析完之后就会创建一个DefaultSqlSessionFactory。

Mapper代理的创建

在整合Mybatis的时候,我们需要定义一个,MapperScannerConfigurer并指定一个basePackage属性来实现Mapper的自动扫描, MapperScannerConfigurer实现BeanDefinitionRegistryPostProcessor一个BeanFactory后置处理器 ,在Spring启动的过程中会触发postProcessBeanDefinitionRegistry方法的执行。该方法会扫描basePackages包下的Mpaper,然后把Mapper封装成一个一个的MapperFactoryBean。也就是说Mapper是通过MapperFactoryBen来创建的。

在MapperFactoryBean是一个FactoryBean,在它的getObject方法中,使用了sqlSessionTemplate.getMapper方法来创建Mapper的代理。底层和之前分析mybatis的时候是一样,是通过MapperProxyFactory#newInstance方法通过Proxy.newProxyInstance来为mapper生成的代理类。

SqlSession的管理

MapperFactoryBean继承了SqlSessionDaoSupport 其中提供了一个SqlSessionTemplate,SqlSessionTemplate是线程安全的,在SqlSessionTemplate中通过动态代理来创建的SqlSesion。不直接使用DeafultSqlSession是因为它并不是线程安全的。

mapper的执行

在我们执行mapper对数据库操作的时候,请求会调用MapperProxy去执行(Mapper被动态代理),MapperProxy中会调用SqlSesion去执行。在Spring中是通过SqlSessionTemplate通过动态代理来创建的SqlSession,所以请求会来到SqlSessionTemplate的一个内部类SqlSessionInterceptor(SqlSession被动态代理)在其invoke方法中,会先去TransactionSynchronizationManager中的threadLocal获取真正的SqlSession,如果没有就会使用SqlSessionFactory创建一个DefaultSqlSession,然后缓存到TransactionSynchronizationManager中的threadLocal中。以此来保证线程安全。同时SqlSessionTemplate还关联了事务的操作。


在这里插入图片描述

相关文章
|
10天前
|
Java 应用服务中间件 Nacos
Spring Cloud 常用各个组件详解及实现原理(附加源码+实现逻辑图)
Spring Cloud 常用各个组件详解及实现原理(附加源码+实现逻辑图)
22 0
|
13天前
|
监控 数据可视化 安全
一套成熟的Spring Cloud智慧工地平台源码,自主版权,开箱即用
这是一套基于Spring Cloud的智慧工地管理平台源码,具备自主版权,易于使用。平台运用现代技术如物联网、大数据等改进工地管理,服务包括建设各方,提供人员、车辆、视频监控等七大维度的管理。特色在于可视化管理、智能报警、移动办公和分布计算存储。功能涵盖劳务实名制管理、智能考勤、视频监控AI识别、危大工程监控、环境监测、材料管理和进度管理等,实现工地安全、高效的智慧化管理。
|
1天前
|
SQL Java 数据库连接
15:MyBatis对象关系与映射结构-Java Spring
15:MyBatis对象关系与映射结构-Java Spring
15 4
|
4天前
|
设计模式 安全 Java
【初学者慎入】Spring源码中的16种设计模式实现
以上是威哥给大家整理了16种常见的设计模式在 Spring 源码中的运用,学习 Spring 源码成为了 Java 程序员的标配,你还知道Spring 中哪些源码中运用了设计模式,欢迎留言与威哥交流。
|
4天前
|
SQL Java 数据库连接
一文细说Mybatis八大核心源码
以上 是V哥给大家整理的8大核心组件的全部内容,为什么说选择 Java 就是选择未来,真正爱 Java 的人,一定喜欢深入研究,学习源码只是第一步,要有一杆子捅到操作系统才够刺激。
|
4天前
|
XML Java 数据库连接
Spring Boot与MyBatis:整合与实战
【4月更文挑战第29天】在现代的Java Web应用开发中,持久化层框架扮演了至关重要的角色。MyBatis作为一款优秀的持久化框架,被广泛应用于Java开发中。Spring Boot提供了简化开发流程的功能,而与MyBatis的整合也变得更加便捷。
14 0
|
7天前
|
Java Shell 测试技术
Spring源码搭建教程
Spring源码搭建教程
13 0
|
7天前
|
SQL XML Java
Mybatis源码解析
Mybatis源码解析
18 0
|
8天前
|
SQL 缓存 Java
|
8天前
|
XML 人工智能 Java
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)