1. 程式人生 > >【Mybatis】如何繼承Mybatis中的Mapper.xml檔案

【Mybatis】如何繼承Mybatis中的Mapper.xml檔案

最近在寫一個 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.
*/
@Resource
public 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 code
MapperBuilderAssistant

  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.
*/
@Resource
public 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由於沒有被註冊,所以報錯了;

有興趣可以下載閱讀或者直接使用我整合的
自動生成擴充套件外掛