学习笔记----Mybatis源码----mybatis集成spring原理

639 阅读13分钟

1、Spring与Myabatis集成的介绍

1.1 What is MyBatis-Spring?

MyBatis-Spring integrates MyBatis seamlessly with Spring. This library allows MyBatis to participate in Spring transactions, takes care of building MyBatis mappers and SqlSessions and inject them into other beans, translates MyBatis exceptions into Spring DataAccessExceptions, and finally, it lets you build your application code free of dependencies on MyBatis, Spring or MyBatis-Spring.

翻译:

MyBatis-Spring 将 MyBatis 与 Spring 无缝集成。这个库允许 MyBatis 参与 Spring 事务,负责构建 MyBatis 映射器和SqlSessions 并将它们注入到其他 bean 中,将 MyBatis 异常翻译成 Spring DataAccessExceptions,最后它可以让你构建你的应用程序代码,而不依赖于 MyBatis、Spring 或MyBatis-Spring。

上述介绍来自,Mybatis官网,有兴趣可以自己看下,mybatis.org/spring/

1.2如何使用?

参考官网:mybatis.org/spring/gett…

今天分析的spring和mybatis集成主要是按照XML的方式分析,因为说实话spring和mybatis集成比较简单,集成的例子我也是拿的网络大佬的列子,例子参考juejin.cn/book/694491…

后面我会补充JavaConfig的方式,再提一嘴springboot的方式。所以,先学spring源码是多么的重要。。。。。。。。。。

  • 导入集成spring的所需要的jar
  <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.5</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.6.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.2.6.RELEASE</version>
    </dependency>
  • 编写spring文件

这里贴上一张普通情况下spring和mybatis集成,所用到的配置文件,spring-mybatis.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context" xmlns: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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <context:component-scan base-package="com.shihy.mybatis.mapper"/>

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>
    <!--这个配置其实在今天的分析中木有用到,可以忽略。等分析spring管理事务的时候再说-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <tx:annotation-driven transaction-manager="transactionManager"/>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <property name="typeAliasesPackage" value="com.linkedbear.mybatis.entity"/>
    </bean>

    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.linkedbear.mybatis.mapper"/> 
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>
</beans>
  • mybatis的配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="logImpl" value="LOG4J"/>
        <setting name="cacheEnabled" value="true"/>
    </settings>

    <!--
    别名已在初始化SqlSessionFactory时指定
    <typeAliases>
        <package name="com.linkedbear.mybatis.entity"/>
    </typeAliases>

    事务管理器、数据源由SpringFramework统一管控
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    -->

    <mappers>
        <mapper resource="mapper/department.xml"/>
        <mapper resource="mapper/user.xml"/>
    </mappers>
</configuration>
  • 写个启动类
public class MyBatisSpringApplication {
    
    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring-mybatis.xml");
        
        DepartmentMapper departmentMapper = ctx.getBean(DepartmentMapper.class);
        List<Department> departmentList = departmentMapper.findAll();
        departmentList.forEach(System.out::println);
        
        ctx.close();
    }
}

1.3mybatis-spring包的源码地址

github.com/mybatis/spr…

下面直奔主题:

2、Spring和Mybatis集成的核心组件们

来瞅瞅这些小可爱们,这都TM的是啥。

SqlSessionFactoryBean SqlSessionTemplate MapperScannerConfigurer DataSourceTransactionManager DriverManagerDataSource

  • DriverManagerDataSource这个类没什么好说的,数据源,spring包下的,反正也是javax.sql.DataSource的一种实现

  • DataSourceTransactionManager,也没什么好说的,有个成员变量是上面的DriverManagerDataSource对象,这是我们使用Spring管理事务的时候,必须用到的。juejin.cn/post/684490…

  • SqlSessionFactoryBean,这个玩意,很重要很重要很重要。。。。。。。

    单独使用mybatis的时候,MyBatis初始化过程时,SqlSessionFactoryBuilder会通过XMLConfigBuilder等对象读取mybatis-config.xml配置文件以及映射配置信息,得到Configuration对象,然后创建SqlSessionFactory对象。而在与spring集成时,MyBatis中的SqlSessionFactory对象则是由SqlSessionFactoryBean创建的。在上面,applicationContext.xml文件中配置了SqlSessionFactoryBean,其中指定了数据源对象、mybatis-config.xml配置文件的位置等信息。SqlSessionFactoryBean中定义了很多与MyBatis配置相关的字段:

