写点什么

【Mybatis】如何继承 Mybatis 中的 Mapper.xml 文件

  • 2022-10-18
    江西
  • 本文字数:4460 字

    阅读完需:约 1 分钟

作者石臻臻,CSDN 博客之星 Top5Kafka Contributornacos Contributor华为云 MVP,腾讯云 TVP,滴滴 Kafka 技术专家 KnowStreaming PMC)


KnowStreaming 是滴滴开源的Kafka运维管控平台, 有兴趣一起参与参与开发的同学,但是怕自己能力不够的同学,可以联系我,带你一起你参与开源!

最近在写一个 Mybatis 代码自动生成插件,用的是 Mybatis 来扩展,其中有一个需求就是 生成 javaMapper 文件和 xmlMapper 文件的时候 希望另外生成一个扩展类和扩展 xml 文件。原文件不修改,只存放一些基本的信息,开发过程中只修改扩展的 Ext 文件形式如下:SrcTestMapper.java



package com.test.dao.mapper.srctest;
import com.test.dao.model.srctest.SrcTest;import com.test.dao.model.srctest.SrcTestExample;import java.util.List;import org.apache.ibatis.annotations.Param;
public interface SrcTestMapper {    long countByExample(SrcTestExample example);
    int deleteByExample(SrcTestExample example);
    int deleteByPrimaryKey(Integer id);
    int insert(SrcTest record);
    int insertSelective(SrcTest record);
    List<SrcTest> selectByExample(SrcTestExample example);
    SrcTest selectByPrimaryKey(Integer id);
    int updateByExampleSelective(@Param("record") SrcTest record, @Param("example") SrcTestExample example);
    int updateByExample(@Param("record") SrcTest record, @Param("example") SrcTestExample example);
    int updateByPrimaryKeySelective(SrcTest record);
    int updateByPrimaryKey(SrcTest record);}
复制代码

SrcTestMapperExt.java



package com.test.dao.mapper.srctest;
import com.test.dao.model.srctest.SrcTest;import org.apache.ibatis.annotations.Param;
import javax.annotation.Resource;import java.util.List;

/*** SrcTestMapperExt接口* Created by shirenchuang on 2018/6/30.*/@Resourcepublic interface SrcTestMapperExt extends SrcTestMapper {

    List<SrcTest> selectExtTest(@Param("age") int  age);
}

复制代码

SrcTestMapper.xml



<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.test.dao.mapper.srctest.SrcTestMapperExt">  <resultMap id="BaseResultMap" type="com.test.dao.model.srctest.SrcTest">    <id column="id" jdbcType="INTEGER" property="id" />    <result column="name" jdbcType="VARCHAR" property="name" />    <result column="age" jdbcType="INTEGER" property="age" />    <result column="ctime" jdbcType="BIGINT" property="ctime" />  </resultMap><!-- 省略....--></mapper>
复制代码

SrcTestMapperExt.xml



<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.test.dao.mapper.srctest.SrcTestMapperExt">    <select id="selectExtTest" resultMap="BaseResultMap">        select * from src_test where age>#{age}    </select>
</mapper>
复制代码

注意:这里返回的 resultMap="BaseResultMap" 这个 Map 并没有再这个 xml 中定义,这样能使用吗?

上面是我生成的代码;并且能够正常使用;

那么 SrcTestMapperExt.xml 是如何继承 SrcTestMapper.xml 中的定义的呢?

1. 修改命名空间,使他们的命名空间相同,namespace="com.test.dao.mapper.srctest.SrcTestMapperExt"


2. 光这样还不够,因为这个时候你去运行的时候会报错



Caused by: org.apache.ibatis.builder.BuilderException: Wrong namespace. Expected 'com.test.dao.mapper.srctest.SrcTestMapper' but found 'com.test.dao.mapper.srctest.SrcTestMapperExt'.
复制代码

因为 Mybatis 中是必须要 xml 的文件包名和文件名必须跟 Mapper.java 对应起来的比如 com.test.dao.mapper.srctest.SrcTestMapper.java 这个相对应的是 com.test.dao.mapper.srctest.SrcTestMapper.xml 必须是这样子,没有例外,否则就会报错 show the codeMapperBuilderAssistant

  public void setCurrentNamespace(String currentNamespace) {    if (currentNamespace == null) {      throw new BuilderException("The mapper element requires a namespace attribute to be specified.");    }
    if (this.currentNamespace != null && !this.currentNamespace.equals(currentNamespace)) {      throw new BuilderException("Wrong namespace. Expected '"          + this.currentNamespace + "' but found '" + currentNamespace + "'.");    }
    this.currentNamespace = currentNamespace;  }
复制代码

