MyBatis-Plus工作原理

工作中一般都是使用MyBatis-Plus来代替Mybatis来进行对数据库的操作,也一直都想了解MyBatis-Plus和Mybatis的区别。最近结合工作中使用到的MyBatis-Plus功能,简单的看了一次MyBatis-Plus的源码,整理下写个文档,方便不久的将来忘记了,可以重新复习下。
下文描述的是SpringBoot如何把Mybatis-Plus整合到Spring中,以及在我们项目中经常使用到的Mybatis-Plus具体是怎么实现的。

@MapperScan
@MapperScan的作用可以理解为@Import注解 + @MapperScan中配置的包名

@EnableAsync
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
@EnableFeignClients({"com.***.***.common.proxy.api"})
@EnableApolloConfig("application")
@MapperScan("com.***.***.backend.**.dao")
public class SopBackendApplication {
    public static void main(String[] args) { SpringApplication.run(SopBackendApplication.class);
    }
}

SpringBoot启动时会记录启动类上的注解信息,然后Spring会通过ConfigurationClassPostProcessor解析对应的注解bean,当获取到@MapperScan注解中有@Import注解,然后会通过ConfigurationClassPostProcessor类把@Import中的MapperScannerRegistrar加入到Spring的启动逻辑中,因为MapperScannerRegistrar实现了Spring的ImportBeanDefinitionRegistrar接口,所以会被调用到registerBeanDefinitions()方法,而registerBeanDefinitions()方法的逻辑会判断类上面是否有@MapperScan注解,有的话就会通过MapperScannerConfigurer扫描@MapperScan配置的目录,通过ClassPathMapperScanner类扫描@Mapperscan配置目录下的所有加了@Repository注解的mapper,并生成对应的beandefinition信息保存到spring容器中,其中这些beandefinition的beanclass被设置成了MapperFactoryBean,而MapperFactoryBean实现了spring的Facyorybean接口,当spring通过beandefinitionmap创建bean的过程中,会调用MapperFactoryBean的getObject()方法生成xxxMapper接口的代理对象MybayisMapperProxy。到此Mapper接口就可以有代理对象被调用了。

@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
}
@Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
      registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
    }
  }
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
     //设置beanclass为MappeFactoryBean
      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;
      }

xxxMapper.xml
MybatisPlusAutoConfiguration被spring解释的时候,通过调用sqlSessionFactory()方法,加载默认路径下mapper.xml文件,保存到MappedStatement中,包括查询类型,sql语句,参数信息等,通过namespace和mapper对应上。

@Data
@Accessors(chain = true)
@ConfigurationProperties(prefix = Constants.MYBATIS_PLUS)
public class MybatisPlusProperties {

private String[] mapperLocations = new String[]{"classpath*:/mapper/**/*.xml"};

public Resource[] resolveMapperLocations() {
        return Stream.of(Optional.ofNullable(this.mapperLocations).orElse(new String[0]))
            .flatMap(location -> Stream.of(getResources(location)))
            .toArray(Resource[]::new);
    }

    private Resource[] getResources(String location) {
        try {
            return resourceResolver.getResources(location);
        } catch (IOException e) {
            return new Resource[0];
        }
    }
public class MybatisSqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
    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 {
                    //解析xml文件,生成MapperStatement
                        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 + "'");
                }
            }
        }
}


BaseMapper<T>
BaseMapper,继承了该接口后,无需编写mapper.xml文件,即可以获得CRUD功能,那么MyBatis-Plus又是如何做到无需配置mapper.xml就能获得CRUD功能呢?答案就是通过sql注入器AbstractSqlInjecor自己生成sql。

@TableName
在AbstractSqlInject中inspectInject方法会获取BaseMapper<T>中的泛型类型,然后获取Entity上@tableName配置的表名以及@TableField注解字段的信息。

public enum SqlMethod {
    /**
     * 插入
     */
    INSERT_ONE("insert", "插入一条数据(选择字段插入)", "<script>\nINSERT INTO %s %s VALUES %s\n</script>"),
public abstract class AbstractSqlInjector implements ISqlInjector {

    private static final Log logger = LogFactory.getLog(AbstractSqlInjector.class);

    @Override
    public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
      //  获取BaseMapper中的泛型类型,,通过TableName获取mapper关联的是哪个表
        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);
            }
        }
    }
public class TableInfoHelper {
   /**
     * <p>
     * 实体类反射获取表信息【初始化】
     * </p>
     *
     * @param clazz 反射实体类
     * @return 数据库表反射信息
     */
    public synchronized static TableInfo initTableInfo(MapperBuilderAssistant builderAssistant, Class<?> clazz) {
        TableInfo tableInfo = TABLE_INFO_CACHE.get(clazz);
        if (tableInfo != null) {
            if (builderAssistant != null) {
                tableInfo.setConfiguration(builderAssistant.getConfiguration());
            }
            return tableInfo;
        }

        /* 没有获取到缓存信息,则初始化 */
        tableInfo = new TableInfo(clazz);
        GlobalConfig globalConfig;
        if (null != builderAssistant) {
            tableInfo.setCurrentNamespace(builderAssistant.getCurrentNamespace());
            tableInfo.setConfiguration(builderAssistant.getConfiguration());
            globalConfig = GlobalConfigUtils.getGlobalConfig(builderAssistant.getConfiguration());
        } else {
            // 兼容测试场景
            globalConfig = GlobalConfigUtils.defaults();
        }

        /* 初始化表名相关 */
        initTableName(clazz, globalConfig, tableInfo);

        /* 初始化字段相关 */
        initTableFields(clazz, globalConfig, tableInfo);

        /* 放入缓存 */
        TABLE_INFO_CACHE.put(clazz, tableInfo);

        /* 缓存 lambda */
        LambdaUtils.installCache(tableInfo);

        /* 自动构建 resultMap */
        tableInfo.initResultMapIfNeed();

        return tableInfo;
    }

   /**
     * <p>
     * 初始化 表数据库类型,表名,resultMap
     * </p>
     *
     * @param clazz        实体类
     * @param globalConfig 全局配置
     * @param tableInfo    数据库表反射信息
     */
    private static void initTableName(Class<?> clazz, GlobalConfig globalConfig, TableInfo tableInfo) {
        /* 数据库全局配置 */
        GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig();
        TableName table = clazz.getAnnotation(TableName.class);

        String tableName = clazz.getSimpleName();
        String tablePrefix = dbConfig.getTablePrefix();
        String schema = dbConfig.getSchema();
        boolean tablePrefixEffect = true;

        if (table != null) {
            if (StringUtils.isNotEmpty(table.value())) {
                tableName = table.value();
                if (StringUtils.isNotEmpty(tablePrefix) && !table.keepGlobalPrefix()) {
                    tablePrefixEffect = false;
                }
            } else {
                tableName = initTableNameWithDbConfig(tableName, dbConfig);
            }
...
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容