Mybatis-Plus BaseMapper自动生成SQL及MapperProxy

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

问题产生背景

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

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

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("coupon_record")
public class CouponRecord {
    ...
    @TableField(value = "product_quantity")
    private BigDecimal productQuantity;
    public BigDecimal getProductQuantity() {
        // 提交上的代码
        return this.productQuantity.setScale(2, RoundingMode.HALF_DOWN);
        // 解决方式如下
        //return this.productQuantity == null ? null : this.productQuantity.setScale(2, RoundingMode.HALF_DOWN);
    }
    ...
}

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

ServiceImpl的部分代码如下:

public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {
    @Autowired
    protected M baseMapper;
    ...
    @Override
    public int selectCount(Wrapper<T> wrapper) {
        return SqlHelper.retCount(baseMapper.selectCount(wrapper));
    }
    ...
}

BaseMapper所有接口如下:

public interface BaseMapper<T> {
    Integer insert(T entity);
    Integer insertAllColumn(T entity);
    Integer deleteById(Serializable id);
    Integer deleteByMap(@Param("cm") Map<String, Object> columnMap);
    Integer delete(@Param("ew") Wrapper<T> wrapper);
    Integer deleteBatchIds(@Param("coll") Collection<? extends Serializable> idList);
    Integer updateById(@Param("et") T entity);
    Integer updateAllColumnById(@Param("et") T entity);
    Integer update(@Param("et") T entity, @Param("ew") Wrapper<T> wrapper);
    T selectById(Serializable id);
    List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);
    List<T> selectByMap(@Param("cm") Map<String, Object> columnMap);
    T selectOne(@Param("ew") T entity);
    Integer selectCount(@Param("ew") Wrapper<T> wrapper);
    List<T> selectList(@Param("ew") Wrapper<T> wrapper);
    List<Map<String, Object>> selectMaps(@Param("ew") Wrapper<T> wrapper);
    List<Object> selectObjs(@Param("ew") Wrapper<T> wrapper);
    List<T> selectPage(RowBounds rowBounds, @Param("ew") Wrapper<T> wrapper);
    List<Map<String, Object>> selectMapsPage(RowBounds rowBounds, @Param("ew") Wrapper<T> wrapper);
}

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

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

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

框架是如何使用

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

Service 主要代码如下:

@Service
public class CouponRecordService extends ServiceImpl<CouponRecordDao, CouponRecord> {
    public int count(Date endTime) {
        CouponRecord conditionCouponRecord = CouponRecord.builder().status(CouponStatus.USED).isDelete(YesNo.NO.getValue()).build();
        return selectCount(new EntityWrapper<>(conditionCouponRecord).le("create_time", endTime).isNotNull("order_no"));
    }
}

Dao(或者叫 Mapper)

public interface CouponRecordDao extends BaseMapper<CouponRecord> {
}

spring 的相关配置如下:

<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
<span class="hljs-comment">&lt;!-- 自动扫描entity目录, 省掉Configuration.xml里的手工配置 --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"configLocation"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"classpath:mybatis-config.xml"</span>/&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"mapperLocations"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"classpath*:mapper/**/*.xml"</span>/&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"plugins"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">array</span>&gt;</span>
        <span class="hljs-comment">&lt;!-- 分页插件配置 --&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">bean</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"paginationInterceptor"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"com.baomidou.mybatisplus.plugins.PaginationInterceptor"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"dialectType"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"mysql"</span>/&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">bean</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">bean</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"limitInterceptor"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"com.common.mybatis.LimitInterceptor"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">array</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">property</span>&gt;</span>

</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.merchant.activity..dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
<context:component-scan base-package="com.common.
,com.merchant.activity.**">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

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

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

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

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.merchant.activity.**.dao"/>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>

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

大概类图如下:

接下来对源码做分析

BeanDefinition 解析阶段

MapperScannerConfigurer

MapperScannerConfigurer 得继承关系如下图:

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