/**
 * {@code FactoryBean} that creates a MyBatis {@code SqlSessionFactory}. This is the usual way to set up a shared
 * MyBatis {@code SqlSessionFactory} in a Spring application context; the SqlSessionFactory can then be passed to
 * MyBatis-based DAOs via dependency injection.
 *
 * Either {@code DataSourceTransactionManager} or {@code JtaTransactionManager} can be used for transaction demarcation
 * in combination with a {@code SqlSessionFactory}. JTA should be used for transactions which span multiple databases or
 * when container managed transactions (CMT) are being used.
 *
 * @author Putthiphong Boonphong
 * @author Hunter Presnall
 * @author Eduardo Macarron
 * @author Eddú Meléndez
 * @author Kazuki Shimizu
 *
 * @see #setConfigLocation
 * @see #setDataSource
 */
public class SqlSessionFactoryBean
    implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
    //看几个关键的字段
  //代表mybatis-config.xml配置文件
  private Resource configLocation;
  //Mybatis 的全局配置对象!
  private Configuration configuration;

  private Resource[] mapperLocations;
  //数据源对象
  private DataSource dataSource;
  //事务工厂
  private TransactionFactory transactionFactory;

  private Properties configurationProperties;

  private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
  //sqlsession的构造工厂
  private SqlSessionFactory sqlSessionFactory;
  //............................其他的字段,看下源码就会发现,大都是和构造Mybatis的全局对象Configuration有关的。
  //什么别名啦、拦截器、类型转换器等等。。。。。。。。   
    
}

注意!从实现的角度来看,SqlSessionFactoryBean这个类实现了InitializingBean接口(所以学习spring源码是有多么的重要),那么相比它的afterPropertiesSet()方法必定有猫腻:

org.mybatis.spring.SqlSessionFactoryBean#afterPropertiesSet

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

org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory

/**
 * Build a {@code SqlSessionFactory} instance.
 *
 * The default implementation uses the standard MyBatis {@code XMLConfigBuilder} API to build a
 * {@code SqlSessionFactory} instance based on a Reader. Since 1.3.0, it can be specified a {@link Configuration}
 * instance directly(without config file).
 *
 * @return SqlSessionFactory
 * @throws Exception
 *           if configuration is failed
 */
 //注释已经写的很清楚了,创建一个SqlSessionFactory实例,我们来看看:
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

  final Configuration targetConfiguration;

  XMLConfigBuilder xmlConfigBuilder = null;
  //如果  Configuration对象存在,则使用指定的Configuration对象并对其进行配置。其实我们可以自己创建个Configuration类型的bean,然后注入    SqlSessionFactoryBean中
  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对象,读取指定的mybatis-config.xml配置文件。这行代码。。。不就是单独使用Mybatis的时候
    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");
    //直接创建  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);
  //从这行代码开始,就是我们在SqlSessionFactoryBean定义的时候,注入的属性,如:typeAliasesPackage
  //start  
  if (hasLength(this.typeAliasesPackage)) {
    scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
        .filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
        .filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
  }

  if (!isEmpty(this.typeAliases)) {
    Stream.of(this.typeAliases).forEach(typeAlias -> {
      targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
      LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
    });
  }

  if (!isEmpty(this.plugins)) {
    Stream.of(this.plugins).forEach(plugin -> {
      targetConfiguration.addInterceptor(plugin);
      LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
    });
  }

  if (hasLength(this.typeHandlersPackage)) {
    scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
        .filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
        .forEach(targetConfiguration.getTypeHandlerRegistry()::register);
  }

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

  targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler);

  if (!isEmpty(this.scriptingLanguageDrivers)) {
    Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
      targetConfiguration.getLanguageRegistry().register(languageDriver);
      LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
    });
  }
  Optional.ofNullable(this.defaultScriptingLanguageDriver)
      .ifPresent(targetConfiguration::setDefaultScriptingLanguage);

  if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls
    try {
      targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
    } catch (SQLException e) {
      throw new NestedIOException("Failed getting a databaseId", e);
    }
  }

  Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);
  //end
    
  if (xmlConfigBuilder != null) {
    try {
      //使用  xmlConfigBuilder对mybatis-config.xml配置文件进行解析。这就是Mybatis里面的内容了
      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();
    }
  }
  //设置Environment对象
    //如果我们没有配置transactionFactory对象,则Spring会帮我们默认使用SpringManagedTransactionFactory
  targetConfiguration.setEnvironment(new Environment(this.environment,
      this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
      this.dataSource));
  //根据mapperLocations配置,处理映射配置文件以及相应的Mapper接口
  if (this.mapperLocations != null) {
    if (this.mapperLocations.length == 0) {
      LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
    } else {
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }
        try {
          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
          xmlMapperBuilder.parse();
        } catch (Exception e) {
          throw new 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.");
  }
  //最终调用sqlSessionFactoryBuilder的builder方法,创建SqlSessionFactory对象并返回,其实就是。这像不像我们单独使用Mybatis的时候,用的那行代码,   SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(xml);
  return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}

