目录
- Spring+Mybatis + Mybatis-Plus 自定义无XML的sql生成及MapperProxy代理生成
- 问题产生背景
- 框架是如何使用
- 无Xml的SQL是如何生成生成及SQL长成什么样
- MapperProxy代理生成
- 总结
Spring+Mybatis + Mybatis-Plus 自定义无XML的sql生成及MapperProxy代理生成
问题产生背景
现在新服务ORM框架是使用mybatis3.4.6
、mybatis-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的入口。
大概类图如下:
接下来对源码做分析
BeanDefinition解析阶段
MapperScannerConfigurer
MapperScannerConfigurer得继承关系如下图:
从图中看出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>
写到代码的注释可能都不怎么关注,这里再次强调下重点,如果不注意后续可能有些会懵逼的。这是怎么来的。
- BeanDefinition的class设置为MapperFactoryBean
- 将原始mapper的接口类型以MapperFactoryBean构造器的参数传入,也就是后面你将看到参数是mapperInterface.
BeanDefinition初始化阶段
MapperFactoryBean
经过上面的扫描并注册,现在容器中已经存在了我们的Mapper Bean了,在上面的说构建Mapper BeanDefinition的时候注意这些BeanDefinition的class类型设置为了MapperFactoryBean,先看看MapperFactoryBean的继承关系如下:
从图中,看出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 类的名字起的真好,看名字就是一个注册器。这里的注册器有一箭双雕的作用
- 定义了一个Map,缓存所知道的Mapper,后面初始化MapperProxy代理用的着,不然后面不好取哦
- 将解析出来的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生成的大致类图
还记得在上面分析代码的时候,我们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#invoke
,MapperMethod
初始化的时候会初始化MethodSignature
,MethodSignature类意思就是方法签名,将会对paramNameResolver(参数处理器),returnType(返回类型),ResultHandler(结果处理器)的处理等。
总结
跟着源码看下,学习到东西还是很多得。
- 设计模式:代理、工厂、模板、委派等
- spring容器初始化流程
- spring中很多扩展点等等
一个很简单问题,解决是解决了,但并不代表你从中学到了什么。根据通过上面其实我们还可以总结一些写插件的结论
- BeanDefinition类型设置为实现了FactoryBean的一些类,比如这里的MapperFactoryBean,FeignClientFactoryBean(这里提出来是为了说明spring-cloud-openfeign也是基于这样的思路搞得)
- 实现FactoryBean得好处:在依赖bean得地方将会叫用getObject,这里要做的文章就多了。Spring源码中有很多实现FactoryBean得类
- 接口注入,比如这里得我们写的XXXXDao,这种BaseMapper得注入,这种一般都采用了代理模式,spring-cloud-openfeign那些接口也是一样。所以才能像正常调用一样。