mybatis

Mybatis 源码分析(与Spring 结合)—— sql 执

2019-03-21  本文已影响0人  habit_learning

SqlSessionFactory 生成

SqlSessionFactory 的生成是通过 SqlSessionFactoryBean 生成的。在讲源码之前,我们先看看 Mybatis 在 Springboot 中的配置:

    @Bean(name = "dataSource")
    @ConfigurationProperties(prefix = "spring.datasource.cashier")
    public DataSource dataSource() {
        return new DruidDataSource();
    }

    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSource);
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        configuration.setMapUnderscoreToCamelCase(Boolean.TRUE);
        configuration.setUseGeneratedKeys(Boolean.TRUE);
        sessionFactoryBean.setConfiguration(configuration);
        sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:yongda.mapper/*.xml"));
        return sessionFactoryBean.getObject();
    }

上面配置中,将数据源DataSource,配置信息Configuration等属性赋值给SqlSessionFactoryBean,然后调用sessionFactoryBean.getObject()方法,接下来我们看看这个方法做了什么。

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

    return this.sqlSessionFactory;
  }

直接返回SqlSessionFactory对象,其初始化操作是在afterPropertiesSet()方法中,里面会调用buildSqlSessionFactory()SqlSessionFactory赋值:

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
  ......
  return this.sqlSessionFactoryBuilder.build(configuration);
}

 public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

我们可以看到,生成了DefaultSqlSessionFactory作为SqlSessionFactory

MapperScannerRegistrar

MapperScannerRegistrar,Mappper 接口的扫描配置类。

我们程序一般会使用@MapperScan(basePackages = "xxx")来指定需要扫描的 Mapper 接口路径。

我们可以看其结构:

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

  /**
   * Type-safe alternative to {@link #basePackages()} for specifying the packages
   * to scan for annotated components. The package of each class specified will be scanned.
   * <p>Consider creating a special no-op marker class or interface in each package
   * that serves no purpose other than being referenced by this attribute.
   */
  Class<?>[] basePackageClasses() default {};

  /**
   * The {@link BeanNameGenerator} class to be used for naming detected components
   * within the Spring container.
   */
  Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

  /**
   * This property specifies the annotation that the scanner will search for.
   * <p>
   * The scanner will register all interfaces in the base package that also have
   * the specified annotation.
   * <p>
   * Note this can be combined with markerInterface.
   */
  Class<? extends Annotation> annotationClass() default Annotation.class;

  /**
   * This property specifies the parent that the scanner will search for.
   * <p>
   * The scanner will register all interfaces in the base package that also have
   * the specified interface class as a parent.
   * <p>
   * Note this can be combined with annotationClass.
   */
  Class<?> markerInterface() default Class.class;

  /**
   * Specifies which {@code SqlSessionTemplate} to use in the case that there is
   * more than one in the spring context. Usually this is only needed when you
   * have more than one datasource.
   */
  String sqlSessionTemplateRef() default "";

  /**
   * Specifies which {@code SqlSessionFactory} to use in the case that there is
   * more than one in the spring context. Usually this is only needed when you
   * have more than one datasource.
   */
  String sqlSessionFactoryRef() default "";

  /**
   * Specifies a custom MapperFactoryBean to return a mybatis proxy as spring bean.
   *
   */
  Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;

}

这里使用了@Import注解来引入配置类,相当于 Spring 中的 <import/>标签。@Import注解的参数可以是一个 @Configuration 配置类,也可以是一个ImportSelector接口,也可以是ImportBeanDefinitionRegistrar接口。如果是一个 @Configuration 配置类,会将类中 @Bean 修饰的 bean 注册到 IOC 容器;如果是ImportSelector接口的实现类,那就会根据实现的逻辑对 @Configuration 配置类进行筛选;如果是一个ImportBeanDefinitionRegistrar接口实现类,那么也会根据该实现类的逻辑来创建 Bean。

