Spring整合MyBatis詳細(xì)分析

mybatis-spring官網(wǎng)
這里我們以傳統(tǒng)的spring為例,因?yàn)榕渲酶鼮橹庇^,在spring中使用注解的效果是一樣的。

我們在其它幾篇文章中已經(jīng)介紹了MyBatis的工作流程、核心模塊和底層原理。了解了MyBatis的原生API里面有三個核心對象:
SqlSessionFactory、SqlSession和MapperProxy
大部分時(shí)候我們不會在項(xiàng)目中單獨(dú)使用MyBatis的工程,而是集成到Spring中使用,但是卻沒有看到這三個對象在代碼里面出現(xiàn)。我們都是直接注入一個Mapper接口,然后調(diào)用Mapper接口的方法。所以有下面幾個問題,我們要弄清楚:

  1. SqlSessionFactory是什么時(shí)候創(chuàng)建的
  2. SqlSession去哪里了?為什么不用它來獲取Mapper?
  3. 為什么@Autowired注入一個接口,在使用的時(shí)候卻變成了一個代理對象?在IOC的容器里面我們注入的是什么?注入的時(shí)候發(fā)生了什么事情?

下面,先看一下把MyBatis集成到Spring中要做的幾件事:

  1. 除了MyBatis的依賴之外,我們還需要在pom中添加MyBatis和Spring整合的依賴
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.0</version>
</dependency>
  1. 在Spring的applicationContext.xml中配置SqlSessionFactoryBean,它是用來幫助我們創(chuàng)建會話的,其中還要指定全局配置文件和mapper映射器文件的路徑
<!-- 在Spring啟動時(shí)創(chuàng)建 sqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="configLocation" value="classpath:mybatis-config.xml"></property>
    <property name="mapperLocations" value="classpath:mapper/*.xml"></property>
    <property name="dataSource" ref="dataSource"/>
</bean>
  1. 然后在applicationContext.xml中配置需要掃描Mapper接口的路徑:
    在MyBatis中有幾種方式,第一種是配置一個MapperScannerConfigurer,第二種是使用scan標(biāo)簽
<!--配置掃描器,將mybatis的接口實(shí)現(xiàn)加入到  IOC容器中  -->
<!--
    <mybatis-spring:scan #base-package="com.yrk.dao"/>
-->
<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.yrk.dao"/>
</bean>

還有一種是使用注解@MapperScan,比如我們在springboot的啟動類上加上一個注解

@SpringBootApplication
@MapperScan("com.yrk.mybatis")
public class MyBatisAnnotationApplication {
    /**
     * @param args
     */
    public static void main(String[] args) {
        SpringApplication.run(MyBatisAnnotationApplication.class, args);
    }
}

創(chuàng)建會話工廠
Spring對MyBatis的對象進(jìn)行管理,并不是替換MyBatis的核心對象,也就意味著MyBatis中SqlSessionFactory、SqlSession和MapperProxy這些類都會用到,而mybatis-spring.jar里面的類只是做了一些封裝或者橋梁的工作。所以,第一步我們先看一下在spring里面是怎么創(chuàng)建工廠類的:
我們在Spring的配置文件中配置了一個SqlSessionFactoryBean,我們來看一下這個類

在這里插入圖片描述

它實(shí)現(xiàn)了InitializingBean接口,所以要實(shí)現(xiàn)afterPropertiesSet()方法,這個方法會在Bean的屬性值設(shè)置完成時(shí)被調(diào)用。另外,它實(shí)現(xiàn)了FactoryBean接口,所以在初始化的時(shí)候,實(shí)際上是調(diào)用getObject()方法,它里面調(diào)用的也是afterPropertiesSet()方法

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

    return this.sqlSessionFactory;
  }

在afterPropertiesSet方法中,調(diào)用了buildSqlSessionFactory()方法。

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

在buildSqlSessionFactory()方法中,第一步我們定義了一個Configuration,叫做targetConfiguration;
426 行,判斷Configuration 對象是否已經(jīng)存在,也就是是否已經(jīng)解析過。如果已經(jīng)有對象,就覆蓋一下屬性。
433 行,如果Configuration 不存在,但是配置了configLocation 屬性,就根據(jù)mybatis-config.xml 的文件路徑,構(gòu)建一個xmlConfigBuilder 對象。
436 行,否則,Configuration 對象不存在,configLocation 路徑也沒有,只能使用默認(rèn)屬性去構(gòu)建去給configurationProperties 賦值。
后面就是基于當(dāng)前factory 對象里面已有的屬性,對targetConfiguration 對象里面屬性的賦值。
在第498 行,如果xmlConfigBuilder 不為空,也就是上面的第二種情況,調(diào)用了xmlConfigBuilder.parse()去解析配置文件,最終會返回解析好的Configuration 對象,這里的parse()方法是MyBatis里面的方法,和我們單獨(dú)使用MyBatis時(shí)候的解析全局配置文件的過程是一樣的。
在第507 行, 如果沒有明確指定事務(wù)工廠, 默認(rèn)使用SpringManagedTransactionFactory 。它創(chuàng)建SpringManagedTransaction 也有g(shù)etConnection()和close()方法。
在520 行,調(diào)用xmlMapperBuilder.parse(),這個步驟我們之前了解過了,它的作用是把接口和對應(yīng)的MapperProxyFactory 注冊到MapperRegistry 中。
最后調(diào)用sqlSessionFactoryBuilder.build() 返回了一個DefaultSqlSessionFactory。OK,在這里我們完成了編程式的案例里面的第一步,根據(jù)配置文件獲得一個工廠類,它是單例的,會在后面用來創(chuàng)建SqlSession。

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
    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)) {
      String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
      Stream.of(typeAliasPackageArray).forEach(packageToScan -> {
        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 + "'");
      });
    }
    if (!isEmpty(this.plugins)) {
      Stream.of(this.plugins).forEach(plugin -> {
        targetConfiguration.addInterceptor(plugin);
        LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
      });
    }
    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);
      }
    }
    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 NestedIOException("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 (!isEmpty(this.mapperLocations)) {
      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 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");
    }
    return this.sqlSessionFactoryBuilder.build(targetConfiguration);
  }

