myBaits sql的配置方式与加载原理

整体流程

myBatis提供了四种方式寻找sql映射语句,包括使用相对类路径的资源引用、完全限定资源定位符、类名或者包名等,相应的myBatis-config.xml的mapper结点配置为:

<mappers>
    <!-- 使用相对于类路径的资源引用 -->
    <!-- <mapper resource="mybatis/CountryMapper.xml" /> -->
    <!-- 使用完全限定资源定位符(URL) -->
    <mapper url="file:///E:/myBatis/mybatis-3-mybatis-3.5.3/src/test/rescouce/mybatis/CountryMapper.xml"/>
    <!-- 使用映射器接口实现类的完全限定类名 -->
    <!-- <mapper class="com.mybatis.mapper.CountryMapper"></mapper> -->
    <!-- 将包内的映射器接口实现全部注册为映射器 -->
    <!-- <package name="com.mybatis.mapper"></package> -->
</mappers>

在解析主配置文件时,XMLConfigBuilder会依据不同的映射方式来进行登记注册。

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        // 加载mapper的4种方式,分别为package、resource、url、class,优先级从前往后
        for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
                String mapperPackage = child.getStringAttribute("name");
                configuration.addMappers(mapperPackage);
            } else {
                String resource = child.getStringAttribute("resource");
                String url = child.getStringAttribute("url");
                String mapperClass = child.getStringAttribute("class");
                // 解析resource
                if (resource != null && url == null && mapperClass == null) {
                    ErrorContext.instance().resource(resource);
                    InputStream inputStream = Resources.getResourceAsStream(resource);
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource,
                                                                         configuration.getSqlFragments());
                    mapperParser.parse();
                    // 解析url
                } else if (resource == null && url != null && mapperClass == null) {
                    ErrorContext.instance().resource(url);
                    InputStream inputStream = Resources.getUrlAsStream(url);
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url,
                                                                         configuration.getSqlFragments());
                    mapperParser.parse();
                    // 解析mapperClass
                } else if (resource == null && url == null && mapperClass != null) {
                    Class<?> mapperInterface = Resources.classForName(mapperClass);
                    configuration.addMapper(mapperInterface);
                } else {
                    throw new BuilderException(
                        "A mapper element may only specify a url, resource or class, but not more than one.");
                }
            }
        }
    }
}

可以从上述代码中看到,当配置类名或者包名时,将使用configuration.addMappers()这个重载的方式分别处理,解析其上的注解配置。而当用相对或绝对路径直接配置xml文档时,直接使用XMLMapperBuilder对xml文件进行解析,下面针对这四种情况分别进行分析。

使用类名的方式进行加载:

使用类名加载时,configuration.addMappers()调用参数为接收一个Class的重载方法,进一步调用MapperRegistry对应的重载方法:

public <T> void addMapper(Class<T> type) {
    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);
            }
        }
    }
}

然后使用MapperAnnotationBuilder.parse来遍历接口中的每一个方法,执行parseStatement方法,在这个方法中会调用getSqlSourceFromAnnotations来从接口方法的注解中抽取sql语句,调用getParameterType方法获取接口方法需要的参数类型,调用parseResultMap或者直接从@ResultMap注解中获取返回值的类型。再由MapperBuilderAssistant.addMappedStatement方法构造一个MappeStatement对象,并放置到Configuration的mappedStatements中,至此,完成了该接口方法的解析与构造。

当客户端直接中SqlSession的selectList等方法时,将从mappedStatements获取对应的MappedStatement对象,进而通过Executor、Handler等体系完成数据的CURD。

当客户端通过SqlSession调用getMapper时,先使用MapperRegistry维护的knownMappers这个map获取对应类型的代理工厂,由代理工厂通过动态代理的方式生成对应接口的代理实例,实际进行操作时,由这个代理实例进行。调用这个接口的某个方法时,会由实现了InvocationHandler接口的MapperProxy代理处理器进行接管,在invoke方法中构造对应的MappedMethod,其中包含了方法签名MethodSignature和sql命令SqlCommand。MappedMethod的execute统一拦截所有的数据操作,依据CommandType的不同,执行不行的SqlSession的方法。

使用包名的方式进行加载:

当检测到当前xml配置的结点名称是package时,调用Configuration的接收一个String的addMappers重载方法,进而调用MapperRegistry对应的重载方法。在这个方法中,对当前包内的所有接口进行遍历,按照上述直接以接口名进行配置的方式进行处理。

public void addMappers(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
        addMapper(mapperClass);
    }
}

使用基于类路径的xml文件的方式进行加载:

读取到xml的配置文件到inputstream,然后构造XMLMapperBuilder,其中configurationElement方法解析sql语句,bindMapperForNamespace方法将配置的namespace配置到mapper中以使在获取时动态代理生成实例。

使用完全限定资源定位符的方式进行加载:

和上述基于类路径的xml文件的解析方式完全相同,只是获取xml文件的方式不同,前者调用Resources.getResourceAsStream(resource)进行获取,使用url的方式调用Resources.getUrlAsStream(url)进行获取。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值