Mybatis-Plus是一个 MyBatis增强工具包,简化 CRUD 操作,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生,号称无侵入,现在开发中比较常用,包括我自己现在的项目中ORM框架除使用JPA就是他了。
我好奇的是他是如何实现单表的CRUD操作的?
不看源码之前,其实我大致能猜一猜:因为他号称零入侵,只做增强,那我们就能简单的理解为他只是在上面做了一层封装类似于装饰器模式,简化了许多繁重的操作。
但是万变不离其宗,他最后应该还是执行MyBatis里Mapper注册MappedStatement这一套,所以他应该是内置了一套CRUD的SQL模板,根据不同的entity来生成对应的语句,然后注册到Mapper中用来执行。
带着猜想,我们具体跟下他的注册流程。
1.MybatisPlusAutoConfigurationMybatis-Plus依托于spring,一切都是用的ioc这一套。创建SqlSessionFactory从之前的SqlSessionFactoryBuilder主动创建改成ioc来控制创建。具体我们看一代码:
@Bean @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { // TODO 使用 MybatisSqlSessionFactoryBean 而不是 SqlSessionFactoryBean MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean(); factory.setDataSource(dataSource); factory.setVfs(SpringBootVFS.class); if (StringUtils.hasText(this.properties.getConfigLocation())) { factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation())); } //初始化configuration applyConfiguration(factory); if (this.properties.getConfigurationProperties() != null) { factory.setConfigurationProperties(this.properties.getConfigurationProperties()); } if (!ObjectUtils.isEmpty(this.interceptors)) { factory.setPlugins(this.interceptors); } if (this.databaseIdProvider != null) { factory.setDatabaseIdProvider(this.databaseIdProvider); } if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) { factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage()); } if (this.properties.getTypeAliasesSuperType() != null) { factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType()); } if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) { factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage()); } if (!ObjectUtils.isEmpty(this.typeHandlers)) { factory.setTypeHandlers(this.typeHandlers); } //获得mapper文件 Resource[] mapperLocations = this.properties.resolveMapperLocations(); if (!ObjectUtils.isEmpty(mapperLocations)) { factory.setMapperLocations(mapperLocations); } // TODO 对源码做了一定的修改(因为源码适配了老旧的mybatis版本,但我们不需要适配) Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver(); if (!ObjectUtils.isEmpty(this.languageDrivers)) { factory.setScriptingLanguageDrivers(this.languageDrivers); } Optional.ofNullable(defaultLanguageDriver).ifPresent(factory::setDefaultScriptingLanguageDriver); // TODO 自定义枚举包 if (StringUtils.hasLength(this.properties.getTypeEnumsPackage())) { factory.setTypeEnumsPackage(this.properties.getTypeEnumsPackage()); } // TODO 此处必为非 NULL GlobalConfig globalConfig = this.properties.getGlobalConfig(); // TODO 注入填充器 this.getBeanThen(MetaObjectHandler.class, globalConfig::setMetaObjectHandler); // TODO 注入主键生成器 this.getBeanThen(IKeyGenerator.class, i -> globalConfig.getDbConfig().setKeyGenerator(i)); // TODO 注入sql注入器 this.getBeanThen(ISqlInjector.class, globalConfig::setSqlInjector); // TODO 注入ID生成器 this.getBeanThen(IdentifierGenerator.class, globalConfig::setIdentifierGenerator); // TODO 设置 GlobalConfig 到 MybatisSqlSessionFactoryBean factory.setGlobalConfig(globalConfig); return factory.getObject(); }代码比较简单,再加上是国人开发的框架,在关键节点上有一定的注释,所以看上去还算是轻松加愉快。这个方法基本上就是MybatisSqlSessionFactoryBean的初始化操作。
我们主要是看Mapper的生成,所以其它的放一旁,所以我们基本最在意的应该是注入sql注入器this.getBeanThen(ISqlInjector.class, globalConfig::setSqlInjector) 。
2.ISqlInjector(SQL自动注入器接口) public interface ISqlInjector { /** * 检查SQL是否注入(已经注入过不再注入) * * @param builderAssistant mapper 信息 * @param mapperClass mapper 接口的 class 对象 */ void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass); } public abstract class AbstractSqlInjector implements ISqlInjector { private static final Log logger = LogFactory.getLog(AbstractSqlInjector.class); @Override public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) { Class<?> modelClass = extractModelClass(mapperClass); if (modelClass != null) { String className = mapperClass.toString(); Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration()); if (!mapperRegistryCache.contains(className)) { //获得CRUD一系列的操作方法 List<AbstractMethod> methodList = this.getMethodList(mapperClass); if (CollectionUtils.isNotEmpty(methodList)) { //取得对应TableEntity TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass); // 循环注入自定义方法 methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo)); } else { logger.debug(mapperClass.toString() + ", No effective injection method was found."); } mapperRegistryCache.add(className); } } } /** * SQL 默认注入器 * * @author hubin * @since 2018-04-10 */ public class DefaultSqlInjector extends AbstractSqlInjector { @Override public List<AbstractMethod> getMethodList(Class<?> mapperClass) { return Stream.of( new Insert(), new Delete(), new DeleteByMap(), new DeleteById(), new DeleteBatchByIds(), new Update(), new UpdateById(), new SelectById(), new SelectBatchByIds(), new SelectByMap(), new SelectOne(), new SelectCount(), new SelectMaps(), new SelectMapsPage(), new SelectObjs(), new SelectList(), new SelectPage() ).collect(toList()); } }ISqlInjector接口只有一个inspectInject方法来提供SQL注入的操作,在AbstractSqlInjector抽象类来提供具体的操作,最终对外的默认实现类是DefaultSqlInjector。