創(chuàng)建SqlSession
Q1: 可以直接使用DefaultSqlSession嗎?
通過上面一步,我們現(xiàn)在已經(jīng)有一個DefaultSqlSessionFactory了,按照編程式的開發(fā)過程,接下來我們要創(chuàng)建一個SqlSession的實(shí)現(xiàn)類,但是在Spring中,我們不是直接使用DefaultSqlSession的,而是對它進(jìn)行了一個封裝,這個SqlSession的實(shí)現(xiàn)類就是SqlSessionTemplate。這個跟Spring封裝其他的組件也是一樣的,比如JdbcTemplate,RedisTemplate等等,也是Spring跟MyBatis整合最關(guān)鍵的一個類。

為什么不用DefaultSqlSession?因?yàn)樗皇蔷€程安全的,而SqlSessionTemplate是線程安全的。

/**
 * The default implementation for {@link SqlSession}.
 * Note that this class is not Thread-Safe.
 *
 * @author Clinton Begin
 */
public class DefaultSqlSession implements SqlSession {
/**
 * 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>
 * 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 {

在編程式的開發(fā)中,SqlSession我們會在每次請求的時(shí)候創(chuàng)建一個,但是Spring里面只有一個SqlSessionTemplate(默認(rèn)是單例的),多個線程同時(shí)調(diào)用的時(shí)候如何保證線程安全?
SqlSessionTemplate里面有DefaultSqlSession中的所有方法,selectOne()、selectList()、insert()、update()、delete(),不過它都是通過一個代理對象實(shí)現(xiàn)的。這個代理對象在SqlSessionTemplate的構(gòu)造方法中通過一個代理類創(chuàng)建:

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;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
 }

SqlSessionTemplate中的所有方法都會先走到內(nèi)部代理類SqlSessionInterceptor的invoke方法:

private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
        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()
          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
          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);
        }
      }
    }
  }

首先會使用工廠類、執(zhí)行器類型、異常解析器創(chuàng)建一個sqlSession。

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
      return session;
    }
    LOGGER.debug(() -> "Creating a new SqlSession");
    session = sessionFactory.openSession(executorType);
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
    return session;
  }

在getSqlSession方法中,會先去從SqlSessionHolder中獲取SqlSession,如果獲取到就直接返回,如果沒有就新創(chuàng)建一個,并把這個新創(chuàng)建的SqlSession注冊到SqlSessionHolder中。SqlSessionHolder是通過TransactionSynchronizationManager.getResource()方法獲取到的,實(shí)際上是從ThreadLocal中獲取,所以SqlSessionTemplate是線程安全的。

private static final ThreadLocal<Map<Object, Object>> resources =
            new NamedThreadLocal<>("Transactional resources");
            
@Nullable
    public static Object getResource(Object key) {
        Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
        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;
    }

    /**
     * Actually check the value of the resource that is bound for the given key.
     */
    @Nullable
    private static Object doGetResource(Object actualKey) {
        Map<Object, Object> map = resources.get();
        if (map == null) {
            return null;
        }
        Object value = map.get(actualKey);
        // Transparently remove ResourceHolder that was marked as void...
        if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
            map.remove(actualKey);
            // Remove entire ThreadLocal if empty...
            if (map.isEmpty()) {
                resources.remove();
            }
            value = null;
        }
        return value;
    }

Q2: 怎么拿到一個SqlSessionTemplate?
我們知道在Spring里面會用SqlSessionTemplate替換DefaultSqlSession,那么接下來看一下怎么在DAO層拿到一個SqlSessionTemplate。
用過Hibernate的同學(xué)應(yīng)該記得,如果不用注入式的方式,我們在DAO層注入一個HibernateTemplate的一種方式是讓我們的DAO層的實(shí)現(xiàn)類去集成HibernateDaoSupport。MyBatis也是一樣,它提供一個SqlSessionDaoSupport, 里面持有SqlSessionTemplate對象,并且提供了一個getSqlSession()方法,讓我們獲得一個SqlSessionTemplate:

