Mybatis Plus的sql语句执行分析(三)

group : com.baomidou

version:3.5.2.2-SNAPSHOT

目标

目的很简单,根据查询语句来反推执行过程。我们需要先了解整体脉络长什么样子,才能更加深入的了解源码内容,来方便我们后续进行二次开发。

查询语句

定义了AirBaseMapper:

public interface AirBaseMapper extends BaseMapper<AirBase> {
}

不想写sql,直接使用的LambdaQueryChainWrapper来执行count的查询。

 UserService bean = annotationConfigApplicationContext.getBean(UserService.class);
        AirBaseMapper airBaseMapper = annotationConfigApplicationContext.getBean(AirBaseMapper.class);
        LambdaQueryChainWrapper<AirBase> airBaseLambdaQueryChainWrapper = new LambdaQueryChainWrapper<>(airBaseMapper);
        Long one = airBaseLambdaQueryChainWrapper.count();

开始分析

直接查看airBaseLambdaQueryChainWrapper.count的实现,代码如下:

default Long count() {
    return SqlHelper.retCount(getBaseMapper().selectCount(getWrapper()));
}

SqlHelper.retCount不重要,直接跳过。

getBaseMapper()方法是LambdaQueryChainWrapper类中的方法,返回的是类的baseMapper属性。

public class LambdaQueryChainWrapper<T> extends AbstractChainWrapper<T, SFunction<T, ?>, LambdaQueryChainWrapper<T>, LambdaQueryWrapper<T>>
    implements ChainQuery<T>, Query<LambdaQueryChainWrapper<T>, T, SFunction<T, ?>> {

    private final BaseMapper<T> baseMapper;

    public LambdaQueryChainWrapper(BaseMapper<T> baseMapper) {
        super();
        this.baseMapper = baseMapper;
        super.wrapperChildren = new LambdaQueryWrapper<>();
    }
    
      @Override
    public BaseMapper<T> getBaseMapper() {
        return baseMapper;
    }
}

也就是调用的AirBaseMapper的selectCount方法。

关于AirBaseMapper

根据第一章我们可以知道我们自定义的AirBaseMapper在定义BeanDefinition的时候BeanClass会被设置为MapperFactoryBean。MapperFactoryBean实现了FactoryBean,所以创建对象的时候会执行getObject方法;同时MapperFactoryBean继承了SqlSessionDaoSupport并且最终实现了InitializingBean,所以在初始化完之后会执行afterPropertiesSet方法,继而最终执行MapperFacotyBean的checkDaoConfig方法。

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

  /**
   * {@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}
   */
  @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;
  }
}
public abstract class DaoSupport implements InitializingBean {
    protected final Log logger = LogFactory.getLog(this.getClass());

    public DaoSupport() {
    }

    public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
        this.checkDaoConfig();

        try {
            this.initDao();
        } catch (Exception var2) {
            throw new BeanInitializationException("Initialization of DAO failed", var2);
        }
    }

    protected abstract void checkDaoConfig() throws IllegalArgumentException;

    protected void initDao() throws Exception {
    }
}

这里有个疑问,就是MapperFactoryBean继承了抽象类SqlSessionDaoSupport,SqlSessionDaoSupport中有属性sqlSessionTemplate。那这个sqlSession是怎么获取到值的呢?其实就是ClassPathMapperSanner的processBeanDefinitions方法中设置的AutowireMode为AbstractBeanDefinition.AUTOWIRE_BY_TYPE,所以创建对象的时候会执行createSqlSessionTemplate方法。