这个 this.currentNamespace 和参数传进来的 currentNamespace 比较是否相等;参数传进来的 currentNamespace 就是我们 xml 中的<mapper namespace="com.test.dao.mapper.srctest.SrcTestMapperExt">值;然后 this.currentNamespace 是从哪里设置的呢?this.currentNamespace = currentNamespace;跟下代码:MapperAnnotationBuilder

  public void parse() {    String resource = type.toString();    if (!configuration.isResourceLoaded(resource)) {      loadXmlResource();      configuration.addLoadedResource(resource);      assistant.setCurrentNamespace(type.getName());      parseCache();      parseCacheRef();      Method[] methods = type.getMethods();      for (Method method : methods) {        try {          // issue #237          if (!method.isBridge()) {            parseStatement(method);          }        } catch (IncompleteElementException e) {          configuration.addIncompleteMethod(new MethodResolver(this, method));        }      }    }    parsePendingMethods();  }
复制代码

看到 assistant.setCurrentNamespace(type.getName());它获取的是 type.getName() ;这个 type 的最终来源是 MapperFactoryBean

  @Override  protected void checkDaoConfig() {    super.checkDaoConfig();
    notNull(this.mapperInterface, "Property 'mapperInterface' is required");
    Configuration configuration = getSqlSession().getConfiguration();    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {      try {        configuration.addMapper(this.mapperInterface);      } catch (Exception e) {        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);        throw new IllegalArgumentException(e);      } finally {        ErrorContext.instance().reset();      }    }  }
复制代码

configuration.addMapper(this.mapperInterface);这行应该就明白了加载 mapperInterface 的时候会跟相应的 xml 映射,并且会去检验 namespace 是否跟 mapperInterface 相等!

那么既然命名空间不能修改,那第一条不白说了?还怎么实现 Mapper.xml 的继承啊?别慌,既然是这样子,那我们可以让 MapperInterface 中的 SrcTestMapper.java 别被加载进来就行了啊!!只加载 MapperExt.java 不就行了?

3. 修改 applicationContext.xml,让 Mapper.java 不被扫描


Mapper.java 接口扫描配置

    <!-- Mapper接口所在包名,Spring会自动查找其下的Mapper -->    <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">        <property name="basePackage" value="com.test.dao.mapper"/><!--        该属性实际上就是起到一个过滤的作用,如果设置了该属性,那么MyBatis的接口只有包含该注解,才会被扫描进去。-->        <property name="annotationClass" value="javax.annotation.Resource"/>        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>    </bean>
复制代码

basePackage 把 Mapper.java 扫描进去没有关系,重点是<property name="annotationClass" value="javax.annotation.Resource"/>这样 MapperScanner 会把没有配置注解的过滤掉;回头看我们的 MapperExt.java 配置文件是有加上注解的

/*** SrcTestMapperExt接口* Created by shirenchuang on 2018/6/30.*/@Resourcepublic interface SrcTestMapperExt extends SrcTestMapper {    List<SrcTest> selectExtTest(@Param("age") int  age);}
复制代码

这样子之后,基本上问题就解决了,还有一个地方特别要注意一下的是.xml 文件的配置

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">        <property name="dataSource" ref="dataSource"/>        <property name="configLocation" value="classpath:mybatis-config.xml"/>        <!-- 必须将mapper,和mapperExt也一起扫描-->        <property name="mapperLocations" value="classpath:com/test/dao/mapper/**/*.xml"/>    </bean>
复制代码

这样配置没有错,但是我之前的配置写成了<property name="mapperLocations" value="classpath:com/test/dao/mapper/**/*Mapper.xml"/>

这样子 MapperExt.xml 没有被扫描进去,在我执行单元测试的时候

  @Test    public void selectExt(){        List<SrcTest> tests = srcTestService.selectExtTest(9);        System.out.println(tests.toString());    }
复制代码

err_console

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.test.dao.mapper.srctest.SrcTestMapperExt.selectExtTest

复制代码

但是执行 ````srcTestService.insertSelective(srcTest);不会出错 原因就是 insertSelective是在SrcTestMapper.xml中存在 ,已经被注册到com.test.dao.mapper.srctest.SrcTestMapperExt```命名空间了,但是 selectExtTest 由于没有被注册,所以报错了;

发布于: 刚刚阅读数: 3
用户头像

关注公众号: 石臻臻的杂货铺 获取最新文章 2019-09-06 加入

进高质量滴滴技术交流群,只交流技术不闲聊 加 szzdzhp001 进群 20w字《Kafka运维与实战宝典》PDF下载请关注公众号:石臻臻的杂货铺

评论

发布
暂无评论
【Mybatis】如何继承Mybatis中的Mapper.xml文件_mybatis_石臻臻的杂货铺_InfoQ写作社区