2024-01-20  阅读(91)
原文作者:墨家巨子@俏如来 原文地址: https://blog.csdn.net/u014494148/article/details/122342051

2021一路有你,2022我们继续加油!你的肯定是我最大的动力

博主在参加博客之星评比,点击链接 , https://bbs.csdn.net/topics/603957267 疯狂打Call!五星好评 感谢


前言

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

202401201945519851.png

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的继承体系图

202401201945530952.png
首先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映射器的自动扫描,先看一下这个类的继承体系

202401201945535823.png
首先 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还关联了事务的操作。

202401201945540664.png

博主在参加博客之星评比,点击链接 , https://bbs.csdn.net/topics/603957267 疯狂打Call!五星好评 感谢

阅读全文
  • 点赞