真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

MyBatis通用Mapper實(shí)現(xiàn)原理及相關(guān)內(nèi)容

MyBatis通用Mapper實(shí)現(xiàn)原理

目前成都創(chuàng)新互聯(lián)已為成百上千家的企業(yè)提供了網(wǎng)站建設(shè)、域名、網(wǎng)頁(yè)空間、成都網(wǎng)站托管、企業(yè)網(wǎng)站設(shè)計(jì)、慶陽(yáng)網(wǎng)站維護(hù)等服務(wù),公司將堅(jiān)持客戶導(dǎo)向、應(yīng)用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長(zhǎng),共同發(fā)展。

本文會(huì)先介紹通用 Mapper 的簡(jiǎn)單原理,然后使用最簡(jiǎn)單的代碼來實(shí)現(xiàn)這個(gè)過程。

基本原理

通用 Mapper 提供了一些通用的方法,這些通用方法是以接口的形式提供的,例如。

public interface SelectMapper {
  /**
   * 根據(jù)實(shí)體中的屬性值進(jìn)行查詢,查詢條件使用等號(hào)
   */
  @SelectProvider(type = BaseSelectProvider.class, method = "dynamicSQL")
  List select(T record);
}

接口和方法都使用了泛型,使用該通用方法的接口需要指定泛型的類型。通過 Java 反射可以很容易得到接口泛型的類型信息,代碼如下。

Type[] types = mapperClass.getGenericInterfaces();
Class<?> entityClass = null;
for (Type type : types) {
  if (type instanceof ParameterizedType) {
    ParameterizedType t = (ParameterizedType) type;
    //判斷父接口是否為 SelectMapper.class
    if (t.getRawType() == SelectMapper.class) {
      //得到泛型類型
      entityClass = (Class<?>) t.getActualTypeArguments()[0];
      break;
    }
  }
}

實(shí)體類中添加的 JPA 注解只是一種映射實(shí)體和數(shù)據(jù)庫(kù)表關(guān)系的手段,通過一些默認(rèn)規(guī)則或者自定義注解也很容易設(shè)置這種關(guān)系,獲取實(shí)體和表的對(duì)應(yīng)關(guān)系后,就可以根據(jù)通用接口方法定義的功能來生成和 XML 中一樣的 SQL 代碼。動(dòng)態(tài)生成 XML 樣式代碼的方式有很多,最簡(jiǎn)單的方式就是純 Java 代碼拼字符串,通用 Mapper 為了盡可能的少的依賴選擇了這種方式。如果使用模板(如FreeMarker,Velocity 和 beetl 等模板引擎)實(shí)現(xiàn),自由度會(huì)更高,也能方便開發(fā)人員調(diào)整。

在 MyBatis 中,每一個(gè)方法(注解或 XML 方式)經(jīng)過處理后,最終會(huì)構(gòu)造成 MappedStatement 實(shí)例,這個(gè)對(duì)象包含了方法id(namespace+id)、結(jié)果映射、緩存配置、SqlSource 等信息,和 SQL 關(guān)系最緊密的是其中的 SqlSource,MyBatis 最終執(zhí)行的 SQL 時(shí)就是通過這個(gè)接口的 getBoundSql 方法獲取的。

在 MyBatis 中,使用@SelectProvider 這種方式定義的方法,最終會(huì)構(gòu)造成 ProviderSqlSource,ProviderSqlSource 是一種處于中間的 SqlSource,它本身不能作為最終執(zhí)行時(shí)使用的 SqlSource,但是他會(huì)根據(jù)指定方法返回的 SQL 去構(gòu)造一個(gè)可用于最后執(zhí)行的 StaticSqlSource,StaticSqlSource的特點(diǎn)就是靜態(tài) SQL,支持在 SQL 中使用#{param} 方式的參數(shù),但是不支持 , 等標(biāo)簽。

為了能根據(jù)實(shí)體類動(dòng)態(tài)生成支持動(dòng)態(tài) SQL 的方法,通用 Mapper 從這里入手,利用ProviderSqlSource 可以生成正常的 MappedStatement,可以直接利用 MyBatis 各種配置和命名空間的特點(diǎn)(這是通用 Mapper 選擇這種方式的主要原因)。在生成 MappedStatement 后,“過河拆橋” 般的利用完就把 ProviderSqlSource 替換掉了,正常情況下,ProviderSqlSource 根本就沒有執(zhí)行的機(jī)會(huì)。在通用 Mapper 定義的實(shí)現(xiàn)方法中,提供了 MappedStatement 作為參數(shù),有了這個(gè)參數(shù),我們就可以根據(jù) ms 的 id(規(guī)范情況下是 接口名.方法名)得到接口,通過接口的泛型可以獲取實(shí)體類(entityClass),根據(jù)實(shí)體和表的關(guān)系我們可以拼出 XML 方式的動(dòng)態(tài) SQL,一個(gè)簡(jiǎn)單的方法如下。

/**
 * 查詢?nèi)拷Y(jié)果
 * @param ms
 * @return
 */
public String selectAll(MappedStatement ms) {
  final Class<?> entityClass = getEntityClass(ms);
  //修改返回值類型為實(shí)體類型
  setResultType(ms, entityClass);
  StringBuilder sql = new StringBuilder();
  sql.append(SqlHelper.selectAllColumns(entityClass));
  sql.append(SqlHelper.fromTable(entityClass, tableName(entityClass)));
  sql.append(SqlHelper.orderByDefault(entityClass));
  return sql.toString();
}

拼出的 XML 形式的動(dòng)態(tài) SQL,使用 MyBatis 的 XMLLanguageDriver 中的 createSqlSource 方法可以生成 SqlSource。然后使用反射用新的 SqlSource 替換ProviderSqlSource 即可,如下代碼。