ClassPathMapperScanner 构造

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

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
  ...
  @Override
  public void afterPropertiesSet() throws Exception {
      // 验证是否配置了 basePackage
    notNull(this.basePackage, "Property'basePackage'is required");
  }

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// left intentionally blank
}

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

ClassPathMapperScanner#scan

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

public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
    ...
    public int scan(String... basePackages) {
        // 获取之前容器中 bean 的数量
		int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
		// 真正干事的 --- 扫描, 调用子类 ClassPathMapperScanner#doScan(basePackages) 方法
		doScan(basePackages);
		// Register annotation config processors, if necessary.
		if (this.includeAnnotationConfig) {
			AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
		}
		// 返回注册 bean 的数量
		return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
	}
    // 真正干事的扫描 生成 BeanDefinition 集合
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
        // BeanDefinitionHolder 的集合
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
		for (String basePackage : basePackages) {
            // 通过查找候选 bean 定义
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
            // 遍历进行部分逻辑处理
			for (BeanDefinition candidate : candidates) {
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
                // 设置作用域
				candidate.setScope(scopeMetadata.getScopeName());
                // 生成 beanName
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
				if (candidate instanceof AbstractBeanDefinition) {
                    // 增加默认值,autowireCandidate
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				if (candidate instanceof AnnotatedBeanDefinition) {
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
                // 注册 BeanDefinition 到容器中。
				if (checkCandidate(beanName, candidate)) {
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					definitionHolder =
							AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}
    ...
}
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
      // 调用父类的 ClassPathBeanDefinitionScanner#doScaner(basePackages) 方法,扫描生产 BeanDefinitionHolder 集合
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
<span class="hljs-keyword">if</span> (beanDefinitions.isEmpty()) {
  logger.warn(<span class="hljs-string">"No MyBatis mapper was found in '"</span> + Arrays.toString(basePackages) + <span class="hljs-string">"' package. Please check your configuration."</span>);
} <span class="hljs-keyword">else</span> {
  <span class="hljs-comment">// MapperBean 需要一些额外的处理,查看这个方法</span>
  processBeanDefinitions(beanDefinitions);
}

<span class="hljs-keyword">return</span> beanDefinitions;

}

<span class="hljs-comment">//对每个Mapper的BeanDefinition定义处理, </span>

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
// 构造器参数,下一行代码将 Bean 设置为 MapperFactoryBean,MapperFactoryBean 的构造器中有个参数是 mapperInterface
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
// 这一步非常重要,把我们的 Bean 设置为 MapperFactoryBean, 接下来会看到 MapperFactoryBean 的继承关系
definition.setBeanClass(this.mapperFactoryBean.getClass());

  definition.getPropertyValues().add(<span class="hljs-string">"addToConfig"</span>, <span class="hljs-built_in">this</span>.addToConfig);

  <span class="hljs-type">boolean</span> <span class="hljs-variable">explicitFactoryUsed</span> <span class="hljs-operator">=</span> <span class="hljs-literal">false</span>;
  <span class="hljs-comment">// 在bean中增加sqlSessionFactory</span>
  <span class="hljs-keyword">if</span> (StringUtils.hasText(<span class="hljs-built_in">this</span>.sqlSessionFactoryBeanName)) {
    definition.getPropertyValues().add(<span class="hljs-string">"sqlSessionFactory"</span>, <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeBeanReference</span>(<span class="hljs-built_in">this</span>.sqlSessionFactoryBeanName));
    explicitFactoryUsed = <span class="hljs-literal">true</span>;
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.sqlSessionFactory != <span class="hljs-literal">null</span>) {
    definition.getPropertyValues().add(<span class="hljs-string">"sqlSessionFactory"</span>, <span class="hljs-built_in">this</span>.sqlSessionFactory);
    explicitFactoryUsed = <span class="hljs-literal">true</span>;
  }
  <span class="hljs-comment">// 在bean中增加sqlSessionTemplate</span>
  <span class="hljs-keyword">if</span> (StringUtils.hasText(<span class="hljs-built_in">this</span>.sqlSessionTemplateBeanName)) {
    definition.getPropertyValues().add(<span class="hljs-string">"sqlSessionTemplate"</span>, <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeBeanReference</span>(<span class="hljs-built_in">this</span>.sqlSessionTemplateBeanName));
    explicitFactoryUsed = <span class="hljs-literal">true</span>;
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.sqlSessionTemplate != <span class="hljs-literal">null</span>) {
    definition.getPropertyValues().add(<span class="hljs-string">"sqlSessionTemplate"</span>, <span class="hljs-built_in">this</span>.sqlSessionTemplate);
    explicitFactoryUsed = <span class="hljs-literal">true</span>;
  }
  <span class="hljs-comment">// 设置自动注入模式</span>
  <span class="hljs-keyword">if</span> (!explicitFactoryUsed) {
    definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
  }
}

}
}

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

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

