前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Mybatis学习笔记(一)- Mapper整合和注入原理分析流程

Mybatis学习笔记(一)- Mapper整合和注入原理分析流程

作者头像
写一点笔记
发布2020-12-31 10:18:00
8850
发布2020-12-31 10:18:00
举报
文章被收录于专栏:程序员备忘录

上期中我们主要学习了Spring的动态bean注册,其中的主要接口是ImportBeanDefinitionRegistrar,在文中我们还主要学习接口的上游做了哪些事情。今天我们主要通过mybatis的mapper管理来学习一下该接口的下游方法调用过程。据此也尝试搞清楚mybatis的mapper的管理过程。

对此我们就从registerBeanDefinitions方法看起。

代码语言:javascript
复制
 public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//拿到具体的注解并转化成成annotationAttributes
        AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
//实例化一个自定义的扫描器
        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
        if (this.resourceLoader != null) {
            scanner.setResourceLoader(this.resourceLoader);
        }
   //获取注解元素annotationClass
        Class extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
        if (!Annotation.class.equals(annotationClass)) {
            scanner.setAnnotationClass(annotationClass);
        }
//获取要被扫描过程检测的接口,由markerinterface元素指定。
        Class markerInterface = annoAttrs.getClass("markerInterface");
        if (!Class.class.equals(markerInterface)) {
            scanner.setMarkerInterface(markerInterface);
        }
//设置类名生成器
        Class extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
        if (!BeanNameGenerator.class.equals(generatorClass)) {
            scanner.setBeanNameGenerator((BeanNameGenerator)BeanUtils.instantiateClass(generatorClass));
        }
//设置mapper管理器,会将扫描的mapper类放到核心配置类configuration中
        Class extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
        if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
            scanner.setMapperFactoryBean((MapperFactoryBean)BeanUtils.instantiateClass(mapperFactoryBeanClass));
        }
//设置sqlsessionTemplate的名称
        scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
//设置sqlsessionfactory的名称
        scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
        List basePackages = new ArrayList();
//获取要扫描的包名称
        String[] var10 = annoAttrs.getStringArray("value");
        int var11 = var10.length;

        int var12;
        String pkg;
        for(var12 = 0; var12 < var11; ++var12) {
            pkg = var10[var12];
            if (StringUtils.hasText(pkg)) {
                basePackages.add(pkg);
            }
        }
//从beasePackages中获取要扫描的包名
        var10 = annoAttrs.getStringArray("basePackages");
        var11 = var10.length;

        for(var12 = 0; var12 < var11; ++var12) {
            pkg = var10[var12];
            if (StringUtils.hasText(pkg)) {
                basePackages.add(pkg);
            }
        }
//从basePackageClasses中获取要扫描的类的全路径,然后获取要扫描的包名
        Class[] var15 = annoAttrs.getClassArray("basePackageClasses");
        var11 = var15.length;

        for(var12 = 0; var12 < var11; ++var12) {
            Class clazz = var15[var12];
            basePackages.add(ClassUtils.getPackageName(clazz));
        }
        String mapperHelperRef = annoAttrs.getString("mapperHelperRef");
        String[] properties = annoAttrs.getStringArray("properties");
        if (StringUtils.hasText(mapperHelperRef)) {
            scanner.setMapperHelperBeanName(mapperHelperRef);
        } else if (properties != null && properties.length > 0) {
            scanner.setMapperProperties(properties);
        } else {
            try {
                scanner.setMapperProperties(this.environment);
            } catch (Exception var14) {
                LOGGER.warn("只有 Spring Boot 环境中可以通过 Environment(配置文件,环境变量,运行参数等方式) 配置通用 Mapper,其他环境请通过 @MapperScan 注解中的 mapperHelperRef 或 properties 参数进行配置!如果你使用 tk.mybatis.mapper.session.Configuration 配置的通用 Mapper,你可以忽略该错误!", var14);
            }
        }
