目录

  • Spring+Mybatis + Mybatis-Plus 自定义无XML的sql生成及MapperProxy代理生成
  • 问题产生背景
  • 框架是如何使用
  • 无Xml的SQL是如何生成生成及SQL长成什么样
  • MapperProxy代理生成
  • 总结

Spring+Mybatis + Mybatis-Plus 自定义无XML的sql生成及MapperProxy代理生成

问题产生背景

现在新服务ORM框架是使用mybatis3.4.6mybatis-plus2.2.0

最近在项目中偶然发现CouponRecord实体类中增加了这样一行代码如下,导致在Service中调用this.selectCount出现NPE。当然出现NPE很好解决,直接判断下是否为null就OK了。

<span style="color:#333333"><code><span style="color:#2b91af">@Data</span>
<span style="color:#2b91af">@Builder</span>
<span style="color:#2b91af">@NoArgsConstructor</span>
<span style="color:#2b91af">@AllArgsConstructor</span>
<span style="color:#2b91af">@TableName</span>(<span style="color:#a31515">"coupon_record"</span>)
<span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">CouponRecord</span> {
    ...
    <span style="color:#2b91af">@TableField</span>(value = <span style="color:#a31515">"product_quantity"</span>)
    <span style="color:#0000ff">private</span> BigDecimal productQuantity;
    <span style="color:#0000ff">public</span> BigDecimal <span style="color:#a31515">getProductQuantity</span>() {
        <span style="color:green">// 提交上的代码</span>
        <span style="color:#0000ff">return</span> <span style="color:#0000ff">this</span>.productQuantity.setScale(2, RoundingMode.HALF_DOWN);
        <span style="color:green">// 解决方式如下</span>
        <span style="color:green">//return this.productQuantity == null ? null : this.productQuantity.setScale(2, RoundingMode.HALF_DOWN);</span>
    }
    ...
}</code></span>

调用链:CouponRecordServiceImpl#count->ServiceImpl#selectCount->BaseMapper#selectCount,主要代码如下:

ServiceImpl的部分代码如下:

<span style="color:#333333"><code><span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">ServiceImpl</span><<span style="color:#a31515">M</span> <span style="color:#0000ff">extends</span> <span style="color:#a31515">BaseMapper</span><<span style="color:#a31515">T</span>>, <span style="color:#a31515">T</span>> <span style="color:#0000ff">implements</span> <span style="color:#a31515">IService</span><<span style="color:#a31515">T</span>> {
    <span style="color:#2b91af">@Autowired</span>
    <span style="color:#0000ff">protected</span> M baseMapper;
    ...
    <span style="color:#2b91af">@Override</span>
    <span style="color:#0000ff">public</span> <span style="color:#0000ff">int</span> <span style="color:#a31515">selectCount</span>(Wrapper<T> wrapper) {
        <span style="color:#0000ff">return</span> SqlHelper.retCount(baseMapper.selectCount(wrapper));
    }
    ...
}</code></span>

BaseMapper所有接口如下:

<span style="color:#333333"><code><span style="color:#0000ff">public</span> <span style="color:#0000ff">interface</span> <span style="color:#a31515">BaseMapper</span><<span style="color:#a31515">T</span>> {
    Integer <span style="color:#a31515">insert</span>(T entity);
    Integer <span style="color:#a31515">insertAllColumn</span>(T entity);
    Integer <span style="color:#a31515">deleteById</span>(Serializable id);
    Integer <span style="color:#a31515">deleteByMap</span>(@Param(<span style="color:#a31515">"cm"</span>) Map<String, Object> columnMap);
    Integer <span style="color:#a31515">delete</span>(@Param(<span style="color:#a31515">"ew"</span>) Wrapper<T> wrapper);
    Integer <span style="color:#a31515">deleteBatchIds</span>(@Param(<span style="color:#a31515">"coll"</span>) Collection<? extends Serializable> idList);
    Integer <span style="color:#a31515">updateById</span>(@Param(<span style="color:#a31515">"et"</span>) T entity);
    Integer <span style="color:#a31515">updateAllColumnById</span>(@Param(<span style="color:#a31515">"et"</span>) T entity);
    Integer <span style="color:#a31515">update</span>(@Param(<span style="color:#a31515">"et"</span>) T entity, @<span style="color:#a31515">Param</span>(<span style="color:#a31515">"ew"</span>) Wrapper<T> wrapper);
    T <span style="color:#a31515">selectById</span>(Serializable id);
    List<T> <span style="color:#a31515">selectBatchIds</span>(@Param(<span style="color:#a31515">"coll"</span>) Collection<? extends Serializable> idList);
    List<T> <span style="color:#a31515">selectByMap</span>(@Param(<span style="color:#a31515">"cm"</span>) Map<String, Object> columnMap);
    T <span style="color:#a31515">selectOne</span>(@Param(<span style="color:#a31515">"ew"</span>) T entity);
    Integer <span style="color:#a31515">selectCount</span>(@Param(<span style="color:#a31515">"ew"</span>) Wrapper<T> wrapper);
    List<T> <span style="color:#a31515">selectList</span>(@Param(<span style="color:#a31515">"ew"</span>) Wrapper<T> wrapper);
    List<Map<String, Object>> selectMaps(<span style="color:#2b91af">@Param</span>(<span style="color:#a31515">"ew"</span>) Wrapper<T> wrapper);
    List<Object> <span style="color:#a31515">selectObjs</span>(@Param(<span style="color:#a31515">"ew"</span>) Wrapper<T> wrapper);
    List<T> <span style="color:#a31515">selectPage</span>(RowBounds rowBounds, @Param(<span style="color:#a31515">"ew"</span>) Wrapper<T> wrapper);
    List<Map<String, Object>> selectMapsPage(RowBounds rowBounds, <span style="color:#2b91af">@Param</span>(<span style="color:#a31515">"ew"</span>) Wrapper<T> wrapper);
}</code></span>

我们在业务代码CouponRecordServiceImpl#count中直接调用,可能会产生如下疑问?

  • 我们没有配置XML为什么调用selectCount可以查询?既然可以查询那么生成的SQL长成什么样子?
  • 通过看ServiceImpl中的代码,会发现是直接注入baseMapper,baseMapper明明是接口咋个就可以使用了呢?

对于工作了这么多年的老司机,猜也猜的出百分之八九十吧。在整理这篇文章之前,以前浏览过,我确实忘记的差不多了。感谢公司能提供给大家不管是组内分享还是部门分享机会,分享总会给自己和他人的很大进步。不扯淡这些了。下面将对此这些疑问来逐一解决。但是这里要说明下,这里只看我们关心的内容,其他比如在与spring整合后有些为什么要这样写,可以找学习spring组来做分享或者后面整理好文章后在分享。

框架是如何使用

任何框架学习,首先要会用,不然就是扯淡。框架都是在实际的应用中逐渐抽象出来的,简化我们工作。

Service主要代码如下:

<span style="color:#333333"><code><span style="color:#2b91af">@Service</span>
<span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">CouponRecordService</span> <span style="color:#0000ff">extends</span> <span style="color:#a31515">ServiceImpl</span><<span style="color:#a31515">CouponRecordDao</span>, <span style="color:#a31515">CouponRecord</span>> {
    <span style="color:#0000ff">public</span> <span style="color:#0000ff">int</span> <span style="color:#a31515">count</span>(Date endTime) {
        CouponRecord conditionCouponRecord = CouponRecord.builder().status(CouponStatus.USED).isDelete(YesNo.NO.getValue()).build();
        <span style="color:#0000ff">return</span> selectCount(<span style="color:#0000ff">new</span> EntityWrapper<>(conditionCouponRecord).le(<span style="color:#a31515">"create_time"</span>, endTime).isNotNull(<span style="color:#a31515">"order_no"</span>));
    }
}</code></span>

Dao(或者叫Mapper)

<span style="color:#333333"><code><span style="color:#0000ff">public</span> <span style="color:#0000ff">interface</span> <span style="color:#a31515">CouponRecordDao</span> <span style="color:#0000ff">extends</span> <span style="color:#a31515">BaseMapper</span><<span style="color:#a31515">CouponRecord</span>> {
}</code></span>

