本文作废作废。
原因:跨xml引用: use in another mapper, you just need to specify the fully-qualified id.
本文的唯一一点好处可以省去写namespace的麻烦。
1. 问题
在工作中和同事共同开发一个模块,底层的数据模型用到的是同一个表。日常开发常规地都会动到这个数据模型实体的mapper。合并代码的时候肯定会有冲突相互覆盖。后来想各自写mapper.xml处理相关的逻辑,但是这样resultMap就会有多份。需求经常变,到时候多份resultMap维护起来也麻烦。
所以想到可以扩展一下mybatis支持xml继承。后来查找了一下发现mybatis的resultMap支持继承,利用extends属性扩展原有的定义。最后决定在也在mapper元素上增加extends属性保存要继承的mapper.xml文件。可以把公共的配置提取出来,把复杂的业务逻辑sql和基础的curd sql写在不同的文件里。
后来有另外加入import功能,<import src="xx.xx.xx">
导入其他文件的配置,原理也和extends差不多。类比 类的继承和组合。
2. mybatis改动的地方
2.1 mybatis-3-mapper.dtd
需要改动org.apache.ibatis.builder.xml包下的mybatis-3-mapper.dtd,给mapper增加extends属性
<!ELEMENT mapper (cache-ref | cache | import* | resultMap* | parameterMap* | sql* | insert* | update* | delete* | select* )+>
<!ATTLIST mapper
xmlns:fo CDATA #IMPLIED
namespace CDATA #IMPLIED
extends CDATA #IMPLIED
>
<!ELEMENT import EMPTY>
<!ATTLIST import
src CDATA #REQUIRED
>
2.2 XMLMapperBuilder.configurationElement
需要改动XMLMapperBuilder.configurationElement解析映射xml的处理过程:增加extends属性的处理,具体代码。没有做循环依赖处理:
private void configurationElement(XNode context) {
try {
//1.配置namespace
String namespace = context.getStringAttribute("namespace");
if (namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
extendsElement(context);
mapperChildElement(context);
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
/**根据mapper上的extends所指向的xml文件,加载xml的配置
* 增加父extends,继承xml里mapper下的所有元素*/
private void extendsElement(XNode context) throws Exception
{
String extend = context.getStringAttribute("extends");
if(extend==null)
return;
String[] xmlResources=extend.replace('.', '/').split(",");
for(String xmlResource:xmlResources){
fileElements(xmlResource);
}
}
private void importElement(List<XNode> list) throws Exception
{
for(XNode node:list)
{
String src = node.getStringAttribute("src");
if(src==null)
return;
String[] xmlResources=src.replace('.', '/').split(",");
for(String xmlResource:xmlResources)
fileElements(xmlResource);
}
}
private void fileElements(String xmlResource) throws Exception
{
xmlResource = xmlResource+ ".xml";
InputStream inputStream = Resources.getResourceAsStream(Thread.currentThread().getContextClassLoader(),xmlResource);
XPathParser xpathParser=new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver());
XNode extendsContext=xpathParser.evalNode("/mapper");
extendsElement(extendsContext);
mapperChildElement(extendsContext);
}
private void mapperChildElement(XNode context) throws Exception
{
//1.import
importElement(context.evalNodes("/mapper/import"));
//2.配置cache-ref
cacheRefElement(context.evalNode("cache-ref"));
//3.配置cache
cacheElement(context.evalNode("cache"));
//4.配置parameterMap(已经废弃,老式风格的参数映射)
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//5.配置resultMap(高级功能)
resultMapElements(context.evalNodes("/mapper/resultMap"));
//6.配置sql(定义可重用的 SQL 代码段)
sqlElement(context.evalNodes("/mapper/sql"));
//7.配置select|insert|update|delete TODO
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
}
3. 使用
1)普通的HelloWordMapper.xml映射文件,在mapper元素上加extends继承cn.alpha.demo.BaseMapper
<mapper namespace="cn.alpha.demo.persistence.HelloWorldMapper" extends="cn.alpha.demo.BaseMapper">
<select id="getData" resultMap="ActivRePropMap" flushCache="false">
select NAME_,VALUE_,REV_ from act_ge_property
</select>
</mapper>
2)BaseMapper.xml父映射文件, 在mapper元素上加extends继承cn.alpha.demo.BaseMapper2
<mapper namespace="cn.alpha.BaseMapper" extends="cn.alpha.demo.BaseMapper2">
<resultMap id="ActivRePropMap" type="cn.alpha.demo.bo.ActivReProp" extends="ActivRePropMap-base">
<result property="rev" column="REV_"/>
</resultMap>
</mapper>
3)BaseMapper2.xml祖先映射文件
<mapper namespace="cn.alpha.BaseMapper2" >
<resultMap id="ActivRePropMap-base" type="cn.alpha.demo.bo.ActivReProp">
<id property="name" column="NAME_" />
<result property="value" column="VALUE_"/>
</resultMap>
</mapper>