org.apache.ibatis.session.SqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration)

public SqlSessionFactory build(Configuration config) {
    //其实绕了半天生成的还是DefaultSqlSessionFactory对象
    return new DefaultSqlSessionFactory(config);
}

so,综上所述,在Spring的生命周期中,SqlSessionFactoryBean这个bean创建完,那么我们Mybatis所需要的SqlSessionFactory以及Configuration对象已经构造和初始化完毕。

到此为止,整个 SqlSessionFactoryBean 的任务也就全部完成了,大概抓住几个要点即可:

  1. 几乎可以代替 MyBatis 全局配置文件
  2. 可以传入全局配置文件,供 MyBatis 解析和处理
  3. 代替 MyBatis 处理数据源和事务工厂
  4. 只处理和解析 mapper.xml
  • SqlSessionTemplate

    SqlSessionTemplate 是 MyBatis-Spring 的核心,它实现了SqlSession接口,在MyBatis与Spring集成开发时,用来代替MyBatis中的DefaultSqlSession的功能,所以可以通过SqlSessionTemplate对象完成指定的数据库操作。SqlSessionTemplate是线程安全的,可以在DAO (Data Access Object,数据访问对象)之间共享使用,其底层封装了Spring管理的SqlSession对象。 SqlSessionTemplate中核心字段的含义如下:

    /**
     * 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 {
      //这个sqlSessionFactory对象就是上面SqlSessionFactoryBean给我们创建的那个DefaultSqlSessionFactory对象
      private final SqlSessionFactory sqlSessionFactory;
      //Sqlsession底层使用的Executor类型
      private final ExecutorType executorType;
      //通过JDK动态代理生成的代理对象
      private final SqlSession sqlSessionProxy;
      //异常转换器,不重点描述
      private final PersistenceExceptionTranslator exceptionTranslator;
    
      /**
       * Constructs a Spring managed SqlSession with the {@code SqlSessionFactory} provided as an argument.
       *
       * @param sqlSessionFactory
       *          a factory of SqlSession
       */
      public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
      }
    
      /**
       * Constructs a Spring managed SqlSession with the {@code SqlSessionFactory} provided as an argument and the given
       * {@code ExecutorType} {@code ExecutorType} cannot be changed once the {@code SqlSessionTemplate} is constructed.
       *
       * @param sqlSessionFactory
       *          a factory of SqlSession
       * @param executorType
       *          an executor type on session
       */
      public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
        this(sqlSessionFactory, executorType,
            new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
      }
    
      /**
       * Constructs a Spring managed {@code SqlSession} with the given {@code SqlSessionFactory} and {@code ExecutorType}. A
       * custom {@code SQLExceptionTranslator} can be provided as an argument so any {@code PersistenceException} thrown by
       * MyBatis can be custom translated to a {@code RuntimeException} The {@code SQLExceptionTranslator} can also be null
       * and thus no exception translation will be done and MyBatis exceptions will be thrown
       *
       * @param sqlSessionFactory
       *          a factory of SqlSession
       * @param executorType
       *          an executor type on session
       * @param exceptionTranslator
       *          a translator of exception
       * 它有个构造函数,重点看这个构造函数。
       * 对于sqlSessionProxy,这里使用了JDK动态代理,等下我们关注一下SqlSessionInterceptor这个类,因为SqlSessionTemplate
       * 中实现的SqlSession的方法,如下面的selectOne,都是通过这个JDK动态代理对象进行具体的调用的。
       */
      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());
      }
    
      public SqlSessionFactory getSqlSessionFactory() {
        return this.sqlSessionFactory;
      }
    
      public ExecutorType getExecutorType() {
        return this.executorType;
      }
    
      public PersistenceExceptionTranslator getPersistenceExceptionTranslator() {
        return this.exceptionTranslator;
      }
        
        @Override
      public <T> T selectOne(String statement) {
        return this.sqlSessionProxy.selectOne(statement);
      }
        //............................
        
        //............................
      @Override
      public <T> T getMapper(Class<T> type) {
        //注意,  SqlSessionTemplate把自己作为参数穿进去了。紧接着就是调用org.apache.ibatis.session.Configuration#getMapper的方法了,那就到了Mybatis的内容了,这个this就体现出来了spring用SqlSessionTemplate替换DeafultSqlSession的设计了,虽然底层用的其实还是DeafultSqlSession
        return getConfiguration().getMapper(type, this);
      }
    }
    