spring的相关配置如下:

<span style="color:#333333"><code><span style="color:#0000ff"><<span style="color:#0000ff">bean</span> <span style="color:red">id</span>=<span style="color:#a31515">"sqlSessionFactory"</span> <span style="color:red">class</span>=<span style="color:#a31515">"com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean"</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">property</span> <span style="color:red">name</span>=<span style="color:#a31515">"dataSource"</span> <span style="color:red">ref</span>=<span style="color:#a31515">"dataSource"</span>/></span>

    <span style="color:green"><!-- 自动扫描entity目录, 省掉Configuration.xml里的手工配置 --></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">property</span> <span style="color:red">name</span>=<span style="color:#a31515">"configLocation"</span> <span style="color:red">value</span>=<span style="color:#a31515">"classpath:mybatis-config.xml"</span>/></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">property</span> <span style="color:red">name</span>=<span style="color:#a31515">"mapperLocations"</span> <span style="color:red">value</span>=<span style="color:#a31515">"classpath*:mapper/**/*.xml"</span>/></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">property</span> <span style="color:red">name</span>=<span style="color:#a31515">"plugins"</span>></span>
        <span style="color:#0000ff"><<span style="color:#0000ff">array</span>></span>
            <span style="color:green"><!-- 分页插件配置 --></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">bean</span> <span style="color:red">id</span>=<span style="color:#a31515">"paginationInterceptor"</span> <span style="color:red">class</span>=<span style="color:#a31515">"com.baomidou.mybatisplus.plugins.PaginationInterceptor"</span>></span>
                <span style="color:#0000ff"><<span style="color:#0000ff">property</span> <span style="color:red">name</span>=<span style="color:#a31515">"dialectType"</span> <span style="color:red">value</span>=<span style="color:#a31515">"mysql"</span>/></span>
            <span style="color:#0000ff"></<span style="color:#0000ff">bean</span>></span>
            <span style="color:#0000ff"><<span style="color:#0000ff">bean</span> <span style="color:red">id</span>=<span style="color:#a31515">"limitInterceptor"</span> <span style="color:red">class</span>=<span style="color:#a31515">"com.common.mybatis.LimitInterceptor"</span>/></span>
        <span style="color:#0000ff"></<span style="color:#0000ff">array</span>></span>
    <span style="color:#0000ff"></<span style="color:#0000ff">property</span>></span>
<span style="color:#0000ff"></<span style="color:#0000ff">bean</span>></span>
<span style="color:#0000ff"><<span style="color:#0000ff">bean</span> <span style="color:red">class</span>=<span style="color:#a31515">"org.mybatis.spring.mapper.MapperScannerConfigurer"</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">property</span> <span style="color:red">name</span>=<span style="color:#a31515">"basePackage"</span> <span style="color:red">value</span>=<span style="color:#a31515">"com.merchant.activity.**.dao"</span>/></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">property</span> <span style="color:red">name</span>=<span style="color:#a31515">"sqlSessionFactoryBeanName"</span> <span style="color:red">value</span>=<span style="color:#a31515">"sqlSessionFactory"</span>/></span>
<span style="color:#0000ff"></<span style="color:#0000ff">bean</span>></span>
<span style="color:#0000ff"><<span style="color:#0000ff">context:component-scan</span> <span style="color:red">base-package</span>=<span style="color:#a31515">"com.common.**,com.merchant.activity.**"</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">context:exclude-filter</span> <span style="color:red">type</span>=<span style="color:#a31515">"annotation"</span> <span style="color:red">expression</span>=<span style="color:#a31515">"org.springframework.stereotype.Controller"</span>/></span>
<span style="color:#0000ff"></<span style="color:#0000ff">context:component-scan</span>></span></code></span>

用法+大致配置就是这样的。接下来看看这些无Xml的SQL是怎么生成的以及生成出来的SQL长成什么样?

无Xml的SQL是如何生成生成及SQL长成什么样

在如何使用中,可以看到XML中有如下一段配置:

<span style="color:#333333"><code><span style="color:#0000ff"><<span style="color:#0000ff">bean</span> <span style="color:red">class</span>=<span style="color:#a31515">"org.mybatis.spring.mapper.MapperScannerConfigurer"</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">property</span> <span style="color:red">name</span>=<span style="color:#a31515">"basePackage"</span> <span style="color:red">value</span>=<span style="color:#a31515">"com.merchant.activity.**.dao"</span>/></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">property</span> <span style="color:red">name</span>=<span style="color:#a31515">"sqlSessionFactoryBeanName"</span> <span style="color:red">value</span>=<span style="color:#a31515">"sqlSessionFactory"</span>/></span>
<span style="color:#0000ff"></<span style="color:#0000ff">bean</span>></span></code></span>

这段的配置作用就是扫描我们的Mapper或者Dao的入口。

大概类图如下:

BaseMapperX 表关联查询 basemapper的使用_List

接下来对源码做分析

BeanDefinition解析阶段

MapperScannerConfigurer

MapperScannerConfigurer得继承关系如下图:

BaseMapperX 表关联查询 basemapper的使用_spring_02

从图中看出MapperScannerConfigurer实现了我们关注的BeanDefinitionRegistryPostProcessor、InitializingBean接口,Spring在初始化Bean的时候会执行对应的方法。

ClassPathMapperScanner构造

构造ClassPathMapperScanner扫描类,扫描basePackage包下的Mapper或者Dao并注册我们的Mapper Bean到容器中.

<span style="color:#333333"><code><span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">MapperScannerConfigurer</span> <span style="color:#0000ff">implements</span> <span style="color:#a31515">BeanDefinitionRegistryPostProcessor</span>, <span style="color:#a31515">InitializingBean</span>, <span style="color:#a31515">ApplicationContextAware</span>, <span style="color:#a31515">BeanNameAware</span> {
  ...
  <span style="color:#2b91af">@Override</span>
  <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">afterPropertiesSet</span>() <span style="color:#0000ff">throws</span> Exception {
      <span style="color:green">// 验证是否配置了basePackage</span>
    notNull(<span style="color:#0000ff">this</span>.basePackage, <span style="color:#a31515">"Property 'basePackage' is required"</span>);
  }

  <span style="color:#2b91af">@Override</span>
  <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">postProcessBeanFactory</span>(ConfigurableListableBeanFactory beanFactory) {
    <span style="color:green">// left intentionally blank</span>
  }

  <span style="color:#2b91af">@Override</span>
  <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">postProcessBeanDefinitionRegistry</span>(BeanDefinitionRegistry registry) {
    <span style="color:green">// 是否有占位符,处理之</span>
    <span style="color:#0000ff">if</span> (<span style="color:#0000ff">this</span>.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }
    <span style="color:green">// 扫描</span>
    ClassPathMapperScanner scanner = <span style="color:#0000ff">new</span> ClassPathMapperScanner(registry);
    scanner.setAddToConfig(<span style="color:#0000ff">this</span>.addToConfig);
    scanner.setAnnotationClass(<span style="color:#0000ff">this</span>.annotationClass);
    scanner.setMarkerInterface(<span style="color:#0000ff">this</span>.markerInterface);
    scanner.setSqlSessionFactory(<span style="color:#0000ff">this</span>.sqlSessionFactory);
    scanner.setSqlSessionTemplate(<span style="color:#0000ff">this</span>.sqlSessionTemplate);
    scanner.setSqlSessionFactoryBeanName(<span style="color:#0000ff">this</span>.sqlSessionFactoryBeanName);
    scanner.setSqlSessionTemplateBeanName(<span style="color:#0000ff">this</span>.sqlSessionTemplateBeanName);
    scanner.setResourceLoader(<span style="color:#0000ff">this</span>.applicationContext);
    scanner.setBeanNameGenerator(<span style="color:#0000ff">this</span>.nameGenerator);
    <span style="color:green">// 注册一些过滤器,包括和不包括。有部分可以在xml中配置,比如:annotationClass、markerInterface</span>
    scanner.registerFilters();
    scanner.scan(StringUtils.tokenizeToStringArray(<span style="color:#0000ff">this</span>.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }
  ...
}</code></span>