/**
 * 重新設(shè)置SqlSource
 * @param ms
 * @param sqlSource
 */
protected void setSqlSource(MappedStatement ms, SqlSource sqlSource) {
  MetaObject msObject = SystemMetaObject.forObject(ms);
  msObject.setValue("sqlSource", sqlSource);
}

MetaObject 是MyBatis 中很有用的工具類,MyBatis 的結(jié)果映射就是靠這種方式實(shí)現(xiàn)的。反射信息使用的 DefaultReflectorFactory,這個(gè)類會(huì)緩存反射信息,因此 MyBatis 的結(jié)果映射的效率很高。

到這里核心的內(nèi)容都已經(jīng)說完了,雖然知道怎么去替換 SqlSource了,但是!什么時(shí)候去替換呢?

這一直都是一個(gè)難題,如果不大量重寫 MyBatis 的代碼很難萬無一失的完成這個(gè)任務(wù)。通用 Mapper 并沒有去大量重寫,主要是考慮到以后的升級(jí),也因此在某些特殊情況下,通用 Mapper 的方法會(huì)在沒有被替換的情況下被調(diào)用,這個(gè)問題在將來的 MyBatis 3.5.x 版本中會(huì)以更友好的方式解決(目前的 ProviderSqlSource 已經(jīng)比以前能實(shí)現(xiàn)更多的東西,后面會(huì)講)。

針對(duì)不同的運(yùn)行環(huán)境,需要用不同的方式去替換。當(dāng)使用純 MyBatis (沒有Spring)方式運(yùn)行時(shí),替換很簡(jiǎn)單,因?yàn)闀?huì)在系統(tǒng)中初始化 SqlSessionFactory,可以初始化的時(shí)候進(jìn)行替換,這個(gè)時(shí)候也不會(huì)出現(xiàn)前面提到的問題。替換的方式也很簡(jiǎn)單,通過 SqlSessionFactory 可以得到 SqlSession,然后就能得到 Configuration,通過 configuration.getMappedStatements() 就能得到所有的 MappedStatement,循環(huán)判斷其中的方法是否為通用接口提供的方法,如果是就按照前面的方式替換就可以了。

在使用 Spring 的情況下,以繼承的方式重寫了 MapperScannerConfigurer 和 MapperFactoryBean,在 Spring 調(diào)用 checkDaoConfig 的時(shí)候?qū)?SqlSource 進(jìn)行替換。在使用 Spring Boot 時(shí),提供的 mapper-starter 中,直接注入 List sqlSessionFactoryList 進(jìn)行替換。

下面我們按照這個(gè)思路,以最簡(jiǎn)練的代碼,實(shí)現(xiàn)一個(gè)通用方法。

實(shí)現(xiàn)一個(gè)簡(jiǎn)單的通用Mapper

1. 定義通用接口方法

public interface BaseMapper {
  @SelectProvider(type = SelectMethodProvider.class, method = "select")
  List select(T entity);
}

這里定義了一個(gè)簡(jiǎn)單的 select 方法,這個(gè)方法判斷參數(shù)中的屬性是否為空,不為空的字段會(huì)作為查詢條件進(jìn)行查詢,下面是對(duì)應(yīng)的 Provider。

public class SelectMethodProvider {
  public String select(Object params) {
    return "什么都不是!";
  }
}

這里的 Provider 不會(huì)最終執(zhí)行,只是為了在初始化時(shí)可以生成對(duì)應(yīng)的 MappedStatement。

2. 替換 SqlSource

下面代碼為了簡(jiǎn)單,都指定的 BaseMapper 接口,并且沒有特別的校驗(yàn)。

public class SimpleMapperHelper {
  public static final XMLLanguageDriver XML_LANGUAGE_DRIVER
      = new XMLLanguageDriver();
  /**
   * 獲取泛型類型
   */
  public static Class getEntityClass(Class<?> mapperClass){
    Type[] types = mapperClass.getGenericInterfaces();
    Class<?> entityClass = null;
    for (Type type : types) {
      if (type instanceof ParameterizedType) {
        ParameterizedType t = (ParameterizedType) type;
        //判斷父接口是否為 BaseMapper.class
        if (t.getRawType() == BaseMapper.class) {
          //得到泛型類型
          entityClass = (Class<?>) t.getActualTypeArguments()[0];
          break;
        }
      }
    }
    return entityClass;
  }
  /**
   * 替換 SqlSource
   */
  public static void changeMs(MappedStatement ms) throws Exception {
    String msId = ms.getId();
    //標(biāo)準(zhǔn)msId為 包名.接口名.方法名
    int lastIndex = msId.lastIndexOf(".");
    String methodName = msId.substring(lastIndex + 1);
    String interfaceName = msId.substring(0, lastIndex);
    Class<?> mapperClass = Class.forName(interfaceName);
    //判斷是否繼承了通用接口
    if(BaseMapper.class.isAssignableFrom(mapperClass)){
      //判斷當(dāng)前方法是否為通用 select 方法
      if (methodName.equals("select")) {
        Class entityClass = getEntityClass(mapperClass);
        //必須使用");
        //解析 sqlSource
        SqlSource sqlSource = XML_LANGUAGE_DRIVER.createSqlSource(
            ms.getConfiguration(), sqlBuilder.toString(), entityClass);
        //替換
        MetaObject msObject = SystemMetaObject.forObject(ms);
        msObject.setValue("sqlSource", sqlSource);
      }
    }
  }
}

changeMs 方法簡(jiǎn)單的從 msId 開始,獲取接口和實(shí)體信息,通過反射回去字段信息,使用 標(biāo)簽動(dòng)態(tài)判斷屬性值,這里的寫法和 XML 中一樣,使用 XMLLanguageDriver 處理時(shí)需要在外面包上