org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor

哦,它是SqlSessionTemplate的内部类。。。。。。。还是个InvocationHandler,所以直接喽一眼它的invoke()方法。。。。。。

/**
 * Proxy needed to route MyBatis method calls to the proper SqlSession got from Spring's Transaction Manager It also
 * unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to pass a {@code PersistenceException} to the
 * {@code PersistenceExceptionTranslator}.
 */
private class SqlSessionInterceptor implements InvocationHandler {
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //这又要干嘛?怎么又来个SqlSession?  看下面的解析
    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);
      }
    }
  }
}

org.mybatis.spring.SqlSessionUtils#getSqlSession(org.apache.ibatis.session.SqlSessionFactory, org.apache.ibatis.session.ExecutorType, org.springframework.dao.support.PersistenceExceptionTranslator)

注意,它是SqlSessionUtils里面的方法

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
    PersistenceExceptionTranslator exceptionTranslator) {

  notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
  notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
  //从事务管理器中获取SqlSessionHolder,其中封装了SqlSession对象
  SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
  //获取SqlSessionHolder其中封装的SqlSession对象
  SqlSession session = sessionHolder(executorType, holder);
  if (session != null) {
    return session;
  }

  LOGGER.debug(() -> "Creating a new SqlSession");
  //看这里看这里,这是什么?不就是我们自己使用Mybatis的时候,调用的那个方法吗?org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession(org.apache.ibatis.session.ExecutorType)
    //其实底下还是生成的DefaultSqlSession对象
  session = sessionFactory.openSession(executorType);
  //将Sqlsession对应与Spring事务管理器绑定
  registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

  return session;
}
  • MapperScannerConfigurer

    看到了什么?这个家伙的本质是一个 BeanDefinitionRegistryPostProcessor ,那么我们必须看它的postProcessBeanDefinitionRegistry()方法喽(学习spring源码的重要性!!!):

public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
  //指定扫描的包,该字段可以包含多个通过逗号分隔的包名,这些指定的包以及其子包都会被扫描  
  private String basePackage;

  private boolean addToConfig = true;

  private String lazyInitialization;
  //我们上面创建的DefaultSqlSessionFactory对象
  private SqlSessionFactory sqlSessionFactory;
  ////我们上面创建的SqlSessionTemplate对象
  private SqlSessionTemplate sqlSessionTemplate;

  private String sqlSessionFactoryBeanName;

  private String sqlSessionTemplateBeanName;

  private Class<? extends Annotation> annotationClass;

  private Class<?> markerInterface;

  private Class<? extends MapperFactoryBean> mapperFactoryBeanClass;

  private ApplicationContext applicationContext;

  private String beanName;

  private boolean processPropertyPlaceHolders;

  private BeanNameGenerator nameGenerator;

  private String defaultScope;
  //...............
      /**
   * {@inheritDoc}
   *
   * @since 1.0.2
   */
  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }
    //创建ClassPathMapperScanner
    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.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
    if (StringUtils.hasText(lazyInitialization)) {
      scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
    }
    if (StringUtils.hasText(defaultScope)) {
      scanner.setDefaultScope(defaultScope);
    }
    scanner.registerFilters();
    //开始扫描basePackage字段中指定的包及其子包  
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }
    
  //...............................  
}