ClassPathMapperScanner#scan

扫描类并生成BeanDefinition注入到Spring容器中,注意这里的ClassPathMapperScanner继承ClassPathBeanDefinitionScanner,在ClassPathMapperScanner中未实现scan,所以直接调用父类的scan方法。为了便于阅读这里将源码中的日志删除了。大致源码如下:

<span style="color:#333333"><code><span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">ClassPathBeanDefinitionScanner</span> <span style="color:#0000ff">extends</span> <span style="color:#a31515">ClassPathScanningCandidateComponentProvider</span> {
    ...
    <span style="color:#0000ff">public</span> <span style="color:#0000ff">int</span> <span style="color:#a31515">scan</span>(String... basePackages) {
        <span style="color:green">// 获取之前容器中bean的数量</span>
        <span style="color:#0000ff">int</span> beanCountAtScanStart = <span style="color:#0000ff">this</span>.registry.getBeanDefinitionCount();
        <span style="color:green">// 真正干事的---扫描, 调用子类ClassPathMapperScanner#doScan(basePackages)方法</span>
        doScan(basePackages);
        <span style="color:green">// Register annotation config processors, if necessary.</span>
        <span style="color:#0000ff">if</span> (<span style="color:#0000ff">this</span>.includeAnnotationConfig) {
            AnnotationConfigUtils.registerAnnotationConfigProcessors(<span style="color:#0000ff">this</span>.registry);
        }
        <span style="color:green">// 返回注册bean的数量</span>
        <span style="color:#0000ff">return</span> (<span style="color:#0000ff">this</span>.registry.getBeanDefinitionCount() - beanCountAtScanStart);
    }
    <span style="color:green">// 真正干事的扫描 生成BeanDefinition集合</span>
    <span style="color:#0000ff">protected</span> Set<BeanDefinitionHolder> <span style="color:#a31515">doScan</span>(String... basePackages) {
        Assert.notEmpty(basePackages, <span style="color:#a31515">"At least one base package must be specified"</span>);
        <span style="color:green">// BeanDefinitionHolder 的集合</span>
        Set<BeanDefinitionHolder> beanDefinitions = <span style="color:#0000ff">new</span> LinkedHashSet<BeanDefinitionHolder>();
        <span style="color:#0000ff">for</span> (String basePackage : basePackages) {
            <span style="color:green">// 通过查找候选bean定义</span>
            Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
            <span style="color:green">// 遍历进行部分逻辑处理</span>
            <span style="color:#0000ff">for</span> (BeanDefinition candidate : candidates) {
                ScopeMetadata scopeMetadata = <span style="color:#0000ff">this</span>.scopeMetadataResolver.resolveScopeMetadata(candidate);
                <span style="color:green">// 设置作用域</span>
                candidate.setScope(scopeMetadata.getScopeName());
                <span style="color:green">// 生成beanName</span>
                String beanName = <span style="color:#0000ff">this</span>.beanNameGenerator.generateBeanName(candidate, <span style="color:#0000ff">this</span>.registry);
                <span style="color:#0000ff">if</span> (candidate <span style="color:#0000ff">instanceof</span> AbstractBeanDefinition) {
                    <span style="color:green">// 增加默认值,autowireCandidate</span>
                    postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
                }
                <span style="color:#0000ff">if</span> (candidate <span style="color:#0000ff">instanceof</span> AnnotatedBeanDefinition) {
                    AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
                }
                <span style="color:green">// 注册BeanDefinition到容器中。</span>
                <span style="color:#0000ff">if</span> (checkCandidate(beanName, candidate)) {
                    BeanDefinitionHolder definitionHolder = <span style="color:#0000ff">new</span> BeanDefinitionHolder(candidate, beanName);
                    definitionHolder =
                            AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, <span style="color:#0000ff">this</span>.registry);
                    beanDefinitions.add(definitionHolder);
                    registerBeanDefinition(definitionHolder, <span style="color:#0000ff">this</span>.registry);
                }
            }
        }
        <span style="color:#0000ff">return</span> beanDefinitions;
    }
    ...
}</code></span>
<span style="color:#333333"><code><span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">ClassPathMapperScanner</span> <span style="color:#0000ff">extends</span> <span style="color:#a31515">ClassPathBeanDefinitionScanner</span> {
  <span style="color:#0000ff">public</span> Set<BeanDefinitionHolder> <span style="color:#a31515">doScan</span>(String... basePackages) {
      <span style="color:green">// 调用父类的ClassPathBeanDefinitionScanner#doScaner(basePackages)方法,扫描生产BeanDefinitionHolder集合</span>
    Set<BeanDefinitionHolder> beanDefinitions = <span style="color:#0000ff">super</span>.doScan(basePackages);

    <span style="color:#0000ff">if</span> (beanDefinitions.isEmpty()) {
      logger.warn(<span style="color:#a31515">"No MyBatis mapper was found in '"</span> + Arrays.toString(basePackages) + <span style="color:#a31515">"' package. Please check your configuration."</span>);
    } <span style="color:#0000ff">else</span> {
      <span style="color:green">// MapperBean 需要一些额外的处理,查看这个方法</span>
      processBeanDefinitions(beanDefinitions);
    }

    <span style="color:#0000ff">return</span> beanDefinitions;
  }
  
    <span style="color:green">//对每个Mapper的BeanDefinition定义处理, </span>
  <span style="color:#0000ff">private</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">processBeanDefinitions</span>(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    <span style="color:#0000ff">for</span> (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      <span style="color:green">// 构造器参数,下一行代码将Bean设置为MapperFactoryBean,MapperFactoryBean的构造器中有个参数是mapperInterface</span>
      definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); 
      <span style="color:green">// 这一步非常重要,把我们的Bean设置为MapperFactoryBean,接下来会看到MapperFactoryBean的继承关系</span>
      definition.setBeanClass(<span style="color:#0000ff">this</span>.mapperFactoryBean.getClass());

      definition.getPropertyValues().add(<span style="color:#a31515">"addToConfig"</span>, <span style="color:#0000ff">this</span>.addToConfig);

      <span style="color:#0000ff">boolean</span> explicitFactoryUsed = <span style="color:#0000ff">false</span>;
      <span style="color:green">// 在bean中增加sqlSessionFactory</span>
      <span style="color:#0000ff">if</span> (StringUtils.hasText(<span style="color:#0000ff">this</span>.sqlSessionFactoryBeanName)) {
        definition.getPropertyValues().add(<span style="color:#a31515">"sqlSessionFactory"</span>, <span style="color:#0000ff">new</span> RuntimeBeanReference(<span style="color:#0000ff">this</span>.sqlSessionFactoryBeanName));
        explicitFactoryUsed = <span style="color:#0000ff">true</span>;
      } <span style="color:#0000ff">else</span> <span style="color:#0000ff">if</span> (<span style="color:#0000ff">this</span>.sqlSessionFactory != <span style="color:#0000ff">null</span>) {
        definition.getPropertyValues().add(<span style="color:#a31515">"sqlSessionFactory"</span>, <span style="color:#0000ff">this</span>.sqlSessionFactory);
        explicitFactoryUsed = <span style="color:#0000ff">true</span>;
      }
      <span style="color:green">// 在bean中增加sqlSessionTemplate</span>
      <span style="color:#0000ff">if</span> (StringUtils.hasText(<span style="color:#0000ff">this</span>.sqlSessionTemplateBeanName)) {
        definition.getPropertyValues().add(<span style="color:#a31515">"sqlSessionTemplate"</span>, <span style="color:#0000ff">new</span> RuntimeBeanReference(<span style="color:#0000ff">this</span>.sqlSessionTemplateBeanName));
        explicitFactoryUsed = <span style="color:#0000ff">true</span>;
      } <span style="color:#0000ff">else</span> <span style="color:#0000ff">if</span> (<span style="color:#0000ff">this</span>.sqlSessionTemplate != <span style="color:#0000ff">null</span>) {
        definition.getPropertyValues().add(<span style="color:#a31515">"sqlSessionTemplate"</span>, <span style="color:#0000ff">this</span>.sqlSessionTemplate);
        explicitFactoryUsed = <span style="color:#0000ff">true</span>;
      }
      <span style="color:green">// 设置自动注入模式</span>
      <span style="color:#0000ff">if</span> (!explicitFactoryUsed) {
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }
  }
}</code></span>