我们看引入的MapperScannerRegistrar结构:

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

  private ResourceLoader resourceLoader;

  /**
   * {@inheritDoc}
   */
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

    AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

    // this check is needed in Spring 3.1
    if (resourceLoader != null) {
      scanner.setResourceLoader(resourceLoader);
    }

    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
      scanner.setAnnotationClass(annotationClass);
    }

    Class<?> markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
      scanner.setMarkerInterface(markerInterface);
    }

    Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
      scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
    }

    Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
      scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
    }

    scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
    scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));

    List<String> basePackages = new ArrayList<String>();
    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();
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void setResourceLoader(ResourceLoader resourceLoader) {
    this.resourceLoader = resourceLoader;
  }

}

我们发现,MapperScannerRegistrarImportBeanDefinitionRegistrar 的实现类,而ImportBeanDefinitionRegistrar接口的作用是,它允许我们直接通过BeanDefinitionRegistry 对象注册 bean。其提供的 API 为 ImportBeanDefinitionRegistrar#registerBeanDefinitions,而此 API 被调用的时机是在解析导入的@Configuration配置类的核心方法ConfigurationClassPostProcessor#processConfigBeanDefinitions中,具体源码就不深入分析,感兴趣的同学自行了解。

接下来我们看MapperScannerRegistrar 注册 Mapper 接口的流程。首先根据标注的@MapperScan获取 basePackage,之后通过ClassPathMapperScanner去扫描包,获取所有 Mapper 接口类的BeanDefinition,之后具体配置,设置 beanClass 为MapperFactoryBean,设置 sqlSessionFactory 属性为上面生成的DefaultSqlSessionFactory,通过ClassPathBeanDefinitionScanner父类进行 bean 注册,自动注入的时候,就会调用MapperFactoryBeangetObject方法获取实际类型的实例。

如果是与 Spring 集成的话,配置文件如下:

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="xxx" />
    </bean>

这里的MapperScannerConfigurer实现的功能和MapperScannerRegistrar是一致的,我就不累述了。

MapperFactoryBean 父类 SqlSessionDaoSupport-持久层创建

private SqlSession sqlSession;

  private boolean externalSqlSession;

  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (!this.externalSqlSession) {
      //包装成 SqlSessionTemplate 对象
      this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
    }
  }

上面我们已经介绍了,MapperScannerRegistrar会给扫描到的 Mapper 替换为 MapperFactoryBean,并且会给其设置 sqlSessionFactory 属性。mybatis 持久层的操作都会被包装成SqlSessionTemplate对象。

SqlSessionTemplate 构造函数

基本都是由SqlSessionFactory作为入参:

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    //默认的 ExecutorType 为 ExecutorType.SIMPLE
    this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
  }

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;
    //对数据库的操作也会包装成代理的形式,所有的 CRUD 操作则都由 sqlSessionProxy 对象来完成
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
  }

这里代理了SqlSession,后续所有的 CRUD 操作都是基于SqlSession的。

MapperFactoryBean

它实现了FactoryBean,故是一个工厂 Bean,在初始化阶段,返回的实例类型就是getObject()返回值类型:

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

getSqlSession 得到的是之前创建的SqlSessionTemplate,于是我们直接看SqlSessionTemplte.getMapper(type)方法:

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

SqlSessionTemplate什么都没做,把包袱甩给了Configuration, 接下来就看看Configuration。源码如下:

 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

Configuration不要这烫手的山芋,接着甩给了MapperRegistry, 那咱看看MapperRegistry。 源码如下:

  /**
   * 烂活净让我来做了,没法了,下面没人了,我不做谁来做
   * @param type
   * @param sqlSession
   * @return
   */
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //能偷懒的就偷懒,俺把粗活交给 MapperProxyFactory 去做
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      //关键在这儿
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

MapperProxyFactory是个苦B的人,粗活最终交给它去做了。咱们看看源码:

/**
   * 别人虐我千百遍,我待别人如初恋
   * @param mapperProxy
   * @return
   */
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    //动态代理我们写的 dao 接口
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
  
  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

通过以上的动态代理,为每个 dao 接口,生成一个MapperProxy代理,咱们就可以方便地使用 dao 接口啦。至此,程序启动阶段关于 Mybatis 做的事情就完事了,接下来,我们看看执行一条 SQL 时的处理流程。

MapperProxy

当调用 dao 层的方法时,会走其代理类 MapperProxyinvoke方法:

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //二话不说,主要交给 MapperMethod 自己去管
    return mapperMethod.execute(sqlSession, args);
  }

MapperMethod:

  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    if (SqlCommandType.INSERT == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) {
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
        result = executeForMap(sqlSession, args);
      } else {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
    } else {
      throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

看着代码不少,不过其实就是先判断 CRUD 类型,然后根据类型去选择到底执行sqlSession中的哪个方法,绕了一圈,又转回sqlSession了,这里的sqlSession就是程序在启动阶段创建的sqlSessionTemplate。当sqlSession调用具体的 CURD 方法时,会调用其代理类SqlSessionInterceptor.invoke()方法:

private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      //通过 SqlSessionUtils.getSqlSession() 获得真实处理 CRUD 的持久层,默认为 DefaultSqlSession
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
        //执行 CRUD 操作
        Object result = method.invoke(sqlSession, args);
        //非事务处理的数据操作需要强制 commit
        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) {
          //关闭 sqlSession
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

创建sqlSession的步骤是通过SqlSessionUtils.getSqlSession()来完成的,默认生成的是DefaultSqlSession,下面我们拿 SELECT 方式为例,直接进入DefaultSqlSession.selectList()

 @Override
  public <E> List<E> selectList(String statement) {
    return this.selectList(statement, null);
  }

  @Override
  public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      // 将 sql 语句转换为 MappedStatement
      MappedStatement ms = configuration.getMappedStatement(statement);
      //CRUD 实际上是交给 Excetor 去处理
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

至此,sql 执行流程已经完毕,下面我们通过流程图来看看整个流程:


彩蛋

Mybatis 的二级缓存机制

一级缓存:基于 HashMap 的本地缓存,它的生命周期是和 SqlSession一致的,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认一级缓存是开启的。

二级缓存:也是基于 HashMap 的本地缓存,不同在于其存储作用域为Mapper级别的,如果多个SqlSession之间需要共享缓存,则需要使用到二级缓存,默认不打开二级缓存。

开启二级缓存的方式:
MyBatis 配置文件中通过:

<settings>
    <setting name = "cacheEnabled" value = "true" />
</settings>

还需要在 Mapper 的 xml 配置文件中加入<cache>标签。

开启二级缓存之后的数据查询流程:二级缓存 -> 一级缓存 -> 数据库。

缓存更新机制:当某一个作用域(一级缓存SqlSession/二级缓存Mapper)进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。

由上面我们知道,SqlSession是由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);

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

首先从SqlSessionHolder中获取SqlSession,如果SqlSessionHolder中没有,则新建一个。所以,我们看sessionHolder(executorType, holder)方法是如何获取SqlSession的:

private static SqlSession sessionHolder(ExecutorType executorType, SqlSessionHolder holder) {
    SqlSession session = null;
    // 验证当前线程是否存在事务
    if (holder != null && holder.isSynchronizedWithTransaction()) {
      if (holder.getExecutorType() != executorType) {
        throw new TransientDataAccessResourceException(
            "Cannot change the ExecutorType when there is an existing transaction");
      }

      holder.requested();

      LOGGER.debug(() -> "Fetched SqlSession [" + holder.getSqlSession() + "] from current transaction");
      session = holder.getSqlSession();
    }
    return session;
  }

由上面可知,只有当前线程存在事务时,SqlSession就会被复用,否则将新建一个。

上一篇下一篇

猜你喜欢

热点阅读