org.springframework.context.annotation.ClassPathBeanDefinitionScanner#scan

public int scan(String... basePackages) {
    int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
    //看这个
    this.doScan(basePackages);
    if (this.includeAnnotationConfig) {
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }

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

很不巧,这个 scan 方法其实是 ClassPathBeanDefinitionScanner 的,因为 ClassPathMapperScanner 继承自 ClassPathBeanDefinitionScanner ,所以会先来到 ClassPathBeanDefinitionScanner 中:

org.mybatis.spring.mapper.ClassPathMapperScanner#doScan

@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
  //会先来到ClassPathBeanDefinitionScanner#doScan,这个方法就是Spring 的源码了,不展开,大概逻辑是根据包路径,把里面的类和接口扫描成ScannedGenericBeanDefinition对象
  //内容大概是这些Bean definition with name 'departmentMapper': 
  //  Generic bean: class [com.linkedbear.mybatis.mapper.DepartmentMapper]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [E:\shihy\CodeOfWork\mybatis-analysis-learning-code\mybatis-08-spring\target\classes\com\linkedbear\mybatis\mapper\DepartmentMapper.class]  
  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;
}

org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for(Iterator var3 = beanDefinitions.iterator(); var3.hasNext(); definition.setLazyInit(this.lazyInitialization)) {
        BeanDefinitionHolder holder = (BeanDefinitionHolder)var3.next();
        definition = (GenericBeanDefinition)holder.getBeanDefinition();
         // 获取Mapper接口的全限定名
        String beanClassName = definition.getBeanClassName();
        LOGGER.debug(() -> {
            return "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName + "' mapperInterface";
        });
        //给即将要生产的bean对象,的构造函数,预先放入值
        definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
        //这里贼重要,虽然父类方法扫描出来了ScannedGenericBeanDefinition对象,而且beanClasss属性值是我们自己的接口,如上面的com.linkedbear.mybatis.mapper.DepartmentMapper,但是这一步统一全部被设置为为mapperFactoryBeanClass,即MapperFactoryBean.class
        //所以这里我们知道了,我们写的mapper接口们,就是起到了一个标识的作用,即实际只会用到它的类名和方法名而已,beanName虽然是我们自己的接口名,但具体的实现是MapperFactoryBean。且MapperFactoryBean的构造函数的值,已经被预先放入了,就是上面放的beanClassName
        //呵呵,高级,骚气
        definition.setBeanClass(this.mapperFactoryBeanClass);
        definition.getPropertyValues().add("addToConfig", this.addToConfig);
        // 给MapperFactoryBean传入SqlSessionFactory,实际也就是注入我们的DefaultSqlSessionFactory对象。。。
        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(() -> {
                    return "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(() -> {
                    return "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.";
                });
            }

            definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
            explicitFactoryUsed = true;
        }

        if (!explicitFactoryUsed) {
            LOGGER.debug(() -> {
                return "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.";
            });
            definition.setAutowireMode(2);
        }
    }

}

下面来看看MapperFactoryBean

  • MapperFactoryBean

    看看它的实现,哦豁,又是个FactoryBean,而且实现了SqlSessionDaoSupport。那么我们来看看它的getObject()方法(学习spring源码是多么的重要!!!!)