写到代码的注释可能都不怎么关注,这里再次强调下重点,如果不注意后续可能有些会懵逼的。这是怎么来的。

  1. BeanDefinition的class设置为MapperFactoryBean
  2. 将原始mapper的接口类型以MapperFactoryBean构造器的参数传入,也就是后面你将看到参数是mapperInterface.

BeanDefinition初始化阶段

MapperFactoryBean

经过上面的扫描并注册,现在容器中已经存在了我们的Mapper Bean了,在上面的说构建Mapper BeanDefinition的时候注意这些BeanDefinition的class类型设置为了MapperFactoryBean,先看看MapperFactoryBean的继承关系如下:

BaseMapperX 表关联查询 basemapper的使用_SQL_03

从图中,看出MapperFactoryBean是实现了InitializingBean接口。DaoSupport对afterPropertiesSet()实现了。我们都知道Spring在初始化会Bean的时候将会调用afterPropertiesSet()方法。那么看看这个方法干了什么事

<span style="color:#333333"><code><span style="color:#0000ff">public</span> <span style="color:#0000ff">abstract</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">DaoSupport</span> <span style="color:#0000ff">implements</span> <span style="color:#a31515">InitializingBean</span> {
    <span style="color:#0000ff">protected</span> <span style="color:#0000ff">final</span> Log logger = LogFactory.getLog(getClass());
    <span style="color:#2b91af">@Override</span>
    <span style="color:#0000ff">public</span> <span style="color:#0000ff">final</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">afterPropertiesSet</span>() <span style="color:#0000ff">throws</span> IllegalArgumentException, BeanInitializationException {
        <span style="color:green">// 检查Dao配置</span>
        checkDaoConfig();
        <span style="color:green">// Let concrete implementations initialize themselves.</span>
        <span style="color:#0000ff">try</span> {
            initDao();
        }
        <span style="color:#0000ff">catch</span> (Exception ex) {
            <span style="color:#0000ff">throw</span> <span style="color:#0000ff">new</span> BeanInitializationException(<span style="color:#a31515">"Initialization of DAO failed"</span>, ex);
        }
    }
    <span style="color:#0000ff">protected</span> <span style="color:#0000ff">abstract</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">checkDaoConfig</span>() <span style="color:#0000ff">throws</span> IllegalArgumentException;
    <span style="color:#0000ff">protected</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">initDao</span>() <span style="color:#0000ff">throws</span> Exception {
    }
}</code></span>

一看典型的模板设计模式,真正处理在子类中。这里我们关心的是checkDaoConfig(),看看子类MapperFactoryBean#checkDaoConfig实现干了些什么事

<span style="color:#333333"><code><span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">MapperFactoryBean</span><<span style="color:#a31515">T</span>> <span style="color:#0000ff">extends</span> <span style="color:#a31515">SqlSessionDaoSupport</span> <span style="color:#0000ff">implements</span> <span style="color:#a31515">FactoryBean</span><<span style="color:#a31515">T</span>> {
  ...
  <span style="color:#0000ff">protected</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">checkDaoConfig</span>() {
    <span style="color:#0000ff">super</span>.checkDaoConfig();<span style="color:green">//调用父类的方法,父类就是检查sqlSession是否为null。null的话抛出异常</span>
    notNull(<span style="color:#0000ff">this</span>.mapperInterface, <span style="color:#a31515">"Property 'mapperInterface' is required"</span>);
    <span style="color:green">// 通过sqlSession获取MybatisConfiguration,相当于我们每一个MapperBean都是由SqlSession的,否则你想咋个查询呢</span>
    Configuration configuration = getSqlSession().getConfiguration();
    <span style="color:#0000ff">if</span> (<span style="color:#0000ff">this</span>.addToConfig && !configuration.hasMapper(<span style="color:#0000ff">this</span>.mapperInterface)) {
      <span style="color:#0000ff">try</span> {
        <span style="color:green">// 将mapperInterface注册到configuration中。</span>
        configuration.addMapper(<span style="color:#0000ff">this</span>.mapperInterface);
      } <span style="color:#0000ff">catch</span> (Exception e) {
        logger.error(<span style="color:#a31515">"Error while adding the mapper '"</span> + <span style="color:#0000ff">this</span>.mapperInterface + <span style="color:#a31515">"' to configuration."</span>, e);
        <span style="color:#0000ff">throw</span> <span style="color:#0000ff">new</span> IllegalArgumentException(e);
      } <span style="color:#0000ff">finally</span> {
        ErrorContext.instance().reset();
      }
    }
  }
  ...
}</code></span>

MybatisConfiguration#addMapper干的就是将类型注册到我们Mapper容器中,便于后续取

<span style="color:#333333"><code><span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">MybatisConfiguration</span> <span style="color:#0000ff">extends</span> <span style="color:#a31515">Configuration</span> {
    ...
    <span style="color:#0000ff">public</span> <span style="color:#0000ff">final</span> MybatisMapperRegistry mybatisMapperRegistry = <span style="color:#0000ff">new</span> MybatisMapperRegistry(<span style="color:#0000ff">this</span>);
    <span style="color:#0000ff">public</span> <T> <span style="color:#0000ff">void</span> <span style="color:#a31515">addMapper</span>(Class<T> type) {
        mybatisMapperRegistry.addMapper(type);
    }
    ...
}</code></span>

接下来就要看看MybatisMapperRegistry#addMapper注册到底干了何事。猜猜应该就是自定义无XML的sql生产注入。哪些是自定义?就是我们BaseMapper中的那一堆方法。

XXXRegistry 类的名字起的真好,看名字就是一个注册器。这里的注册器有一箭双雕的作用

  1. 定义了一个Map,缓存所知道的Mapper,后面初始化MapperProxy代理用的着,不然后面不好取哦
  2. 将解析出来的SQL,注册到Configuration中
<span style="color:#333333"><code><span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">MybatisMapperRegistry</span> <span style="color:#0000ff">extends</span> <span style="color:#a31515">MapperRegistry</span> {
    ...
    <span style="color:green">// 这个knownMappers之前以为起的不够好。。当再次看的时候发现还真不错,known翻译就是众所周知,那么在这里就是我们已经扫描并且已经注册了的Mapper了,在内部来说当然是都知道的。</span>
    <span style="color:#0000ff">private</span> <span style="color:#0000ff">final</span> Map<Class<?>, MapperProxyFactory<?>> knownMappers = <span style="color:#0000ff">new</span> HashMap<>();
    <span style="color:#0000ff">public</span> <T> <span style="color:#0000ff">void</span> <span style="color:#a31515">addMapper</span>(Class<T> type) {
        <span style="color:#0000ff">if</span> (type.isInterface()) {
            <span style="color:green">// 注入过就不再执行了。</span>
            <span style="color:#0000ff">if</span> (hasMapper(type)) {
                <span style="color:#0000ff">return</span>;
            }
            <span style="color:#0000ff">boolean</span> loadCompleted = <span style="color:#0000ff">false</span>;
            <span style="color:#0000ff">try</span> {
                <span style="color:green">// 这里先记着,后面查看我们MapperProxy代理用的着哦</span>
                knownMappers.put(type, <span style="color:#0000ff">new</span> MapperProxyFactory<>(type));
                <span style="color:green">// mybatisMapper注解构建器</span>
                MybatisMapperAnnotationBuilder parser = <span style="color:#0000ff">new</span> MybatisMapperAnnotationBuilder(config, type);
                <span style="color:green">// 解析</span>
                parser.parse();
                loadCompleted = <span style="color:#0000ff">true</span>;
            } <span style="color:#0000ff">finally</span> {
                <span style="color:#0000ff">if</span> (!loadCompleted) {
                    knownMappers.remove(type);
                }
            }
        }
    }
    ...
}</code></span>

MybatisMapperAnnotationBuilder#parse

