Mybatis-Plus BaseMapper自动生成SQL及MapperProxy
Posted lanweijava
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mybatis-Plus BaseMapper自动生成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了。
@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"/>
<!-- 自动扫描entity目录, 省掉Configuration.xml里的手工配置 -->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath*:mapper/**/*.xml"/>
<property name="plugins">
<array>
<!-- 分页插件配置 -->
<bean id="paginationInterceptor" class="com.baomidou.mybatisplus.plugins.PaginationInterceptor">
<property name="dialectType" value="mysql"/>
</bean>
<bean id="limitInterceptor" class="com.common.mybatis.LimitInterceptor"/>
</array>
</property>
</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);
if (beanDefinitions.isEmpty()) {
logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
// MapperBean 需要一些额外的处理,查看这个方法
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
//对每个Mapper的BeanDefinition定义处理,
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("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
// 在bean中增加sqlSessionFactory
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
// 在bean中增加sqlSessionTemplate
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
// 设置自动注入模式
if (!explicitFactoryUsed) {
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
}
写到代码的注释可能都不怎么关注,这里再次强调下重点,如果不注意后续可能有些会懵逼的。这是怎么来的。
- BeanDefinition的class设置为MapperFactoryBean
- 将原始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 类的名字起的真好,看名字就是一个注册器。这里的注册器有一箭双雕的作用
- 定义了一个Map,缓存所知道的Mapper,后面初始化MapperProxy代理用的着,不然后面不好取哦
- 将解析出来的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);
}
}
public void inject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
this.configuration = builderAssistant.getConfiguration();
this.builderAssistant = builderAssistant;
this.languageDriver = configuration.getDefaultScriptingLanguageInstance();
/**
* 驼峰设置 PLUS 配置 > 原始配置
*/
GlobalConfiguration globalCache = this.getGlobalConfig();
if (!globalCache.isDbColumnUnderline()) {
globalCache.setDbColumnUnderline(configuration.isMapUnderscoreToCamelCase());
}
Class<?> modelClass = extractModelClass(mapperClass);
if (null != modelClass) {
/**
* 初始化 SQL 解析
*/
if (globalCache.isSqlParserCache()) {
PluginUtils.initSqlParserInfoCache(mapperClass);
}
// 这里获取tableInfo. 这里你会看到我们@TableName了。。
TableInfo table = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
//生成sql注入sql
injectSql(builderAssistant, mapperClass, modelClass, table);
}
}
// 看到这个方法里面的injectXXXX是不是和我们BaseMapper里的一样呢。对这里挨着一个个的去实现。
protected void injectSql(MapperBuilderAssistant builderAssistant, Class<?> mapperClass, Class<?> modelClass, TableInfo table) {
/**
* #148 表信息包含主键,注入主键相关方法
*/
if (StringUtils.isNotEmpty(table.getKeyProperty())) {
/** 删除 */
this.injectDeleteByIdSql(false, mapperClass, modelClass, table);
this.injectDeleteByIdSql(true, mapperClass, modelClass, table);
/** 修改 */
this.injectUpdateByIdSql(true, mapperClass, modelClass, table);
this.injectUpdateByIdSql(false, mapperClass, modelClass, table);
/** 查询 */
this.injectSelectByIdSql(false, mapperClass, modelClass, table);
this.injectSelectByIdSql(true, mapperClass, modelClass, table);
} else {
// 表不包含主键时 给予警告
logger.warn(String.format("%s ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method.",
modelClass.toString()));
}
/**
* 正常注入无需主键方法
*/
/** 插入 */
this.injectInsertOneSql(true, mapperClass, modelClass, table);
this.injectInsertOneSql(false, mapperClass, modelClass, table);
/** 删除 */
this.injectDeleteSql(mapperClass, modelClass, table);
this.injectDeleteByMapSql(mapperClass, table);
/** 修改 */
this.injectUpdateSql(mapperClass, modelClass, table);
/** 查询 */
this.injectSelectByMapSql(mapperClass, modelClass, table);
this.injectSelectOneSql(mapperClass, modelClass, table);
this.injectSelectCountSql(mapperClass, modelClass, table);
this.injectSelectListSql(SqlMethod.SELECT_LIST, mapperClass, modelClass, table);
this.injectSelectListSql(SqlMethod.SELECT_PAGE, mapperClass, modelClass, table);
this.injectSelectMapsSql(SqlMethod.SELECT_MAPS, mapperClass, modelClass, table);
this.injectSelectMapsSql(SqlMethod.SELECT_MAPS_PAGE, mapperClass, modelClass, table);
this.injectSelectObjsSql(SqlMethod.SELECT_OBJS, mapperClass, modelClass, table);
/** 自定义方法 */
this.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<>();
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MybatisPlusMapperRegistry.");
}
try {
// 通过代理工厂再实例化。我们得MapperProxy代理
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + 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#invoke
,MapperMethod
初始化的时候会初始化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那些接口也是一样。所以才能像正常调用一样。
以上是关于Mybatis-Plus BaseMapper自动生成SQL及MapperProxy的主要内容,如果未能解决你的问题,请参考以下文章
Mybatis-plus中BaseMapper和IService功能相似重复,为啥要提供两个接口?
MyBatis-Plus01_概述初始化工程BaseMapper和Service中的CRUD常用注解
MyBatis-Plus01_概述初始化工程BaseMapper和Service中的CRUD常用注解
mybatis-plus 自定义basemapper支持批量增删改操作