//设置可以被扫描到的标志
        scanner.registerFilters();
//进行for循环遍历
        scanner.doScan(StringUtils.toStringArray(basePackages));
    }

在registerfilters方法中,就是通过设置需要被扫描的类的一些标记信息。要么是注解,要么是接口。

代码语言:javascript
复制
    public void registerFilters() {
        boolean acceptAllInterfaces = true;
        if (this.annotationClass != null) {
//设置需要被扫描的注解,通过上面的注解上的元素设定
            this.addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
            acceptAllInterfaces = false;
        }
//设置需要被拦截的接口
        if (this.markerInterface != null) {
            this.addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
                protected boolean matchClassName(String className) {
                    return false;
                }
            });
            acceptAllInterfaces = false;
        }
//是否拦截所有的接口
        if (acceptAllInterfaces) {
            this.addIncludeFilter(new TypeFilter() {
                public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
                    return true;
                }
            });
        }
//不需要被拦截的类型
        this.addExcludeFilter(new TypeFilter() {
            public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
                String className = metadataReader.getClassMetadata().getClassName();
                return className.endsWith("package-info") ? true : metadataReader.getAnnotationMetadata().hasAnnotation("tk.mybatis.mapper.annotation.RegisterMapper");
            }
        });
    }

通过上述两个步骤,我们知道mybatis先是通过@mapperscan注解设置一些值,然后通过设置需要被拦截的类的一些基本信息。然后就要开始for循环逐个扫描包路径了。

代码语言:javascript
复制
public SetdoScan(String... basePackages) {
//直接进行扫描,将符合条件的类beandefinition信息进行返回
        Set beanDefinitions = super.doScan(basePackages);
        if (beanDefinitions.isEmpty()) {
            this.logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
        } else {
//对符合条件的bean进行一些填充
            this.processBeanDefinitions(beanDefinitions);
        }
        return beanDefinitions;
    }

寻找符合条件的bean。

代码语言:javascript
复制
 public SetfindCandidateComponents(String basePackage) {
        if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
               return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
        }
        else {
               return scanCandidateComponents(basePackage);
        }
 }
代码语言:javascript
复制
//根据设置的拦截信息进行判断
       private boolean indexSupportsIncludeFilters() {
              for (TypeFilter includeFilter : this.includeFilters) {
                     if (!indexSupportsIncludeFilter(includeFilter)) {
                            return false;
                     }
              }
              return true;
       }

       private boolean indexSupportsIncludeFilter(TypeFilter filter) {
              if (filter instanceof AnnotationTypeFilter) {
                     Class annotation = ((AnnotationTypeFilter) filter).getAnnotationType();
                     return (AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, annotation) ||
                                   annotation.getName().startsWith("javax."));
              }
              if (filter instanceof AssignableTypeFilter) {
                     Class target = ((AssignableTypeFilter) filter).getTargetType();
                     return AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, target);
              }
              return false;
       }

//这块判断是接口哦,他可以扫描接口
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
    }

通过上述对bean是否符合条件进行判断之后,就开始对扫描到的bean,也就是我们的mapper进行处理了。如下所示:

代码语言:javascript
复制
private void processBeanDefinitions(SetbeanDefinitions) {
        Iterator var3 = beanDefinitions.iterator();

        while(var3.hasNext()) {
//拿到具体的mapper
            BeanDefinitionHolder holder = (BeanDefinitionHolder)var3.next();
            GenericBeanDefinition definition = (GenericBeanDefinition)holder.getBeanDefinition();
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + definition.getBeanClassName() + "' mapperInterface");
            }
//设置构造函数
            definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