/**
 * BeanFactory that enables injection of MyBatis mapper interfaces. It can be set up with a SqlSessionFactory or a
 * pre-configured SqlSessionTemplate.
 * <p>
 * Sample configuration:
 *
 * <pre class="code">
 * {@code
 *   <bean id="baseMapper" class="org.mybatis.spring.mapper.MapperFactoryBean" abstract="true" lazy-init="true">
 *     <property name="sqlSessionFactory" ref="sqlSessionFactory" />
 *   </bean>
 *
 *   <bean id="oneMapper" parent="baseMapper">
 *     <property name="mapperInterface" value="my.package.MyMapperInterface" />
 *   </bean>
 *
 *   <bean id="anotherMapper" parent="baseMapper">
 *     <property name="mapperInterface" value="my.package.MyAnotherMapperInterface" />
 *   </bean>
 * }
 * </pre>
 * <p>
 * Note that this factory can only inject <em>interfaces</em>, not concrete classes.
 *
 * @author Eduardo Macarron
 *
 * @see SqlSessionTemplate
 */
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
  //这个mapperInterface是我们的mapper接口对应的Class对象
  private Class<T> mapperInterface;

  private boolean addToConfig = true;

  public MapperFactoryBean() {
    // intentionally empty
  }
  //看这个构造函数,就是上面预先放入的,根据我们放入的接口的名字,解析出这个mapperInterface对象
  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void checkDaoConfig() {
    super.checkDaoConfig();

    notNull(this.mapperInterface, "Property 'mapperInterface' is required");

    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }

  /**
   * {@inheritDoc}
   * 看这个方法,会调用父类的,org.mybatis.spring.support.SqlSessionDaoSupport#getSqlSession。
   * 其实就是调用sqlSessionTemplate的getMapper方法,。然后返回的是这个mapperInterface接口的JDK代理对象,这就是
   * Mybaits的内容了,org.apache.ibatis.binding.MapperProxy#MapperProxy, 它的成员变量sqlSession是SqlSessionTemplate而不是DefaultSessionTemplate了
   */
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Class<T> getObjectType() {
    return this.mapperInterface;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isSingleton() {
    return true;
  }

  // ------------- mutators --------------

  /**
   * Sets the mapper interface of the MyBatis mapper
   *
   * @param mapperInterface
   *          class of the interface
   */
  public void setMapperInterface(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  /**
   * Return the mapper interface of the MyBatis mapper
   *
   * @return class of the interface
   */
  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  /**
   * If addToConfig is false the mapper will not be added to MyBatis. This means it must have been included in
   * mybatis-config.xml.
   * <p>
   * If it is true, the mapper will be added to MyBatis in the case it is not already registered.
   * <p>
   * By default addToConfig is true.
   *
   * @param addToConfig
   *          a flag that whether add mapper to MyBatis or not
   */
  public void setAddToConfig(boolean addToConfig) {
    this.addToConfig = addToConfig;
  }

  /**
   * Return the flag for addition into MyBatis config.
   *
   * @return true if the mapper will be added to MyBatis in the case it is not already registered.
   */
  public boolean isAddToConfig() {
    return addToConfig;
  }
}

SqlSessionDaoSupport

public abstract class SqlSessionDaoSupport extends DaoSupport {
    //看这里,所以MapperFactoryBean对象中,会有一个SqlSessionTemplate对象
    private SqlSessionTemplate sqlSessionTemplate;

    public SqlSessionDaoSupport() {
    }

    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
            this.sqlSessionTemplate = this.createSqlSessionTemplate(sqlSessionFactory);
        }

    }

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

    public final SqlSessionFactory getSqlSessionFactory() {
        return this.sqlSessionTemplate != null ? this.sqlSessionTemplate.getSqlSessionFactory() : null;
    }

    public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
        this.sqlSessionTemplate = sqlSessionTemplate;
    }
    //直接返回我们定义的sqlSessionTemplate对象
    public SqlSession getSqlSession() {
        return this.sqlSessionTemplate;
    }

    public SqlSessionTemplate getSqlSessionTemplate() {
        return this.sqlSessionTemplate;
    }

    protected void checkDaoConfig() {
        Assert.notNull(this.sqlSessionTemplate, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
    }

关于spring集成Mybatis的关键类都解析完毕了。

浅出一下:

我们获得的所有Mapper接口的代理对象,里面 的sqlSession属性都是SqlSessionTemplate类型的,

所以类似于selectOne这种方法会经过SqlSessionTemplate的重载方法们。重载方法们又其实调用的是sqlSessionProxy这个JDK动态代理对象,是个SqlSessionInterceptor类型,最终会调用到它的invoker()方法,在这个放里面,会去获取sqlsession。

那么问题来了,spring为什么要搞个SqlSessionTemplate套一层呢?

理由:

为了让spring管理事务,

因为 SessionInterceptor的invoker()方法获取sqlsession的时候会先从事务管理器中获取,这个里面有数据库的连接对象。。。。。。。

org.mybatis.spring.SqlSessionUtils#getSqlSession(org.apache.ibatis.session.SqlSessionFactory, org.apache.ibatis.session.ExecutorType, org.springframework.dao.support.PersistenceExceptionTranslator)