接下来将是生成无xml对应的SQL了。??

<span style="color:#333333"><code><span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">MybatisMapperAnnotationBuilder</span> <span style="color:#0000ff">extends</span> <span style="color:#a31515">MapperAnnotationBuilder</span> {
    ...
    <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">parse</span>() {
        String resource = type.toString();
        <span style="color:#0000ff">if</span> (!configuration.isResourceLoaded(resource)) {
            <span style="color:green">// 加载xml</span>
            loadXmlResource();
            configuration.addLoadedResource(resource);
            assistant.setCurrentNamespace(type.getName());
            parseCache();
            parseCacheRef();
            Method[] methods = type.getMethods();
            <span style="color:green">// 类型是否是BaseMapper</span>
            <span style="color:#0000ff">if</span> (BaseMapper.class.isAssignableFrom(type)) {
                GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
            }
            <span style="color:#0000ff">for</span> (Method method : methods) {
                <span style="color:#0000ff">try</span> {
                    <span style="color:green">// issue #237</span>
                    <span style="color:#0000ff">if</span> (!method.isBridge()) {
                        parseStatement(method);
                    }
                } <span style="color:#0000ff">catch</span> (IncompleteElementException e) {
                    configuration.addIncompleteMethod(<span style="color:#0000ff">new</span> MethodResolver(<span style="color:#0000ff">this</span>, method));
                }
            }
        }
        parsePendingMethods();
    }
    ...
}</code></span>

在上面的parse方法中,我们重点关心GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);获取SQL注入器,再根据类型type生成sql注入

<span style="color:#333333"><code><span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">AutoSqlInjector</span> <span style="color:#0000ff">implements</span> <span style="color:#a31515">ISqlInjector</span> {
    <span style="color:green">// 注入到builderAssistant</span>
     <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">inspectInject</span>(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
        String className = mapperClass.toString();
        Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration());
         <span style="color:green">// 判断之前是否注入过</span>
        <span style="color:#0000ff">if</span> (!mapperRegistryCache.contains(className)) {
            <span style="color:green">// 注入</span>
            inject(builderAssistant, mapperClass);
            <span style="color:green">// 加入到缓存中</span>
            mapperRegistryCache.add(className);
        }
    }
    
    <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">inject</span>(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
        <span style="color:#0000ff">this</span>.configuration = builderAssistant.getConfiguration();
        <span style="color:#0000ff">this</span>.builderAssistant = builderAssistant;
        <span style="color:#0000ff">this</span>.languageDriver = configuration.getDefaultScriptingLanguageInstance();
        <span style="color:green">/**
         * 驼峰设置 PLUS 配置 > 原始配置
         */</span>
        GlobalConfiguration globalCache = <span style="color:#0000ff">this</span>.getGlobalConfig();
        <span style="color:#0000ff">if</span> (!globalCache.isDbColumnUnderline()) {
            globalCache.setDbColumnUnderline(configuration.isMapUnderscoreToCamelCase());
        }
        Class<?> modelClass = extractModelClass(mapperClass);
        <span style="color:#0000ff">if</span> (<span style="color:#0000ff">null</span> != modelClass) {
            <span style="color:green">/**
             * 初始化 SQL 解析
             */</span>
            <span style="color:#0000ff">if</span> (globalCache.isSqlParserCache()) {
                PluginUtils.initSqlParserInfoCache(mapperClass);
            }
            <span style="color:green">// 这里获取tableInfo. 这里你会看到我们@TableName了。。</span>
            TableInfo table = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
            <span style="color:green">//生成sql注入sql</span>
            injectSql(builderAssistant, mapperClass, modelClass, table);
        }
    }
    
    <span style="color:green">// 看到这个方法里面的injectXXXX是不是和我们BaseMapper里的一样呢。对这里挨着一个个的去实现。</span>
    <span style="color:#0000ff">protected</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">injectSql</span>(MapperBuilderAssistant builderAssistant, Class<?> mapperClass, Class<?> modelClass, TableInfo table) {
        <span style="color:green">/**
         * #148 表信息包含主键,注入主键相关方法
         */</span>
        <span style="color:#0000ff">if</span> (StringUtils.isNotEmpty(table.getKeyProperty())) {
            <span style="color:green">/** 删除 */</span>
            <span style="color:#0000ff">this</span>.injectDeleteByIdSql(<span style="color:#0000ff">false</span>, mapperClass, modelClass, table);
            <span style="color:#0000ff">this</span>.injectDeleteByIdSql(<span style="color:#0000ff">true</span>, mapperClass, modelClass, table);
            <span style="color:green">/** 修改 */</span>
            <span style="color:#0000ff">this</span>.injectUpdateByIdSql(<span style="color:#0000ff">true</span>, mapperClass, modelClass, table);
            <span style="color:#0000ff">this</span>.injectUpdateByIdSql(<span style="color:#0000ff">false</span>, mapperClass, modelClass, table);
            <span style="color:green">/** 查询 */</span>
            <span style="color:#0000ff">this</span>.injectSelectByIdSql(<span style="color:#0000ff">false</span>, mapperClass, modelClass, table);
            <span style="color:#0000ff">this</span>.injectSelectByIdSql(<span style="color:#0000ff">true</span>, mapperClass, modelClass, table);
        } <span style="color:#0000ff">else</span> {
            <span style="color:green">// 表不包含主键时 给予警告</span>
            logger.warn(String.format(<span style="color:#a31515">"%s ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method."</span>,
                    modelClass.toString()));
        }
        <span style="color:green">/**
         * 正常注入无需主键方法
         */</span>
        <span style="color:green">/** 插入 */</span>
        <span style="color:#0000ff">this</span>.injectInsertOneSql(<span style="color:#0000ff">true</span>, mapperClass, modelClass, table);
        <span style="color:#0000ff">this</span>.injectInsertOneSql(<span style="color:#0000ff">false</span>, mapperClass, modelClass, table);
        <span style="color:green">/** 删除 */</span>
        <span style="color:#0000ff">this</span>.injectDeleteSql(mapperClass, modelClass, table);
        <span style="color:#0000ff">this</span>.injectDeleteByMapSql(mapperClass, table);
        <span style="color:green">/** 修改 */</span>
        <span style="color:#0000ff">this</span>.injectUpdateSql(mapperClass, modelClass, table);
        <span style="color:green">/** 查询 */</span>
        <span style="color:#0000ff">this</span>.injectSelectByMapSql(mapperClass, modelClass, table);
        <span style="color:#0000ff">this</span>.injectSelectOneSql(mapperClass, modelClass, table);
        <span style="color:#0000ff">this</span>.injectSelectCountSql(mapperClass, modelClass, table);
        <span style="color:#0000ff">this</span>.injectSelectListSql(SqlMethod.SELECT_LIST, mapperClass, modelClass, table);
        <span style="color:#0000ff">this</span>.injectSelectListSql(SqlMethod.SELECT_PAGE, mapperClass, modelClass, table);
        <span style="color:#0000ff">this</span>.injectSelectMapsSql(SqlMethod.SELECT_MAPS, mapperClass, modelClass, table);
        <span style="color:#0000ff">this</span>.injectSelectMapsSql(SqlMethod.SELECT_MAPS_PAGE, mapperClass, modelClass, table);
        <span style="color:#0000ff">this</span>.injectSelectObjsSql(SqlMethod.SELECT_OBJS, mapperClass, modelClass, table);
        <span style="color:green">/** 自定义方法 */</span>
        <span style="color:#0000ff">this</span>.inject(configuration, builderAssistant, mapperClass, modelClass, table);
    }
}</code></span>

看到上面的AutoSqlInjector#injectSql这个方法,你会发觉到就和BaseMapper中一样了。这里就是将那些方法解析生成并注入。下面将以AutoSqlInjector#injectSelectCountSql为例,看看他到底咋个搞得。