//设置该bean的name,mapperfactoryBean。这里设置主要是为了注入到mapperfacatorybean中
            definition.setBeanClass(this.mapperFactoryBean.getClass());
            if (StringUtils.hasText(this.mapperHelperBeanName)) {
                definition.getPropertyValues().add("mapperHelper", new RuntimeBeanReference(this.mapperHelperBeanName));
            } else {
                if (this.mapperHelper == null) {
                    this.mapperHelper = new MapperHelper();
                }

                definition.getPropertyValues().add("mapperHelper", this.mapperHelper);
            }
//设置addToConfig属性
            definition.getPropertyValues().add("addToConfig", this.addToConfig);
            boolean explicitFactoryUsed = false;
            if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
//sqlSessionFactory,设置sqlSessionFactory类
                definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
                explicitFactoryUsed = true;
            } else if (this.sqlSessionFactory != null) {
                definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
                explicitFactoryUsed = true;
            }
//设置sqlSessionTemplate类名称
            if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
                if (explicitFactoryUsed) {
                    this.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) {
                    this.logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
                }

                definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
                explicitFactoryUsed = true;
            }

            if (!explicitFactoryUsed) {
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
                }

                definition.setAutowireMode(2);
            }
        }

我们通过查看MapperFactoryBean的构造函数发现如下:

通过上述操作,我们知道我们的的mapper已经进入beandefinition中了,也就是mybatis需要的哪些接口。通过上述的一些分析。接口扫描和注册基本已经完成。现在就成了spring的整合问题。为此有了mybatis-spring的jar包用来整合。

sqlSessionFactoryBean类实现了接口initalizingbean。我们先看看这个接口做了哪些事情。

代码语言:javascript
复制
public class SqlSessionFactoryBean implements FactoryBean, InitializingBean, ApplicationListener;

我们看一下具体的实现

代码语言:javascript
复制
@Override
  public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
              "Property 'configuration' and 'configLocation' can not specified with together");
//创建sqlSessionFactory
    this.sqlSessionFactory = buildSqlSessionFactory();
  }

  protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

    final Configuration targetConfiguration;

    XMLConfigBuilder xmlConfigBuilder = null;
//xml解析
    if (this.configuration != null) {
      targetConfiguration = this.configuration;
      if (targetConfiguration.getVariables() == null) {
        targetConfiguration.setVariables(this.configurationProperties);
      } else if (this.configurationProperties != null) {
//设置xml路径等信息
        targetConfiguration.getVariables().putAll(this.configurationProperties);
      }
    } else if (this.configLocation != null) {
//建一个解析器
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      targetConfiguration = xmlConfigBuilder.getConfiguration();
    } else {
      LOGGER.debug(() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
      targetConfiguration = new Configuration();
      Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
    }

    Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
    Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
    Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);

    if (hasLength(this.typeAliasesPackage)) {
      scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType)
          .forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
    }

    if (!isEmpty(this.typeAliases)) {
      Stream.of(this.typeAliases).forEach(typeAlias -> {
        targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
        LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
      });
    }

    if (!isEmpty(this.plugins)) {
      Stream.of(this.plugins).forEach(plugin -> {
        targetConfiguration.addInterceptor(plugin);
        LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
      });
    }

    if (hasLength(this.typeHandlersPackage)) {
      scanClasses(this.typeHandlersPackage, TypeHandler.class).stream()
          .filter(clazz -> !clazz.isInterface())
          .filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
          .filter(clazz -> ClassUtils.getConstructorIfAvailable(clazz) != null)
          .forEach(targetConfiguration.getTypeHandlerRegistry()::register);
    }

    if (!isEmpty(this.typeHandlers)) {
      Stream.of(this.typeHandlers).forEach(typeHandler -> {
        targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
        LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
      });
    }

    if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
      try {
        targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
      } catch (SQLException e) {
        throw new NestedIOException("Failed getting a databaseId", e);
      }
    }

    Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);

    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(this.environment,
        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.");
    }

    return this.sqlSessionFactoryBuilder.build(targetConfiguration);
  }