public abstract class SqlSessionDaoSupport extends DaoSupport {

  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()) {
      this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
    }
  }

  /**
   * Create a SqlSessionTemplate for the given SqlSessionFactory. Only invoked if populating the DAO with a
   * SqlSessionFactory reference!
   * <p>
   * Can be overridden in subclasses to provide a SqlSessionTemplate instance with different configuration, or a custom
   * SqlSessionTemplate subclass.
   *
   * @param sqlSessionFactory
   *          the MyBatis SqlSessionFactory to create a SqlSessionTemplate for
   * @return the new SqlSessionTemplate instance
   * @see #setSqlSessionFactory
   */
  @SuppressWarnings("WeakerAccess")
  protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    return new SqlSessionTemplate(sqlSessionFactory);
  }

  /**
   * Return the MyBatis SqlSessionFactory used by this DAO.
   *
   * @return a factory of SqlSession
   */
  public final SqlSessionFactory getSqlSessionFactory() {
    return (this.sqlSessionTemplate != null ? this.sqlSessionTemplate.getSqlSessionFactory() : null);
  }

  /**
   * Set the SqlSessionTemplate for this DAO explicitly, as an alternative to specifying a SqlSessionFactory.
   *
   * @param sqlSessionTemplate
   *          a template of SqlSession
   * @see #setSqlSessionFactory
   */
  public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
    this.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;
  }

  /**
   * Return the SqlSessionTemplate for this DAO, pre-initialized with the SessionFactory or set explicitly.
   * <p>
   * <b>Note: The returned SqlSessionTemplate is a shared instance.</b> You may introspect its configuration, but not
   * modify the configuration (other than from within an {@link #initDao} implementation). Consider creating a custom
   * SqlSessionTemplate instance via {@code new SqlSessionTemplate(getSqlSessionFactory())}, in which case you're
   * allowed to customize the settings on the resulting instance.
   *
   * @return a template of SqlSession
   */
  public SqlSessionTemplate getSqlSessionTemplate() {
    return this.sqlSessionTemplate;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void checkDaoConfig() {
    notNull(this.sqlSessionTemplate, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
  }

}

Mapper接口的调用过程

首先画了一个基础的流程图,从创建Mapper对象到执行的过程,其中重点就是两个代理对象的使用。接下来就根据这个图来进行源码分析。

在这里插入图片描述

  • SqlSessionTemplate代理对象的创建(其实说的是内部的sqlSessionProxy属性的代理对象

在AirbaseMapper接口对象的创建过程中,会调用MapperFactoryBean的getObject方法。因为BeanClass被设置为MapperFactoryBean,同时AutowireMode为AbstractBeanDefinition.AUTOWIRE_BY_TYPE,有疑问查看深入理解Spring AutoWireMode文章

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

然后这个方法将被执行。

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

也就是每一个我们定义的mapper接口都会在内部创建一个SqlSessionProxy。核心就是SqlSessionInterceptor()。这个的sqlSession属性就是DefaultSqlSession了。因为方法内部执行了DefaultSqlSessionFactory的openSession。

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

method.invoke就会执行DefaultSqlSession类中去。

  • MybatisMapperProxy对象创建

通过上面的sqlSessionTemplate获取Mapper对象。

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // TODO 这里换成 MybatisMapperProxyFactory 而不是 MapperProxyFactory
    // fix https://github.com/baomidou/mybatis-plus/issues/4247
    MybatisMapperProxyFactory<T> mapperProxyFactory = (MybatisMapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
        mapperProxyFactory = (MybatisMapperProxyFactory<T>) knownMappers.entrySet().stream()
            .filter(t -> t.getKey().getName().equals(type.getName())).findFirst().map(Map.Entry::getValue)
            .orElseThrow(() -> new BindingException("Type " + type + " is not known to the MybatisPlusMapperRegistry."));
    }
    try {
        return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

非常简单就是代理对象的创建。

public T newInstance(SqlSession sqlSession) {
    final MybatisMapperProxy<T> mapperProxy = new MybatisMapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}

总结

通过反推的方式能知道大概的执行过程,了解内部的实现方式。然后我们就可以梳理里面有哪些细节,分别的作用是什么,来完善知识结构。

  • 17
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Mybatis-Plus是一个基于Mybatis的增强工具,它简化了Mybatis的使用方式,提供了更加易用的API和更强大的功能。在使用Mybatis-Plus编写SQL语句时,我们可以进行一些优化来提高性能和效率。 首先,可以通过使用合适的索引来优化SQL语句。索引可以加快查询速度,减少数据库的查询时间。需要注意的是,在创建索引时,要根据实际场景和查询需求选择适当的列进行索引,避免过多或冗余的索引。 其次,可以使用合适的分页查询方式进行优化。在查询大量数据时,可以通过分页查询的方式减少返回的数据量,提高查询效率。Mybatis-Plus提供了PageHelper插件,可以方便地进行分页查询。 另外,可以通过合理的SQL语句编写和设计来优化查询性能。避免在查询语句中使用通配符进行模糊查询,因为模糊查询会增加数据库的查询负担。如果查询条件中的某个字段有固定的值,可以将其写为精确查询条件,而不是使用模糊查询。 此外,还可以对需要查询的字段进行筛选,只选择需要的字段,避免返回多余的数据,减少网络传输和查询时间。 最后,为了避免SQL注入攻击,需要使用参数绑定的方式传递参数,而不是直接拼接参数到SQL语句中。这样可以避免特殊字符对SQL语句的破坏和安全问题。 总之,通过合理使用Mybatis-Plus提供的功能和技巧,我们可以对SQL语句进行优化,提高查询性能和效率。同时,还需要根据具体业务场景和需求进行调整和优化,持续改进和优化SQL语句的编写方式。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Thomas & Friends

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值