前言
MyBatis-plus
是完全基於MyBatis
開發的一個增強工具,是在MyBatis
的基礎上做增強的框架,爲簡化開發、提高效率而生。它在MyBatis
原本的框架上增加了很多實用性功能,比如樂觀鎖插件、字段自動填充功能、分頁插件、條件構造器、sql 注入器等等。使用 MyBatis-plus
可以完全不寫任何 XML
文件,直接使用繼承了BaseMapper
接口的類對象完成對數據庫的映射操作
基於映射的原理,MyBatis-plus
必然要實現 Mapper
中的方法與 SQL
語句的對應轉化,以下即爲 MyBatis-plus
重要流程圖例
1. Mapper 對象方法映射爲 SQL 語句
-
在
MyBatis-plus
中,MybatisPlusAutoConfiguration
自動配置類的sqlSessionFactory()
方法爲Spring
提供創建sqlSession
的工廠類對象,對sqlSessionFactory
進行定義的定義類變爲了MybatisSqlSessionFactoryBean
。在sqlSessionFactory()
方法中,除了注入MyBatis
本身的組件,還會注入MyBatis-plus
的 主鍵生成器、SQL 注入器等組件,最後通過MybatisSqlSessionFactoryBean#getObject()
方法獲取到sqlSessionFactory
對象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())); } applyConfiguration(factory); if (this.properties.getConfigurationProperties() != null) { factory.setConfigurationProperties(this.properties.getConfigurationProperties()); } ...... // TODO 自定義枚舉包 if (StringUtils.hasLength(this.properties.getTypeEnumsPackage())) { factory.setTypeEnumsPackage(this.properties.getTypeEnumsPackage()); } // TODO 此處必爲非 NULL GlobalConfig globalConfig = this.properties.getGlobalConfig(); // TODO 注入填充器 if (this.applicationContext.getBeanNamesForType(MetaObjectHandler.class, false, false).length > 0) { MetaObjectHandler metaObjectHandler = this.applicationContext.getBean(MetaObjectHandler.class); globalConfig.setMetaObjectHandler(metaObjectHandler); } // TODO 注入主鍵生成器 if (this.applicationContext.getBeanNamesForType(IKeyGenerator.class, false, false).length > 0) { IKeyGenerator keyGenerator = this.applicationContext.getBean(IKeyGenerator.class); globalConfig.getDbConfig().setKeyGenerator(keyGenerator); } // TODO 注入sql注入器 if (this.applicationContext.getBeanNamesForType(ISqlInjector.class, false, false).length > 0) { ISqlInjector iSqlInjector = this.applicationContext.getBean(ISqlInjector.class); globalConfig.setSqlInjector(iSqlInjector); } // TODO 設置 GlobalConfig 到 MybatisSqlSessionFactoryBean factory.setGlobalConfig(globalConfig); return factory.getObject(); }
-
MybatisSqlSessionFactoryBean#getObject()
執行懶加載策略,最後通過buildSqlSessionFactory()
方法創建SqlSessionFactory
工廠類對象。這個方法的流程很長,不過大致可以分爲兩個步驟:1.創建 MybatisXMLConfigBuilder 對象,調用其 parse() 方法去解析 XML 配置文件及 Mapper
2.解析獲得的信息存儲在 targetConfiguration 對象中,根據其信息創建 SqlSessionFactory 對象
protected SqlSessionFactory buildSqlSessionFactory() throws Exception { final MybatisConfiguration targetConfiguration; // TODO 使用 MybatisXmlConfigBuilder 而不是 XMLConfigBuilder MybatisXMLConfigBuilder xmlConfigBuilder = null; ...... } else if (this.configLocation != null) { // TODO 使用 MybatisXMLConfigBuilder // 1.1 創建 MybatisConfiguration 對象 xmlConfigBuilder = new MybatisXMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties); // 2.1 將解析獲得的信息的引用傳遞給 targetConfiguration 對象 targetConfiguration = xmlConfigBuilder.getConfiguration(); } else { LOGGER.debug(() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration"); // TODO 使用 MybatisConfiguration targetConfiguration = new MybatisConfiguration(); Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables); } // TODO 無配置啓動所必須的 this.globalConfig = Optional.ofNullable(this.globalConfig).orElseGet(GlobalConfigUtils::defaults); this.globalConfig.setDbConfig(Optional.ofNullable(this.globalConfig.getDbConfig()).orElseGet(GlobalConfig.DbConfig::new)); // TODO 初始化 id-work 以及 打印騷東西 targetConfiguration.setGlobalConfig(this.globalConfig); ...... // 1.2 開始解析 XML 配置文件 及 Mapper 接口 if (xmlConfigBuilder != null) { try { xmlConfigBuilder.parse(); LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'"); } catch (Exception ex) { throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex); } finally { ErrorContext.instance().reset(); } } targetConfiguration.setEnvironment(new Environment(MybatisSqlSessionFactoryBean.class.getSimpleName(), this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory, this.dataSource)); if (this.mapperLocations != null) { if (this.mapperLocations.length == 0) { LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found."); } else { for (Resource mapperLocation : this.mapperLocations) { if (mapperLocation == null) { continue; } try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e); } finally { ErrorContext.instance().reset(); } LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'"); } } } else { LOGGER.debug(() -> "Property 'mapperLocations' was not specified."); } // 2.2 根據 targetConfiguration 對象中保存的信息創建 SqlSessionFactory 對象 final SqlSessionFactory sqlSessionFactory = new MybatisSqlSessionFactoryBuilder().build(targetConfiguration); ...... return sqlSessionFactory; }
-
MybatisXMLConfigBuilder#parse()
會去解析配置文件,最後會調用到其內部方法mapperElement()
。這個方法完成解析Mapper
工作,並將其添加到配置類MybatisConfiguration
中private void mapperElement(XNode parent) throws Exception { /* * 定義集合 用來分類放置mybatis的Mapper與XML 按順序依次遍歷 */ if (parent != null) { //指定在classpath中的mapper文件 Set<String> resources = new HashSet<>(); //指向一個mapper接口 Set<Class<?>> mapperClasses = new HashSet<>(); setResource(parent, resources, mapperClasses); // 依次遍歷 首先 resource 然後 mapper for (String resource : resources) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); //TODO XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse(); } for (Class<?> mapper : mapperClasses) { // 主要關注此處 configuration.addMapper(mapper); } } }
-
MybatisConfiguration#addMapper()
方法其實是去調用MybatisMapperRegistry#addMapper()
方法,其核心是MybatisMapperAnnotationBuilder#parse()
public <T> void addMapper(Class<T> type) { if (type.isInterface()) { if (hasMapper(type)) { // TODO 如果之前注入 直接返回 return; // TODO 這裏就不拋異常了 // throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { // TODO 這裏也換成 MybatisMapperProxyFactory 而不是 MapperProxyFactory knownMappers.put(type, new MybatisMapperProxyFactory<>(type)); // It's important that the type is added before the parser is run // otherwise the binding may automatically be attempted by the // mapper parser. If the type is already known, it won't try. // TODO 這裏也換成 MybatisMapperAnnotationBuilder 而不是 MapperAnnotationBuilder MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
-
MybatisMapperAnnotationBuilder#parse()
方法真正開始完成Mapper
接口中的方法與SQL 語句
的映射,其中parseStatement()
方法是解析@Select/@Update
等註解寫入的 SQL語句,而代碼GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type)
通過MaBatis-plus
的 SQL 注入器完成Mapper
方法與 SQL 語句的轉化@Override public void parse() { String resource = type.toString(); if (!configuration.isResourceLoaded(resource)) { loadXmlResource(); configuration.addLoadedResource(resource); final String typeName = type.getName(); assistant.setCurrentNamespace(typeName); parseCache(); parseCacheRef(); SqlParserHelper.initSqlParserInfoCache(type); Method[] methods = type.getMethods(); for (Method method : methods) { try { // issue #237 if (!method.isBridge()) { // 解析 @Select 註解寫入的 SQL parseStatement(method); SqlParserHelper.initSqlParserInfoCache(typeName, method); } } catch (IncompleteElementException e) { // TODO 使用 MybatisMethodResolver 而不是 MethodResolver configuration.addIncompleteMethod(new MybatisMethodResolver(this, method)); } } // TODO 注入 CURD 動態 SQL , 放在在最後, because 可能會有人會用註解重寫sql if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) { GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type); } } parsePendingMethods(); }
-
AbstractSqlInjector#inspectInject()
會完成BaseMapper
接口中提供的 17 個默認方法與 SQL 語句的轉化,這部分主要通過AbstractMethod#inject()
方法完成@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)) { List<AbstractMethod> methodList = this.getMethodList(mapperClass); if (CollectionUtils.isNotEmpty(methodList)) { 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); } } }
-
AbstractMethod#inject()
方法並沒有什麼特別的操作,只是調用其子類實現injectMappedStatement()
方法。以Insert#injectMappedStatement()
爲例,其 SQL 語句的核心在於SqlMethod
類,這個枚舉類中緩存了可以動態拼接的 SQL 語句,只需要填上參數 format 就可以得到可執行的 SQL 語句。以上過程結束,只需要將所有信息通過addInsertMappedStatement()
方法封裝成MappedStatement
對象並將其加入到容器中,這樣Mapper
接口方法調用時,就可以通過 動態代理 的方式找到其對應執行的 SQL 語句,至此 SQL 語句準備及配置解析就完成了@Override public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { KeyGenerator keyGenerator = new NoKeyGenerator(); SqlMethod sqlMethod = SqlMethod.INSERT_ONE; String columnScript = SqlScriptUtils.convertTrim(tableInfo.getAllInsertSqlColumnMaybeIf(), LEFT_BRACKET, RIGHT_BRACKET, null, COMMA); String valuesScript = SqlScriptUtils.convertTrim(tableInfo.getAllInsertSqlPropertyMaybeIf(null), LEFT_BRACKET, RIGHT_BRACKET, null, COMMA); String keyProperty = null; String keyColumn = null; // 表包含主鍵處理邏輯,如果不包含主鍵當普通字段處理 if (StringUtils.isNotEmpty(tableInfo.getKeyProperty())) { if (tableInfo.getIdType() == IdType.AUTO) { /** 自增主鍵 */ keyGenerator = new Jdbc3KeyGenerator(); keyProperty = tableInfo.getKeyProperty(); keyColumn = tableInfo.getKeyColumn(); } else { if (null != tableInfo.getKeySequence()) { keyGenerator = TableInfoHelper.genKeyGenerator(tableInfo, builderAssistant, sqlMethod.getMethod(), languageDriver); keyProperty = tableInfo.getKeyProperty(); keyColumn = tableInfo.getKeyColumn(); } } } String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), columnScript, valuesScript); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); return this.addInsertMappedStatement(mapperClass, modelClass, sqlMethod.getMethod(), sqlSource, keyGenerator, keyProperty, keyColumn); }
-
SqlSessionFactory
對象的創建需要回到MybatisSqlSessionFactoryBean#buildSqlSessionFactory()
方法中,很容易追蹤到MybatisSqlSessionFactoryBuilder#build()
方法,最後其實是通過SqlSessionFactoryBuilder#build()
方法創建了一個DefaultSqlSessionFactory
對象返回public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
2. Mapper 操作數據庫的流程
-
@MapperScan
註解通過@Import(MapperScannerRegistrar.class)
引入掃描註冊的類MapperScannerRegistrar
,該類實現了ImportBeanDefinitionRegistrar
接口並重寫registerBeanDefinitions()
方法,在該方法中註冊了MapperScannerConfigurer
類void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); ...... registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); }
-
MapperScannerConfigurer
是Mapper
接口的掃描配置類,實現了BeanDefinitionRegistryPostProcessor
接口,其postProcessBeanDefinitionRegistry()
方法會在容器啓動過程中被回調,通過ClassPathMapperScanner#scan()
方法完成Mapper
的掃描註冊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); scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass); if (StringUtils.hasText(lazyInitialization)) { scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization)); } scanner.registerFilters(); scanner.scan( StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); }
-
ClassPathMapperScanner#processBeanDefinitions()
將掃描到的Mapper
接口生成對應的MapperFactoryBean
形式註冊到容器中,通過MapperFactoryBean
可以獲取Mapper
實例private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { GenericBeanDefinition definition; for (BeanDefinitionHolder holder : beanDefinitions) { definition = (GenericBeanDefinition) holder.getBeanDefinition(); String beanClassName = definition.getBeanClassName(); LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName + "' mapperInterface"); // the mapper interface is the original class of the bean // but, the actual class of the bean is MapperFactoryBean definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59 definition.setBeanClass(this.mapperFactoryBeanClass); definition.getPropertyValues().add("addToConfig", this.addToConfig); boolean explicitFactoryUsed = false; 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; } if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) { if (explicitFactoryUsed) { LOGGER.warn( () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored."); } definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName)); explicitFactoryUsed = true; } else if (this.sqlSessionTemplate != null) { if (explicitFactoryUsed) { LOGGER.warn( () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored."); } definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate); explicitFactoryUsed = true; } if (!explicitFactoryUsed) { LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'."); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); } definition.setLazyInit(lazyInitialization); } }
-
@Autowired
自動注入Mapper
觸發容器獲取 bean 的方法,調用到MapperFactoryBean#getObject()
方法,最終調用到sqlSessionTemplate#getMapper()
方法@Override public <T> T getMapper(Class<T> type) { return getConfiguration().getMapper(type, this); }
-
MyBatis-plus
使用的配置類是MybatisConfiguration
,最終調用到MybatisMapperRegistry#getMapper()
方法,這裏就進入了動態代理獲取MapperProxy
實例的流程@Override public <T> T getMapper(Class<T> type, SqlSession sqlSession) { // TODO 這裏換成 MybatisMapperProxyFactory 而不是 MapperProxyFactory final MybatisMapperProxyFactory<T> mapperProxyFactory = (MybatisMapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MybatisPlusMapperRegistry."); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }
-
MybatisMapperProxyFactory#newInstance()
方法給自動注入返回一個MybatisMapperProxy
代理對象protected T newInstance(MybatisMapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy); }
-
調用
Mapper
接口的方法觸發代理對象的MybatisMapperProxy#invoke()
,此時根據Mapper
對象被調用的方法生成MybatisMapperMethod
對象,通過MybatisMapperMethod#execute()
去真正地執行 SQL 語句,從而完成數據庫操作。此後的流程本文就不再分析,具體可參考文章 MyBatis Mapper 簡要總結@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 (method.isDefault()) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } final MybatisMapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); }