//进行xml转类
  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {

      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
//将转出来的类转变为实体
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

解析xml的

代码语言:javascript
复制
private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class boundType = null;
      try {
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      if (boundType != null) {
        if (!configuration.hasMapper(boundType)) {
          // Spring may not know the real resource name so we set a flag
          // to prevent loading again this resource from the mapper interface
          // look at MapperAnnotationBuilder#loadXmlResource
          configuration.addLoadedResource("namespace:" + namespace);
//在config中添加到configuration
          configuration.addMapper(boundType);
        }
      }
    }

在addMapper方法中

代码语言:javascript
复制
public void addMapper(Classtype) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
//创建一个代理类
        knownMappers.put(type, new MapperProxyFactory<>(type));
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        //缓存起来
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

这里的parse方法执行结束之后,我们的xml就进入knownMappers 保存。

代码语言:javascript
复制
Map, MapperProxyFactory> knownMappers = new HashMap<>();

但是这个addmapper又是在哪里调用的?

我们发现在MapperFactoryBean中有相关的操作。但是没有发现是谁调用了checkDaoConfig接口,所有去父类看一下。

最后在父类中找到了initalizingBean的接口。于是有和spring有了关系。

代码语言:javascript
复制
public abstract class DaoSupport implements InitializingBean {
    protected final Log logger = LogFactory.getLog(this.getClass());

    public DaoSupport() {
    }

    public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
//这里进行了调用用
        this.checkDaoConfig();
        try {
            this.initDao();
        } catch (Exception var2) {
            throw new BeanInitializationException("Initialization of DAO failed", var2);
        }
    }

    protected abstract void checkDaoConfig() throws IllegalArgumentException;

    protected void initDao() throws Exception {
    }
}

而这里的mapperInterface则是我们spring接口扫描的结果。我们还看到在addmapper方法的时候有判断这个接口对应的xml是否已经创建成功了。

同时,我们也发现MapperFactoryBean类也实现了FactoryBean,也就是这里的getObject方法。我们以前学习FactoryBean的时候说如果spring的getBean在内部找不到具体的类的话,就会从其他实现了FactoryBean的接口中获取。那么就这也就是我们开发中使用注解@Autowired注入了一个接口的原因所在。

通过对MapperFactoryBean的学习,我们发现这才是最后的大boss,除此那么既然发生了聚合,那么肯定需要一些基础的东西。于是我们发现

也就是说MapperFactoryBean的时候需要SqlSessionFactory,然后通过sqlsessionFactory创建SqlSessionTemplate。最终调用的还是sqlSessionfactionbean中的configuration,configuration又调用了mapperRegistry。而最终也是通过JDK代理实现的。

代码语言:javascript
复制
@SuppressWarnings("unchecked")
  protected T newInstance(MapperProxymapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxymapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

通过上述的分析,我们大概得可以知道mybatis的过程。首先通过spring接口扫描,然后再将扫描到的接口注入到mapperfactorybean中。而sqlSessionFactoryBean也是和spring相关系的,其通过配置文件找到需要解析的xml文件,然后实例化成代理类,并交由knownMappers进行管理。当mapperfactorybean初始化的时候,将扫描到的接口和xml解析的具体实体进行一一对应起来并放入到mapperRegistry中,等待spring注入的时候通过getBean方法进行实例化。能够注入的原因是mapperfactorybean实现了factoryBean接口。

最后我们大概得说一下,这些流程中的关键类,mapperfactorybean是主要的类。其中聚合sqlsessiontemplate,sqlsessiontemplate是从sqlsessionfactory创建而来。sqlsessionfactory又是从sqlsessionfactorybean中而来,sqlsessionfactorybean主要是解析xml文件并将其保存到knownMappers中的。

至此mybatis的主体逻辑大概想清楚了,那么sql的执行过程又是怎么样的?我们下期再看这个问题吧!

参考文献:

https://www.cnblogs.com/hei12138/p/mybatis-spring.html

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-12-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员备忘录 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档