本文共 3598 字,大约阅读时间需要 11 分钟。
问题:mybatis中mapper接口和xml文件是如何对应起来的。
在mybatis-config.xml中配置一个mapper
是最简单最直接的方式了。这样我们就可以通过SqlSession来获取这个mapper了。
表明上我们获取mapper是通过sqlSession的getMapper函数,稍微看一下源码,发现实际sqlSession调用的Configuration的getMapper函数。继续跟下去,Configuration又调用MapperRegistry类的getMapper函数。
看来MapperRegistry才是最终管理mapper注册和获取的类。
那么mybatis是怎么通过一个xml文件,来注册一个对应的mapper的呢。
研究最简单的加载方式:
public class App { public static void main( String[] args ) { String resource = "mybatis-config.xml"; try { InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); } catch (IOException e) { e.printStackTrace(); } }}
来看一下调用栈:
当我们看到 下面的这个函数实现,心里就豁然开朗了。xml的各种配置方式都在这。
private void mapperElement(XNode parent) throws Exception { if (parent != null) { 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"); 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(); } 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(); } 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."); } } } } }
再往下看 当resource不为空的时候,最终会去执行mapperParser.parse();这个函数中关键的一个地方,bindMapperForNamespace();
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); configuration.addMapper(boundType); } } } }
"namespace" 是关键,就是通过这个命名空间来找到对应的接口的,所以要注册一个mapper最终还有要找到这个接口。
原来怀疑是可以通过xml文件的内容直接反射来注册一个mapper的想法是错误的!
最后看一下完整的调用栈: