SqlSessionFactoryBuilder->build方法中解析的XML文件
// 解析</mappers>标签 mapperElement(root.evalNode("mappers"));
/** * 解析<mappers>标签 * @param parent mappers标签对应的XNode对象 * @throws Exception */ private void mapperElement(XNode parent) throws Exception { if (parent != null) { // 获取<mappers>标签的子标签 for (XNode child : parent.getChildren()) { // <package>子标签 if ("package".equals(child.getName())) { // 获取mapper接口和mapper映射文件对应的package包名 String mapperPackage = child.getStringAttribute("name"); // 将包下所有的mapper接口以及它的代理对象存储到一个Map集合中,key为mapper接口类型,value为代理对象工厂 configuration.addMappers(mapperPackage); } else {// <mapper>子标签 // 获取<mapper>子标签的resource属性 String resource = child.getStringAttribute("resource"); // 获取<mapper>子标签的url属性 String url = child.getStringAttribute("url"); // 获取<mapper>子标签的class属性 String mapperClass = child.getStringAttribute("class"); // 它们是互斥的 if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); // 专门用来解析mapper映射文件 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); // 通过XMLMapperBuilder解析mapper映射文件 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()); // 通过XMLMapperBuilder解析mapper映射文件 mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null) { Class<?> mapperInterface = Resources.classForName(mapperClass); // 将指定mapper接口以及它的代理对象存储到一个Map集合中,key为mapper接口类型,value为代理对象工厂 configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } } }
有三种XML写法:
1.接口所在包
package标签,通过name属性指定mapper接口所在的包名 ,
此时对应的映射文件必须与接口位于同一路径下,并且名称相同
<mappers>
<!-- mapper接口所在的包名 -->
<package name="com.i.mapper"/>
</mappers>
源吗:
if ("package".equals(child.getName())) {
// 获取mapper接口和mapper映射文件对应的package包名
String mapperPackage = child.getStringAttribute("name");
// 将包下所有的mapper接口以及它的代理对象存储到一个Map集合中,key为mapper接口类型,value为代理对象工厂
configuration.addMappers(mapperPackage);
}
反射拿到对应的类
/**
* @since 3.2.2
*/
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
// 根据package名称,加载该包下Mapper接口文件(不是映射文件)
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
// 获取加载的Mapper接口
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
// 将Mapper接口添加到MapperRegistry中
addMapper(mapperClass);
}
}
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
// 如果Map集合中已经有该mapper接口的映射,就不需要再存储了
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 将mapper接口以及它的代理对象存储到一个Map集合中,key为mapper接口类型,value为代理对象工厂
knownMappers.put(type, new MapperProxyFactory<T>(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.
// 用来解析注解方式的mapper接口
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
// 解析注解方式的mapper接口
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
解析注解:
public void parse() { // 获取mapper接口的全路径 String resource = type.toString(); // 是否解析过该mapper接口 if (!configuration.isResourceLoaded(resource)) { // 先解析mapper映射文件 loadXmlResource(); // 设置解析标识 configuration.addLoadedResource(resource); // Mapper构建者助手 assistant.setCurrentNamespace(type.getName()); // 解析CacheNamespace注解 parseCache(); // 解析CacheNamespaceRef注解 parseCacheRef(); Method[] methods = type.getMethods(); for (Method method : methods) { try { // issue #237 if (!method.isBridge()) { // 每个mapper接口中的方法,都解析成MappedStatement对象 parseStatement(method); } } catch (IncompleteElementException e) { configuration.addIncompleteMethod(new MethodResolver(this, method)); } } } parsePendingMethods(); }
解析单个的mapper.xml:
/** * 解析映射文件 * @param context 映射文件根节点<mapper>对应的XNode */ private void configurationElement(XNode context) { try { // 获取<mapper>标签的namespace值,也就是命名空间 String namespace = context.getStringAttribute("namespace"); // 命名空间不能为空 if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } // 设置当前的命名空间为namespace的值 builderAssistant.setCurrentNamespace(namespace); // 解析<cache-ref>子标签 cacheRefElement(context.evalNode("cache-ref")); // 解析<cache>子标签 cacheElement(context.evalNode("cache")); // 解析<parameterMap>子标签 parameterMapElement(context.evalNodes("/mapper/parameterMap")); // 解析<resultMap>子标签 resultMapElements(context.evalNodes("/mapper/resultMap")); // 解析<sql>子标签,也就是SQL片段 sqlElement(context.evalNodes("/mapper/sql")); // 解析<select>\<insert>\<update>\<delete>子标签 buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); } }
2.相对路径配置
mapper标签,通过resource属性引入classpath路径的相对资源
<mappers>
<!-- 通过resource属性引入classpath路径的相对资源-->
<mapper resource="com/i/mapper/FlowerMapper.xml"/>
<mapper resource="com/i/mapper/StudentMapper.xml"/>
<mapper resource="com/i/mapper/TeacherMapper.xml"/>
</mappers>
3.类注册引入
mapper标签,通过class属性指定mapper接口名称,
此时对应的映射文件必须与接口位于同一路径下,并且名称相同
<mappers>
<!-- 使用接口信息进行配置 -->
<mapper class="com.i.mapper.FlowerMapper"/>
<mapper class="com.i.mapper.StudentMapper"/>
<mapper class="com.i.mapper.TeacherMapper"/>
</mappers>
4.使用URL绝对路径方式引入(不用)
mapper标签,通过url引入网络资源或者本地磁盘资源
<mappers>
<mapper url="xml文件访问URL" />
<mapper url="file:///var/mappers/UserMapper.xml"/>
</mappers>