MyBatis-plus 源碼解析

前言

MyBatis-plus是完全基於MyBatis開發的一個增強工具,是在MyBatis的基礎上做增強的框架,爲簡化開發、提高效率而生。它在MyBatis原本的框架上增加了很多實用性功能,比如樂觀鎖插件、字段自動填充功能、分頁插件、條件構造器、sql 注入器等等。使用 MyBatis-plus 可以完全不寫任何 XML 文件,直接使用繼承了BaseMapper 接口的類對象完成對數據庫的映射操作

基於映射的原理,MyBatis-plus 必然要實現 Mapper中的方法與 SQL 語句的對應轉化,以下即爲 MyBatis-plus 重要流程圖例

在這裏插入圖片描述

1. Mapper 對象方法映射爲 SQL 語句

  1. 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();
     }
    
  2. 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;
    }
    
  3. 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);
           }
       }
    }
    
  4. 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);
                 }
             }
         }
     }
    
  5. 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();
     }
    
  6. 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);
             }
         }
     }
    
  7. 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);
     }
    
  8. SqlSessionFactory對象的創建需要回到 MybatisSqlSessionFactoryBean#buildSqlSessionFactory()方法中,很容易追蹤到 MybatisSqlSessionFactoryBuilder#build()方法,最後其實是通過 SqlSessionFactoryBuilder#build()方法創建了一個 DefaultSqlSessionFactory 對象返回

    public SqlSessionFactory build(Configuration config) {
     return new DefaultSqlSessionFactory(config);
    }
    

2. Mapper 操作數據庫的流程

  1. @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());
    }
    
  2. MapperScannerConfigurerMapper接口的掃描配置類,實現了 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));
    }
    
  3. 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);
     }
    }
    
  4. @Autowired 自動注入 Mapper 觸發容器獲取 bean 的方法,調用到 MapperFactoryBean#getObject()方法,最終調用到 sqlSessionTemplate#getMapper()方法

    @Override
    public <T> T getMapper(Class<T> type) {
        return getConfiguration().getMapper(type, this);
    }
    
  5. 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);
         }
     }
    
  6. MybatisMapperProxyFactory#newInstance()方法給自動注入返回一個 MybatisMapperProxy 代理對象

    protected T newInstance(MybatisMapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
    }
    
  7. 調用 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);
     }
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章