public abstract class SqlSessionDaoSupport extends DaoSupport {

  private SqlSessionTemplate sqlSessionTemplate;
  
  /**
   * Users should use this method to get a SqlSession to call its statement methods
   * This is SqlSession is managed by spring. Users should not commit/rollback/close it
   * because it will be automatically done.
   *
   * @return Spring managed thread safe SqlSession
   */
  public SqlSession getSqlSession() {
    return this.sqlSessionTemplate;
  }
  //省略部分代碼

也就是說我們讓DAO 層的實(shí)現(xiàn)類繼承SqlSessionDaoSupport ,就可以獲得SqlSessionTemplate,然后在里面封裝SqlSessionTemplate 的方法。

public  class BaseDao extends SqlSessionDaoSupport {
    //使用sqlSessionFactory
    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    @Autowired
    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        super.setSqlSessionFactory(sqlSessionFactory);
    }

    /**
     * @param statement
     * @return
     */
    public Object selectOne(String statement) {
        return getSqlSession().selectOne(statement);
    }
 }

Q3: 有沒有更好的方法可以拿到SqlSessionTemplate? Spring中我們的DAO層沒有集成SqlSessionDaoSupport,那Spring中是怎么拿到SqlSessionTemplate的?
在真實(shí)項(xiàng)目中,我們只是在DAO層使用@Autowired注入Mapper對象,然后直接調(diào)用Mapper對象的方法區(qū)操作數(shù)據(jù)庫,那Mapper對象的實(shí)例一定是在Spring啟動的時(shí)候被Spring掃描并且注冊了。那這個Mapper是什么時(shí)候掃描的?注冊的時(shí)候又是注冊成什么對象?

回顧一下,我們在applicationContext.xml中配置了一個MapperScannerConfigurer,MapperScannerConfigurer 實(shí)現(xiàn)了BeanDefinitionRegistryPostProcessor 接口,BeanDefinitionRegistryPostProcessor 是BeanFactoryPostProcessor 的子類,可以通過編碼的方式修改、新增或者刪除某些Bean 的定義。


在這里插入圖片描述

我們只需要重寫postProcessBeanDefinitionRegistry()方法,在這里面操作Bean就可以了。在這個方法中,scanner.scan() 方法是ClassPathBeanDefinitionScanner 中的, 而它的子類ClassPathMapperScanner 覆蓋了doScan() 方法, 在doScan() 中調(diào)用了processBeanDefinitions,在processBeanDefinitions方法中,在注冊beanDefinitions 的時(shí)候,BeanClass被改為MapperFactoryBean。

為什么要把beanClass改成MapperFactoryBean呢?因?yàn)镸apperFactoryBean繼承了SqlSessionDaoSupport,可以獲取到SqlSessionTemplate

@Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }

    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();
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }
@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;
  }

  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
      definition.setBeanClass(this.mapperFactoryBean.getClass());

      definition.getPropertyValues().add("addToConfig", this.addToConfig);

      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);
      }
    }
  }
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  private Class<T> mapperInterface;

  private boolean addToConfig = true;

  public MapperFactoryBean() {
    //intentionally empty 
  }
  //省略部分代碼
}

我們使用Mapper 的時(shí)候,只需要在加了Service 注解的類里面使用@Autowired注入Mapper 接口就好了。

@Service
public class EmployeeService {
    @Autowired
    EmployeeMapper employeeMapper;
    public List<Employee> getAll() {
        return employeeMapper.selectByMap(null);
    }
    public void saveEmpsInfo(Employee employee) {
        employeeMapper.insertSelective(employee);
    }
}

Spring在啟動的時(shí)候需要去實(shí)例化EmployeeService,EmployeeService又依賴了EmployeeMapper接口,Spring會根據(jù)Mapper的名字從BeanFactory中獲取它的BeanDefinition,再從BeanDefinition中獲取BeanClass,上面已經(jīng)提到,Mapper的BeanClass已經(jīng)被改成MapperFactoryBean,所以EmployeeMapper的beanClass是MapperFactoryBean。

接下來是創(chuàng)建MapperFactoryBean,因?yàn)镸apperFactoryBean實(shí)現(xiàn)了FactoryBean接口,創(chuàng)建的時(shí)候會調(diào)用getObject()方法:

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

因?yàn)镸apperFactoryBean繼承了SqlSessionDaoSupport,所以getSqlSession()返回的就是SqlSessionTemplate。

@Override
  public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
  }

SqlSessionTemplate的getMapper()方法又是調(diào)用Configuration的getMapper()方法,跟編程式使用里面的getMapper 一樣, 通過工廠類MapperProxyFactory 獲得一個MapperProxy 代理對象。

也就是說,我們注入到Service 層的接口,實(shí)際上還是一個MapperProxy 代理對象。所以最后調(diào)用Mapper 接口的方法,也是執(zhí)行MapperProxy 的invoke()方法,后面的流程就跟編程式的工程里面一模一樣了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容