<span style="color:#333333"><code><span style="color:#0000ff">protected</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">injectSelectCountSql</span>(Class<?> mapperClass, Class<?> modelClass, TableInfo table) {
    <span style="color:green">// 从枚举中获取到sqlMethod</span>
        SqlMethod sqlMethod = SqlMethod.SELECT_COUNT;
    <span style="color:green">// 将sqlMethod.getSql() 格式化</span>
        String sql = String.format(sqlMethod.getSql(), table.getTableName(), sqlWhereEntityWrapper(table));
    <span style="color:green">// 得到SqlSource</span>
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
    <span style="color:green">// 注入</span>
        <span style="color:#0000ff">this</span>.addSelectMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource, Integer.class, <span style="color:#0000ff">null</span>);
    }

<span style="color:green">// 这个方法将是根据实体类,构造一堆条件。构造出来得条件,后续执行我们得sql后会根据OGNL,也将会通过反射机制调用我们得get方法,惨了,所以最上面我们出现得NPE就问题来了。为null当然会NPE出现了。</span>
<span style="color:#0000ff">protected</span> String <span style="color:#a31515">sqlWhereEntityWrapper</span>(TableInfo table) {
        StringBuilder where = <span style="color:#0000ff">new</span> StringBuilder(128);
        where.append(<span style="color:#a31515">"\n<where>"</span>);
        where.append(<span style="color:#a31515">"\n<if test=\"ew!=null\">"</span>);
        where.append(<span style="color:#a31515">"\n<if test=\"ew.entity!=null\">"</span>);
        <span style="color:#0000ff">if</span> (StringUtils.isNotEmpty(table.getKeyProperty())) {
            where.append(<span style="color:#a31515">"\n<if test=\"ew.entity."</span>).append(table.getKeyProperty()).append(<span style="color:#a31515">"!=null\">\n"</span>);
            where.append(table.getKeyColumn()).append(<span style="color:#a31515">"=#{ew.entity."</span>).append(table.getKeyProperty()).append(<span style="color:#a31515">"}"</span>);
            where.append(<span style="color:#a31515">"\n</if>"</span>);
        }
        List<TableFieldInfo> fieldList = table.getFieldList();
        <span style="color:#0000ff">for</span> (TableFieldInfo fieldInfo : fieldList) {
            where.append(convertIfTag(fieldInfo, <span style="color:#a31515">"ew.entity."</span>, <span style="color:#0000ff">false</span>));
            where.append(<span style="color:#a31515">" AND "</span>).append(<span style="color:#0000ff">this</span>.sqlCondition(fieldInfo.getCondition(),
                fieldInfo.getColumn(), <span style="color:#a31515">"ew.entity."</span> + fieldInfo.getEl()));
            where.append(convertIfTag(fieldInfo, <span style="color:#0000ff">true</span>));
        }
        where.append(<span style="color:#a31515">"\n</if>"</span>);
        where.append(<span style="color:#a31515">"\n<if test=\"ew!=null and ew.sqlSegment!=null and ew.notEmptyOfWhere\">\n${ew.sqlSegment}\n</if>"</span>);
        where.append(<span style="color:#a31515">"\n</if>"</span>);
        where.append(<span style="color:#a31515">"\n</where>"</span>);
        where.append(<span style="color:#a31515">"\n<if test=\"ew!=null and ew.sqlSegment!=null and ew.emptyOfWhere\">\n${ew.sqlSegment}\n</if>"</span>);
        <span style="color:#0000ff">return</span> where.toString();
    }</code></span>

生成的sql

selectCount生成出来的SQL如下

<span style="color:#333333"><code>SELECT COUNT(1) FROM activity 
<span style="color:#0000ff"><<span style="color:#0000ff">where</span>></span>
<span style="color:#0000ff"><<span style="color:#0000ff">if</span> <span style="color:red">test</span>=<span style="color:#a31515">"ew!=null"</span>></span>
<span style="color:#0000ff"><<span style="color:#0000ff">if</span> <span style="color:red">test</span>=<span style="color:#a31515">"ew.entity!=null"</span>></span>
<span style="color:#0000ff"><<span style="color:#0000ff">if</span> <span style="color:red">test</span>=<span style="color:#a31515">"ew.entity.id!=null"</span>></span>
id=#{ew.entity.id}
<span style="color:#0000ff"></<span style="color:#0000ff">if</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">if</span> <span style="color:red">test</span>=<span style="color:#a31515">"ew.entity.createTime!=null"</span>></span> AND create_time=#{ew.entity.createTime}<span style="color:#0000ff"></<span style="color:#0000ff">if</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">if</span> <span style="color:red">test</span>=<span style="color:#a31515">"ew.entity.editTime!=null"</span>></span> AND edit_time=#{ew.entity.editTime}<span style="color:#0000ff"></<span style="color:#0000ff">if</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">if</span> <span style="color:red">test</span>=<span style="color:#a31515">"ew.entity.isDelete!=null"</span>></span> AND is_delete=#{ew.entity.isDelete}<span style="color:#0000ff"></<span style="color:#0000ff">if</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">if</span> <span style="color:red">test</span>=<span style="color:#a31515">"ew.entity.keyCode!=null"</span>></span> AND key_code=#{ew.entity.keyCode}<span style="color:#0000ff"></<span style="color:#0000ff">if</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">if</span> <span style="color:red">test</span>=<span style="color:#a31515">"ew.entity.gasStationId!=null"</span>></span> AND gas_station_id=#{ew.entity.gasStationId}<span style="color:#0000ff"></<span style="color:#0000ff">if</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">if</span> <span style="color:red">test</span>=<span style="color:#a31515">"ew.entity.gasStationName!=null"</span>></span> AND gas_station_name=#{ew.entity.gasStationName}<span style="color:#0000ff"></<span style="color:#0000ff">if</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">if</span> <span style="color:red">test</span>=<span style="color:#a31515">"ew.entity.startTime!=null"</span>></span> AND start_time=#{ew.entity.startTime}<span style="color:#0000ff"></<span style="color:#0000ff">if</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">if</span> <span style="color:red">test</span>=<span style="color:#a31515">"ew.entity.endTime!=null"</span>></span> AND end_time=#{ew.entity.endTime}<span style="color:#0000ff"></<span style="color:#0000ff">if</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">if</span> <span style="color:red">test</span>=<span style="color:#a31515">"ew.entity.processor!=null"</span>></span> AND processor=#{ew.entity.processor}<span style="color:#0000ff"></<span style="color:#0000ff">if</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">if</span> <span style="color:red">test</span>=<span style="color:#a31515">"ew.entity.processorParams!=null"</span>></span> AND processor_params=#{ew.entity.processorParams}<span style="color:#0000ff"></<span style="color:#0000ff">if</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">if</span> <span style="color:red">test</span>=<span style="color:#a31515">"ew.entity.bizType!=null"</span>></span> AND biz_type=#{ew.entity.bizType}<span style="color:#0000ff"></<span style="color:#0000ff">if</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">if</span> <span style="color:red">test</span>=<span style="color:#a31515">"ew.entity.remainingJoinTimes!=null"</span>></span> AND remaining_join_times=#{ew.entity.remainingJoinTimes}<span style="color:#0000ff"></<span style="color:#0000ff">if</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">if</span> <span style="color:red">test</span>=<span style="color:#a31515">"ew.entity.optUserId!=null"</span>></span> AND opt_user_id=#{ew.entity.optUserId}<span style="color:#0000ff"></<span style="color:#0000ff">if</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">if</span> <span style="color:red">test</span>=<span style="color:#a31515">"ew.entity.optUserName!=null"</span>></span> AND opt_user_name=#{ew.entity.optUserName}<span style="color:#0000ff"></<span style="color:#0000ff">if</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">if</span> <span style="color:red">test</span>=<span style="color:#a31515">"ew.entity.status!=null"</span>></span> AND status=#{ew.entity.status}<span style="color:#0000ff"></<span style="color:#0000ff">if</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">if</span> <span style="color:red">test</span>=<span style="color:#a31515">"ew.entity.extra!=null"</span>></span> AND extra=#{ew.entity.extra}<span style="color:#0000ff"></<span style="color:#0000ff">if</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">if</span> <span style="color:red">test</span>=<span style="color:#a31515">"ew.entity.createSource!=null"</span>></span> AND create_source=#{ew.entity.createSource}<span style="color:#0000ff"></<span style="color:#0000ff">if</span>></span>
<span style="color:#0000ff"></<span style="color:#0000ff">if</span>></span>
<span style="color:#0000ff"><<span style="color:#0000ff">if</span> <span style="color:red">test</span>=<span style="color:#a31515">"ew!=null and ew.sqlSegment!=null and ew.notEmptyOfWhere"</span>></span>
${ew.sqlSegment}
<span style="color:#0000ff"></<span style="color:#0000ff">if</span>></span>
<span style="color:#0000ff"></<span style="color:#0000ff">if</span>></span>
<span style="color:#0000ff"></<span style="color:#0000ff">where</span>></span>
<span style="color:#0000ff"><<span style="color:#0000ff">if</span> <span style="color:red">test</span>=<span style="color:#a31515">"ew!=null and ew.sqlSegment!=null and ew.emptyOfWhere"</span>></span>
${ew.sqlSegment}
<span style="color:#0000ff"></<span style="color:#0000ff">if</span>></span></code></span>