BeanDefinition 初始化阶段

MapperFactoryBean

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

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

public abstract class DaoSupport implements InitializingBean {
	protected final Log logger = LogFactory.getLog(getClass());
	@Override
	public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
		// 检查 Dao 配置
		checkDaoConfig();
		// Let concrete implementations initialize themselves.
		try {
			initDao();
		}
		catch (Exception ex) {
			throw new BeanInitializationException("Initialization of DAO failed", ex);
		}
	}
	protected abstract void checkDaoConfig() throws IllegalArgumentException;
	protected void initDao() throws Exception {
	}
}

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

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
  ...
  protected void checkDaoConfig() {
    super.checkDaoConfig();// 调用父类的方法,父类就是检查 sqlSession 是否为 null。null 的话抛出异常
    notNull(this.mapperInterface, "Property'mapperInterface'is required");
    // 通过 sqlSession 获取 MybatisConfiguration,相当于我们每一个 MapperBean 都是由 SqlSession 的,否则你想咋个查询呢
    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        // 将 mapperInterface 注册到 configuration 中。
        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();
      }
    }
  }
  ...
}

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

public class MybatisConfiguration extends Configuration {
    ...
    public final MybatisMapperRegistry mybatisMapperRegistry = new MybatisMapperRegistry(this);
    public <T> void addMapper(Class<T> type) {
        mybatisMapperRegistry.addMapper(type);
    }
    ...
}

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

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

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

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

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

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

public class AutoSqlInjector implements ISqlInjector {
    // 注入到 builderAssistant
     public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
        String className = mapperClass.toString();
        Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration());
         // 判断之前是否注入过
        if (!mapperRegistryCache.contains(className)) {
            // 注入
            inject(builderAssistant, mapperClass);
            // 加入到缓存中
            mapperRegistryCache.add(className);
        }
    }
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">inject</span><span class="hljs-params">(MapperBuilderAssistant builderAssistant, Class&lt;?&gt; mapperClass)</span> {
    <span class="hljs-built_in">this</span>.configuration = builderAssistant.getConfiguration();
    <span class="hljs-built_in">this</span>.builderAssistant = builderAssistant;
    <span class="hljs-built_in">this</span>.languageDriver = configuration.getDefaultScriptingLanguageInstance();
    <span class="hljs-comment">/**
     * 驼峰设置 PLUS 配置 &gt; 原始配置
	 */</span>
    <span class="hljs-type">GlobalConfiguration</span> <span class="hljs-variable">globalCache</span> <span class="hljs-operator">=</span> <span class="hljs-built_in">this</span>.getGlobalConfig();
    <span class="hljs-keyword">if</span> (!globalCache.isDbColumnUnderline()) {
        globalCache.setDbColumnUnderline(configuration.isMapUnderscoreToCamelCase());
    }
    Class&lt;?&gt; modelClass = extractModelClass(mapperClass);
    <span class="hljs-keyword">if</span> (<span class="hljs-literal">null</span> != modelClass) {
        <span class="hljs-comment">/**
         * 初始化 SQL 解析
         */</span>
        <span class="hljs-keyword">if</span> (globalCache.isSqlParserCache()) {
            PluginUtils.initSqlParserInfoCache(mapperClass);
        }
        <span class="hljs-comment">// 这里获取tableInfo. 这里你会看到我们@TableName了。。</span>
        <span class="hljs-type">TableInfo</span> <span class="hljs-variable">table</span> <span class="hljs-operator">=</span> TableInfoHelper.initTableInfo(builderAssistant, modelClass);
        <span class="hljs-comment">//生成sql注入sql</span>
        injectSql(builderAssistant, mapperClass, modelClass, table);
    }
}