MapperProxy代理生成

MapperProxy生成的大致类图

BaseMapperX 表关联查询 basemapper的使用_List_04

还记得在上面分析代码的时候,我们BeanDefinition中得beanClass设置为MapperFactoryBean吧,MapperFactoryBean实现FactoryBean。实现FactoryBean好处是什么?我们先看看spring容器refresh的流程

<span style="color:#333333"><code><span style="color:#0000ff">public</span> <span style="color:#0000ff">abstract</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">AbstractApplicationContext</span> <span style="color:#0000ff">extends</span> <span style="color:#a31515">DefaultResourceLoader</span>
        <span style="color:#0000ff">implements</span> <span style="color:#a31515">ConfigurableApplicationContext</span>, <span style="color:#a31515">DisposableBean</span> {
...
<span style="color:green">// 这个就是spring容器启动得核心流程都在这里。</span>
    <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">refresh</span>() <span style="color:#0000ff">throws</span> BeansException, IllegalStateException {
        <span style="color:#0000ff">synchronized</span> (<span style="color:#0000ff">this</span>.startupShutdownMonitor) {
            <span style="color:green">// Prepare this context for refreshing.</span>
            prepareRefresh();
            <span style="color:green">// Tell the subclass to refresh the internal bean factory.</span>
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
            <span style="color:green">// Prepare the bean factory for use in this context.</span>
            prepareBeanFactory(beanFactory);
            <span style="color:#0000ff">try</span> {
                <span style="color:green">// Allows post-processing of the bean factory in context subclasses.</span>
                postProcessBeanFactory(beanFactory);
                <span style="color:green">// Invoke factory processors registered as beans in the context.</span>
                invokeBeanFactoryPostProcessors(beanFactory);
                <span style="color:green">// Register bean processors that intercept bean creation.</span>
                registerBeanPostProcessors(beanFactory);
                <span style="color:green">// Initialize message source for this context.</span>
                initMessageSource();
                <span style="color:green">// Initialize event multicaster for this context.</span>
                initApplicationEventMulticaster();
                <span style="color:green">// Initialize other special beans in specific context subclasses.</span>
                onRefresh();
                <span style="color:green">// Check for listener beans and register them.</span>
                registerListeners();
                <span style="color:green">// Instantiate all remaining (non-lazy-init) singletons.</span>
                finishBeanFactoryInitialization(beanFactory);
                <span style="color:green">// Last step: publish corresponding event.</span>
                finishRefresh();
            }
            <span style="color:#0000ff">catch</span> (BeansException ex) {
                <span style="color:#0000ff">if</span> (logger.isWarnEnabled()) {
                    logger.warn(<span style="color:#a31515">"Exception encountered during context initialization - "</span> +
                            <span style="color:#a31515">"cancelling refresh attempt: "</span> + ex);
                }
                <span style="color:green">// Destroy already created singletons to avoid dangling resources.</span>
                destroyBeans();
                <span style="color:green">// Reset 'active' flag.</span>
                cancelRefresh(ex);
                <span style="color:green">// Propagate exception to caller.</span>
                <span style="color:#0000ff">throw</span> ex;
            }
            <span style="color:#0000ff">finally</span> {
                <span style="color:green">// Reset common introspection caches in Spring's core, since we</span>
                <span style="color:green">// might not ever need metadata for singleton beans anymore...</span>
                resetCommonCaches();
            }
        }
    }
...
}</code></span>

这里我们重点看finishBeanFactoryInitialization(beanFactory),这个方法主要是完成BeanFactory中得非懒加载Bean得初始化工作,在这也将会完成依赖注入的bean,依赖注入的时候,调用AbstractBeanFactory#getBean(String, Class<T>),具体可以详细看看。后续会判断此Bean是否是FactoryBean的类型,如果是将会调用FactoryBean#getObject();那么现在我们再回到MapperFactoryBean#getObject()实现。

MapperFactoryBean#getObject

<span style="color:#333333"><code><span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">MapperFactoryBean</span><<span style="color:#a31515">T</span>> <span style="color:#0000ff">extends</span> <span style="color:#a31515">SqlSessionDaoSupport</span> <span style="color:#0000ff">implements</span> <span style="color:#a31515">FactoryBean</span><<span style="color:#a31515">T</span>> {
    ...
   <span style="color:#0000ff">public</span> T <span style="color:#a31515">getObject</span>() <span style="color:#0000ff">throws</span> Exception {
    <span style="color:green">//这里通过MybatisSqlSessionTemplate去获取我们得Mapper代理。</span>
    <span style="color:#0000ff">return</span> getSqlSession().getMapper(<span style="color:#0000ff">this</span>.mapperInterface);
  }
    ...
}</code></span>

SqlSessionTemplate#getMapper

<span style="color:#333333"><code><span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">SqlSessionTemplate</span> <span style="color:#0000ff">implements</span> <span style="color:#a31515">SqlSession</span>, <span style="color:#a31515">DisposableBean</span> {
    ...
    <span style="color:#2b91af">@Override</span>
    <span style="color:#0000ff">public</span> <T> T <span style="color:#a31515">getMapper</span>(Class<T> type) {
        <span style="color:#0000ff">return</span> getConfiguration().getMapper(type, <span style="color:#0000ff">this</span>);
    }
    <span style="color:#2b91af">@Override</span>
    <span style="color:#0000ff">public</span> Configuration <span style="color:#a31515">getConfiguration</span>() {
        <span style="color:#0000ff">return</span> <span style="color:#0000ff">this</span>.sqlSessionFactory.getConfiguration();
    }
    ...
}</code></span>

MybatisConfiguration#getMapper

<span style="color:#333333"><code><span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">MybatisConfiguration</span> <span style="color:#0000ff">extends</span> <span style="color:#a31515">Configuration</span> {
    <span style="color:#0000ff">public</span> <span style="color:#0000ff">final</span> MybatisMapperRegistry mybatisMapperRegistry = <span style="color:#0000ff">new</span> MybatisMapperRegistry(<span style="color:#0000ff">this</span>);
    ...
    <span style="color:green">//在注册器中获取</span>
    <span style="color:#0000ff">public</span> <T> T <span style="color:#a31515">getMapper</span>(Class<T> type, SqlSession sqlSession) {
        <span style="color:#0000ff">return</span> mybatisMapperRegistry.getMapper(type, sqlSession);
    }
    ...
}</code></span>

MybatisMapperRegistry#getMapper

<span style="color:#333333"><code><span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">MybatisMapperRegistry</span> <span style="color:#0000ff">extends</span> <span style="color:#a31515">MapperRegistry</span> {
    <span style="color:#0000ff">private</span> <span style="color:#0000ff">final</span> Map<Class<?>, MapperProxyFactory<?>> knownMappers = <span style="color:#0000ff">new</span> HashMap<>();
    
    <span style="color:#0000ff">public</span> <T> T <span style="color:#a31515">getMapper</span>(Class<T> type, SqlSession sqlSession) {
        <span style="color:#0000ff">final</span> MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
        <span style="color:#0000ff">if</span> (mapperProxyFactory == <span style="color:#0000ff">null</span>) {
            <span style="color:#0000ff">throw</span> <span style="color:#0000ff">new</span> BindingException(<span style="color:#a31515">"Type "</span> + type + <span style="color:#a31515">" is not known to the MybatisPlusMapperRegistry."</span>);
        }
        <span style="color:#0000ff">try</span> {
            <span style="color:green">// 通过代理工厂再实例化。我们得MapperProxy代理</span>
            <span style="color:#0000ff">return</span> mapperProxyFactory.newInstance(sqlSession);
        } <span style="color:#0000ff">catch</span> (Exception e) {
            <span style="color:#0000ff">throw</span> <span style="color:#0000ff">new</span> BindingException(<span style="color:#a31515">"Error getting mapper instance. Cause: "</span> + e, e);
        }
    }
}</code></span>

MapperProxyFactory#newInstance

MapperProxyFactory是我们常说的工厂设计模式,为我们Mapper生成MapperProxy代理。

<span style="color:#333333"><code><span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">MapperProxyFactory</span><<span style="color:#a31515">T</span>> {

  <span style="color:#0000ff">private</span> <span style="color:#0000ff">final</span> Class<T> mapperInterface;
  <span style="color:green">// 感觉这里写的不好。。。这个可以直接写道MapperProxy里啊,为嘛在这里初始化后做一个参数来传递?难道为了扩展???有什么扩展需要放到这里</span>
  <span style="color:#0000ff">private</span> <span style="color:#0000ff">final</span> Map<Method, MapperMethod> methodCache = <span style="color:#0000ff">new</span> ConcurrentHashMap<Method, MapperMethod>();

  <span style="color:#0000ff">public</span> <span style="color:#a31515">MapperProxyFactory</span>(Class<T> mapperInterface) {
    <span style="color:#0000ff">this</span>.mapperInterface = mapperInterface;
  }

  <span style="color:#0000ff">public</span> Class<T> <span style="color:#a31515">getMapperInterface</span>() {
    <span style="color:#0000ff">return</span> mapperInterface;
  }

  <span style="color:#0000ff">public</span> Map<Method, MapperMethod> <span style="color:#a31515">getMethodCache</span>() {
    <span style="color:#0000ff">return</span> methodCache;
  }

  <span style="color:#2b91af">@SuppressWarnings</span>(<span style="color:#a31515">"unchecked"</span>)
  <span style="color:#0000ff">protected</span> T <span style="color:#a31515">newInstance</span>(MapperProxy<T> mapperProxy) {
    <span style="color:#0000ff">return</span> (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), <span style="color:#0000ff">new</span> Class[] { mapperInterface }, mapperProxy);
  }

  <span style="color:#0000ff">public</span> T <span style="color:#a31515">newInstance</span>(SqlSession sqlSession) {
    <span style="color:#0000ff">final</span> MapperProxy<T> mapperProxy = <span style="color:#0000ff">new</span> MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    <span style="color:#0000ff">return</span> newInstance(mapperProxy);
  }

}</code></span>

MapperProxy

将会给我们每个mapper生成一个代理

<span style="color:#333333"><code><span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">MapperProxy</span><<span style="color:#a31515">T</span>> <span style="color:#0000ff">implements</span> <span style="color:#a31515">InvocationHandler</span>, <span style="color:#a31515">Serializable</span> {

  <span style="color:#0000ff">private</span> <span style="color:#0000ff">static</span> <span style="color:#0000ff">final</span> <span style="color:#0000ff">long</span> serialVersionUID = -6424540398559729838L;
  <span style="color:#0000ff">private</span> <span style="color:#0000ff">final</span> SqlSession sqlSession;
  <span style="color:#0000ff">private</span> <span style="color:#0000ff">final</span> Class<T> mapperInterface;
  <span style="color:#0000ff">private</span> <span style="color:#0000ff">final</span> Map<Method, MapperMethod> methodCache;

  <span style="color:#0000ff">public</span> <span style="color:#a31515">MapperProxy</span>(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    <span style="color:#0000ff">this</span>.sqlSession = sqlSession;
    <span style="color:#0000ff">this</span>.mapperInterface = mapperInterface;
    <span style="color:#0000ff">this</span>.methodCache = methodCache;
  }

  <span style="color:#2b91af">@Override</span>
  <span style="color:#0000ff">public</span> Object <span style="color:#a31515">invoke</span>(Object proxy, Method method, Object[] args) <span style="color:#0000ff">throws</span> Throwable {
    <span style="color:#0000ff">try</span> {
      <span style="color:#0000ff">if</span> (Object.class.equals(method.getDeclaringClass())) {
        <span style="color:#0000ff">return</span> method.invoke(<span style="color:#0000ff">this</span>, args);
      } <span style="color:#0000ff">else</span> <span style="color:#0000ff">if</span> (isDefaultMethod(method)) {
        <span style="color:#0000ff">return</span> invokeDefaultMethod(proxy, method, args);
      }
    } <span style="color:#0000ff">catch</span> (Throwable t) {
      <span style="color:#0000ff">throw</span> ExceptionUtil.unwrapThrowable(t);
    }
    <span style="color:green">// 如果MapperMethod已经存在,放入缓存,否则初始化</span>
    <span style="color:#0000ff">final</span> MapperMethod mapperMethod = cachedMapperMethod(method);
    <span style="color:#0000ff">return</span> mapperMethod.execute(sqlSession, args);
  }

  <span style="color:#0000ff">private</span> MapperMethod <span style="color:#a31515">cachedMapperMethod</span>(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    <span style="color:#0000ff">if</span> (mapperMethod == <span style="color:#0000ff">null</span>) {
      mapperMethod = <span style="color:#0000ff">new</span> MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    <span style="color:#0000ff">return</span> mapperMethod;
  }

  <span style="color:#2b91af">@UsesJava</span>7
  <span style="color:#0000ff">private</span> Object <span style="color:#a31515">invokeDefaultMethod</span>(Object proxy, Method method, Object[] args)
      <span style="color:#0000ff">throws</span> Throwable {
    <span style="color:#0000ff">final</span> Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
        .getDeclaredConstructor(Class.class, <span style="color:#0000ff">int</span>.class);
    <span style="color:#0000ff">if</span> (!constructor.isAccessible()) {
      constructor.setAccessible(<span style="color:#0000ff">true</span>);
    }
    <span style="color:#0000ff">final</span> Class<?> declaringClass = method.getDeclaringClass();
    <span style="color:#0000ff">return</span> constructor
        .newInstance(declaringClass,
            MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
                | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
        .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
  }

  <span style="color:green">/**
   * Backport of java.lang.reflect.Method#isDefault()
   */</span>
  <span style="color:#0000ff">private</span> <span style="color:#0000ff">boolean</span> <span style="color:#a31515">isDefaultMethod</span>(Method method) {
    <span style="color:#0000ff">return</span> (method.getModifiers()
        & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC
        && method.getDeclaringClass().isInterface();
  }
}</code></span>

生成的MapperProxy代理后,将会注入到依赖此Bean的Service中。

后续CRUD的时候,会调用MapperProxy#invokeMapperMethod初始化的时候会初始化MethodSignature,MethodSignature类意思就是方法签名,将会对paramNameResolver(参数处理器),returnType(返回类型),ResultHandler(结果处理器)的处理等。

总结

跟着源码看下,学习到东西还是很多得。

  • 设计模式:代理、工厂、模板、委派等
  • spring容器初始化流程
  • spring中很多扩展点等等

一个很简单问题,解决是解决了,但并不代表你从中学到了什么。根据通过上面其实我们还可以总结一些写插件的结论

  • BeanDefinition类型设置为实现了FactoryBean的一些类,比如这里的MapperFactoryBean,FeignClientFactoryBean(这里提出来是为了说明spring-cloud-openfeign也是基于这样的思路搞得)
  • 实现FactoryBean得好处:在依赖bean得地方将会叫用getObject,这里要做的文章就多了。Spring源码中有很多实现FactoryBean得类
  • 接口注入,比如这里得我们写的XXXXDao,这种BaseMapper得注入,这种一般都采用了代理模式,spring-cloud-openfeign那些接口也是一样。所以才能像正常调用一样。