<span class="hljs-comment">// 看到这个方法里面的injectXXXX是不是和我们BaseMapper里的一样呢。对这里挨着一个个的去实现。</span>
<span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">injectSql</span><span class="hljs-params">(MapperBuilderAssistant builderAssistant, Class&lt;?&gt; mapperClass, Class&lt;?&gt; modelClass, TableInfo table)</span> {
    <span class="hljs-comment">/**
     * #148 表信息包含主键,注入主键相关方法
     */</span>
    <span class="hljs-keyword">if</span> (StringUtils.isNotEmpty(table.getKeyProperty())) {
        <span class="hljs-comment">/** 删除 */</span>
        <span class="hljs-built_in">this</span>.injectDeleteByIdSql(<span class="hljs-literal">false</span>, mapperClass, modelClass, table);
        <span class="hljs-built_in">this</span>.injectDeleteByIdSql(<span class="hljs-literal">true</span>, mapperClass, modelClass, table);
        <span class="hljs-comment">/** 修改 */</span>
        <span class="hljs-built_in">this</span>.injectUpdateByIdSql(<span class="hljs-literal">true</span>, mapperClass, modelClass, table);
        <span class="hljs-built_in">this</span>.injectUpdateByIdSql(<span class="hljs-literal">false</span>, mapperClass, modelClass, table);
        <span class="hljs-comment">/** 查询 */</span>
        <span class="hljs-built_in">this</span>.injectSelectByIdSql(<span class="hljs-literal">false</span>, mapperClass, modelClass, table);
        <span class="hljs-built_in">this</span>.injectSelectByIdSql(<span class="hljs-literal">true</span>, mapperClass, modelClass, table);
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-comment">// 表不包含主键时 给予警告</span>
        logger.warn(String.format(<span class="hljs-string">"%s ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method."</span>,
                modelClass.toString()));
    }
    <span class="hljs-comment">/**
     * 正常注入无需主键方法
     */</span>
    <span class="hljs-comment">/** 插入 */</span>
    <span class="hljs-built_in">this</span>.injectInsertOneSql(<span class="hljs-literal">true</span>, mapperClass, modelClass, table);
    <span class="hljs-built_in">this</span>.injectInsertOneSql(<span class="hljs-literal">false</span>, mapperClass, modelClass, table);
    <span class="hljs-comment">/** 删除 */</span>
    <span class="hljs-built_in">this</span>.injectDeleteSql(mapperClass, modelClass, table);
    <span class="hljs-built_in">this</span>.injectDeleteByMapSql(mapperClass, table);
    <span class="hljs-comment">/** 修改 */</span>
    <span class="hljs-built_in">this</span>.injectUpdateSql(mapperClass, modelClass, table);
    <span class="hljs-comment">/** 查询 */</span>
    <span class="hljs-built_in">this</span>.injectSelectByMapSql(mapperClass, modelClass, table);
    <span class="hljs-built_in">this</span>.injectSelectOneSql(mapperClass, modelClass, table);
    <span class="hljs-built_in">this</span>.injectSelectCountSql(mapperClass, modelClass, table);
    <span class="hljs-built_in">this</span>.injectSelectListSql(SqlMethod.SELECT_LIST, mapperClass, modelClass, table);
    <span class="hljs-built_in">this</span>.injectSelectListSql(SqlMethod.SELECT_PAGE, mapperClass, modelClass, table);
    <span class="hljs-built_in">this</span>.injectSelectMapsSql(SqlMethod.SELECT_MAPS, mapperClass, modelClass, table);
    <span class="hljs-built_in">this</span>.injectSelectMapsSql(SqlMethod.SELECT_MAPS_PAGE, mapperClass, modelClass, table);
    <span class="hljs-built_in">this</span>.injectSelectObjsSql(SqlMethod.SELECT_OBJS, mapperClass, modelClass, table);
    <span class="hljs-comment">/** 自定义方法 */</span>
    <span class="hljs-built_in">this</span>.inject(configuration, builderAssistant, mapperClass, modelClass, table);
}

}

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

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

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

生成的 sql

selectCount 生成出来的 SQL 如下

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

MapperProxy 代理生成

MapperProxy 生成的大致类图

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

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

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

MapperFactoryBean#getObject

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
    ...
   public T getObject() throws Exception {
    // 这里通过 MybatisSqlSessionTemplate 去获取我们得 Mapper 代理。
    return getSqlSession().getMapper(this.mapperInterface);
  }
    ...
}

SqlSessionTemplate#getMapper

public class SqlSessionTemplate implements SqlSession, DisposableBean {
    ...
    @Override
    public <T> T getMapper(Class<T> type) {
        return getConfiguration().getMapper(type, this);
    }
    @Override
    public Configuration getConfiguration() {
        return this.sqlSessionFactory.getConfiguration();
    }
    ...
}

MybatisConfiguration#getMapper

public class MybatisConfiguration extends Configuration {
    public final MybatisMapperRegistry mybatisMapperRegistry = new MybatisMapperRegistry(this);
    ...
    // 在注册器中获取
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return mybatisMapperRegistry.getMapper(type, sqlSession);
    }
    ...
}

MybatisMapperRegistry#getMapper

public class MybatisMapperRegistry extends MapperRegistry {
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
<span class="hljs-keyword">public</span> &lt;T&gt; T <span class="hljs-title function_">getMapper</span><span class="hljs-params">(Class&lt;T&gt; type, SqlSession sqlSession)</span> {
    <span class="hljs-keyword">final</span> MapperProxyFactory&lt;T&gt; mapperProxyFactory = (MapperProxyFactory&lt;T&gt;) knownMappers.get(type);
    <span class="hljs-keyword">if</span> (mapperProxyFactory == <span class="hljs-literal">null</span>) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">BindingException</span>(<span class="hljs-string">"Type "</span> + type + <span class="hljs-string">" is not known to the MybatisPlusMapperRegistry."</span>);
    }
    <span class="hljs-keyword">try</span> {
        <span class="hljs-comment">// 通过代理工厂再实例化。我们得MapperProxy代理</span>
        <span class="hljs-keyword">return</span> mapperProxyFactory.newInstance(sqlSession);
    } <span class="hljs-keyword">catch</span> (Exception e) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">BindingException</span>(<span class="hljs-string">"Error getting mapper instance. Cause: "</span> + e, e);
    }
}

}

MapperProxyFactory#newInstance

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

public class MapperProxyFactory<T> {

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

public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}

public Class<T> getMapperInterface() {
return mapperInterface;
}

public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
}

@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
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);
}

}

MapperProxy

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

public class MapperProxy<T> implements InvocationHandler, Serializable {

private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;

public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}

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

private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}

@UsesJava7
private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
throws Throwable {
final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
.getDeclaredConstructor(Class.class, int.class);
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
final Class<?> declaringClass = method.getDeclaringClass();
return constructor
.newInstance(declaringClass,
MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
.unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
}

/**

  • Backport of java.lang.reflect.Method#isDefault()
    */
    private boolean isDefaultMethod(Method method) {
    return (method.getModifiers()
    & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC
    && method.getDeclaringClass().isInterface();
    }
    }

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

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

  • paramNameResolver 处理器,可以参看俊良的mybatis 参数
  • ResultHandler 这个用法,可以参看我在mybatis 参数文章中的评论,

总结

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

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

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

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