MyBatis印象閱讀之KeyGenerator解析

在上一章內(nèi)容中我們又欠下了這些技術(shù)債:

parameterHandler
resultSetHandler
KeyGenerator

今天我們就來著重解決一下關(guān)于KeyGenerator的源碼藐唠。

1. KeyGenerator解析

首先我們需要了解這個(gè)類主要功能甸怕。我們來看官網(wǎng)介紹:
selectKey 元素中的語句將會首先或之后運(yùn)行胰舆,然后插入語句會被調(diào)用硕旗。這可以提供給你一個(gè)與數(shù)據(jù)庫中自動生成主鍵類似的行為,同時(shí)保持了 Java 代碼的簡潔晃痴。
我們再來看下有哪些屬性:


selectKey 元素的屬性

介紹完之后我們再來看下源碼绞佩,先來看接口源碼冬阳,其中的方法對應(yīng)order屬性的順序:

public interface KeyGenerator {

  void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);

  void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);

}

他子類繼承順序:
KeyGennerator子類繼承

我們以一個(gè)例子來進(jìn)行說明:

@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert({"insert into country (countryname,countrycode) values (#{countryname},#{countrycode})"})
int insertBean(Country country);

然后我們來看一處源碼,看用的是KeyGenerator的哪個(gè)子類媳谁。在MappedStatement的Builder內(nèi)部類中:

      mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;

我們可以看到用的是Jdbc3KeyGenerator這個(gè)類涂滴。

2. Jdbc3KeyGenerator解析

首先我們先來看類構(gòu)造方法:

public class Jdbc3KeyGenerator implements KeyGenerator {

  /**
   * A shared instance.
   *
   * @since 3.4.3
   */
  public static final Jdbc3KeyGenerator INSTANCE = new Jdbc3KeyGenerator();

這里可以看到我們用了單例模式中的餓漢式。再來看他的實(shí)現(xiàn)方法:

  @Override
  public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
    // do nothing
  }

  @Override
  public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
    processBatch(ms, stmt, parameter);
  }

繼續(xù)深入:

  public void processBatch(MappedStatement ms, Statement stmt, Object parameter) {
    // 獲得主鍵屬性的配置晴音。如果為空柔纵,則直接返回,說明不需要主鍵
    final String[] keyProperties = ms.getKeyProperties();
    if (keyProperties == null || keyProperties.length == 0) {
      return;
    }
    try (ResultSet rs = stmt.getGeneratedKeys()) {
      // 獲得返回的自增主鍵
      final ResultSetMetaData rsmd = rs.getMetaData();
      final Configuration configuration = ms.getConfiguration();
      if (rsmd.getColumnCount() < keyProperties.length) {
        // Error?
      } else {
        //賦值自增主鍵值
        assignKeys(configuration, rs, rsmd, keyProperties, parameter);
      }
    } catch (Exception e) {
      throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
    }
  }

那么重點(diǎn)來了:


  private void assignKeys(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd, String[] keyProperties,
      Object parameter) throws SQLException {
    if (parameter instanceof ParamMap || parameter instanceof StrictMap) {
      // Multi-param or single param with @Param
      assignKeysToParamMap(configuration, rs, rsmd, keyProperties, (Map<String, ?>) parameter);
    } else if (parameter instanceof ArrayList && !((ArrayList<?>) parameter).isEmpty()
        && ((ArrayList<?>) parameter).get(0) instanceof ParamMap) {
      // Multi-param or single param with @Param in batch operation
      assignKeysToParamMapList(configuration, rs, rsmd, keyProperties, ((ArrayList<ParamMap<?>>) parameter));
    } else {
      // Single param without @Param
      assignKeysToParam(configuration, rs, rsmd, keyProperties, parameter);
    }
  }

我們按照順序來進(jìn)行分析锤躁,首先便是多@Param參數(shù)情況下搁料,會形成一個(gè)ParamMap,之后調(diào)用 assignKeysToParamMap方法:

  private void assignKeysToParamMap(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd,
      String[] keyProperties, Map<String, ?> paramMap) throws SQLException {
    //如果參數(shù)為空直接返回
    if (paramMap.isEmpty()) {
      return;
    }
    Map<String, Entry<Iterator<?>, List<KeyAssigner>>> assignerMap = new HashMap<>();
    for (int i = 0; i < keyProperties.length; i++) {
      //獲取組成形成KeyAssigner
      Entry<String, KeyAssigner> entry = getAssignerForParamMap(configuration, rsmd, i + 1, paramMap, keyProperties[i],
          keyProperties, true);
      Entry<Iterator<?>, List<KeyAssigner>> iteratorPair = assignerMap.computeIfAbsent(entry.getKey(),
          k -> entry(collectionize(paramMap.get(k)).iterator(), new ArrayList<>()));
      iteratorPair.getValue().add(entry.getValue());
    }
    long counter = 0;
    while (rs.next()) {
      for (Entry<Iterator<?>, List<KeyAssigner>> pair : assignerMap.values()) {
        if (!pair.getKey().hasNext()) {
          throw new ExecutorException(String.format(MSG_TOO_MANY_KEYS, counter));
        }
        Object param = pair.getKey().next();
        //反射賦值
        pair.getValue().forEach(x -> x.assign(rs, param));
      }
      counter++;
    }
  }

大概邏輯就是整理完之后挨個(gè)遍歷賦值系羞,這里重點(diǎn)便是getAssignerForParamMap方法郭计,我們進(jìn)入一起看下:


 private Entry<String, KeyAssigner> getAssignerForParamMap(Configuration config, ResultSetMetaData rsmd,
      int columnPosition, Map<String, ?> paramMap, String keyProperty, String[] keyProperties, boolean omitParamName) {
    //判斷paramMap是否只有一個(gè)唯一key
    boolean singleParam = paramMap.values().stream().distinct().count() == 1;
    //獲取keyProperty的.的位置
    int firstDot = keyProperty.indexOf('.');
    //如果是多個(gè)參數(shù),但是keyProperty又不包含.椒振,會拋出錯(cuò)誤
    if (firstDot == -1) {
      if (singleParam) {
        return getAssignerForSingleParam(config, rsmd, columnPosition, paramMap, keyProperty, omitParamName);
      }
      throw new ExecutorException("Could not determine which parameter to assign generated keys to. "
          + "Note that when there are multiple parameters, 'keyProperty' must include the parameter name (e.g. 'param.id'). "
          + "Specified key properties are " + ArrayUtil.toString(keyProperties) + " and available parameters are "
          + paramMap.keySet());
    }
    //多參數(shù)昭伸,需要截取keyProperty的.之前部分,去paramMap拿對應(yīng)值
    String paramName = keyProperty.substring(0, firstDot);
    if (paramMap.containsKey(paramName)) {
      String argParamName = omitParamName ? null : paramName;
      String argKeyProperty = keyProperty.substring(firstDot + 1);
      return entry(paramName, new KeyAssigner(config, rsmd, columnPosition, argParamName, argKeyProperty));
    } else if (singleParam) {
      return getAssignerForSingleParam(config, rsmd, columnPosition, paramMap, keyProperty, omitParamName);
    } else {
      throw new ExecutorException("Could not find parameter '" + paramName + "'. "
          + "Note that when there are multiple parameters, 'keyProperty' must include the parameter name (e.g. 'param.id'). "
          + "Specified key properties are " + ArrayUtil.toString(keyProperties) + " and available parameters are "
          + paramMap.keySet());
    }
  }

根據(jù)注釋澎迎,我們能看懂個(gè)大概庐杨,之后就進(jìn)入getAssignerForSingleParam方法:

 private Entry<String, KeyAssigner> getAssignerForSingleParam(Configuration config, ResultSetMetaData rsmd,
      int columnPosition, Map<String, ?> paramMap, String keyProperty, boolean omitParamName) {
    // Assume 'keyProperty' to be a property of the single param.
    String singleParamName = nameOfSingleParam(paramMap);
    String argParamName = omitParamName ? null : singleParamName;
    return entry(singleParamName, new KeyAssigner(config, rsmd, columnPosition, argParamName, keyProperty));
  }

還記得之前說的遍歷賦值邏輯么选调,也就是

pair.getValue().forEach(x -> x.assign(rs, param));方法中賦值:

我們來看下這個(gè)方法:


protected void assign(ResultSet rs, Object param) {
      if (paramName != null) {
        // If paramName is set, param is ParamMap
        param = ((ParamMap<?>) param).get(paramName);
      }
      MetaObject metaParam = configuration.newMetaObject(param);
      try {
        if (typeHandler == null) {
          if (metaParam.hasSetter(propertyName)) {
            Class<?> propertyType = metaParam.getSetterType(propertyName);
            typeHandler = typeHandlerRegistry.getTypeHandler(propertyType,
                JdbcType.forCode(rsmd.getColumnType(columnPosition)));
          } else {
            throw new ExecutorException("No setter found for the keyProperty '" + propertyName + "' in '"
                + metaParam.getOriginalObject().getClass().getName() + "'.");
          }
        }
        if (typeHandler == null) {
          // Error?
        } else {
          Object value = typeHandler.getResult(rs, columnPosition);
          metaParam.setValue(propertyName, value);
        }
      } catch (SQLException e) {
        throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e,
            e);
      }
    }
  }

這里就是借助一些輔助的類來進(jìn)行反射賦值了,邏輯自己整理下不難灵份。下面我們也過一下SelectKeyGenerator類仁堪。

3. SelectKeyGenerator解析

我們先繼續(xù)來看構(gòu)造方法:

public class SelectKeyGenerator implements KeyGenerator {

  public static final String SELECT_KEY_SUFFIX = "!selectKey";
  private final boolean executeBefore;
  private final MappedStatement keyStatement;

  public SelectKeyGenerator(MappedStatement keyStatement, boolean executeBefore) {
    this.executeBefore = executeBefore;
    this.keyStatement = keyStatement;
  }
}

之后我們整體再來看一下它的繼承實(shí)現(xiàn),我們只是簡單的過一下各吨,不會具體深入:

@Override
  public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
    if (executeBefore) {
      processGeneratedKeys(executor, ms, parameter);
    }
  }

  @Override
  public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
    if (!executeBefore) {
      processGeneratedKeys(executor, ms, parameter);
    }
  }

  private void processGeneratedKeys(Executor executor, MappedStatement ms, Object parameter) {
    try {
      if (parameter != null && keyStatement != null && keyStatement.getKeyProperties() != null) {
        String[] keyProperties = keyStatement.getKeyProperties();
        final Configuration configuration = ms.getConfiguration();
        final MetaObject metaParam = configuration.newMetaObject(parameter);
        if (keyProperties != null) {
          // Do not close keyExecutor.
          // The transaction will be closed by parent executor.
          Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE);
          List<Object> values = keyExecutor.query(keyStatement, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);
          if (values.size() == 0) {
            throw new ExecutorException("SelectKey returned no data.");
          } else if (values.size() > 1) {
            throw new ExecutorException("SelectKey returned more than one value.");
          } else {
            MetaObject metaResult = configuration.newMetaObject(values.get(0));
            if (keyProperties.length == 1) {
              if (metaResult.hasGetter(keyProperties[0])) {
                setValue(metaParam, keyProperties[0], metaResult.getValue(keyProperties[0]));
              } else {
                // no getter for the property - maybe just a single value object
                // so try that
                setValue(metaParam, keyProperties[0], values.get(0));
              }
            } else {
              handleMultipleProperties(keyProperties, metaParam, metaResult);
            }
          }
        }
      }
    } catch (ExecutorException e) {
      throw e;
    } catch (Exception e) {
      throw new ExecutorException("Error selecting key or setting result to parameter object. Cause: " + e, e);
    }
  }

這里主要SQL之前還是之后的都會到相同的方法枝笨,方法內(nèi)部就是新建一個(gè)Executor,然后向數(shù)據(jù)庫發(fā)送一條簡單的sql執(zhí)行語句揭蜒,返回內(nèi)容則賦值到參數(shù)中横浑。

最后還剩一個(gè)NoKeyGenerator

4. NoKeyGenerator解析

public class NoKeyGenerator implements KeyGenerator {

  /**
   * A shared instance.
   * @since 3.4.3
   */
  public static final NoKeyGenerator INSTANCE = new NoKeyGenerator();

  @Override
  public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
    // Do Nothing
  }

  @Override
  public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
    // Do Nothing
  }

}

這個(gè)類就比較簡單了,沒什么特殊作用屉更。

5. 今日總結(jié)

今天我們接觸了關(guān)于KeyGenerator的源碼徙融,理解了他們的作用,我們再來做下小結(jié):

Jdbc3KeyGenerator:只能在執(zhí)行SQL之后瑰谜,它是一起的欺冀,通過業(yè)務(wù)SQL的執(zhí)行從中獲取數(shù)據(jù)庫自增主鍵信息并匹配復(fù)制
SelectKeyGenerator: 可以在執(zhí)行SQL之前或之后運(yùn)行,都是通過另起訪問一個(gè)簡單的SQL查詢到值并進(jìn)行注入
NoKeyGenerator: 什么操作都沒有

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末萨脑,一起剝皮案震驚了整個(gè)濱河市隐轩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌渤早,老刑警劉巖职车,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異鹊杖,居然都是意外死亡悴灵,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進(jìn)店門骂蓖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來积瞒,“玉大人,你說我怎么就攤上這事登下∶?祝” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵被芳,是天一觀的道長银酬。 經(jīng)常有香客問我,道長筐钟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任赋朦,我火速辦了婚禮篓冲,結(jié)果婚禮上李破,老公的妹妹穿的比我還像新娘。我一直安慰自己壹将,他們只是感情好嗤攻,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著诽俯,像睡著了一般妇菱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上暴区,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天闯团,我揣著相機(jī)與錄音,去河邊找鬼仙粱。 笑死房交,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的伐割。 我是一名探鬼主播候味,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼隔心!你這毒婦竟也來了白群?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤硬霍,失蹤者是張志新(化名)和其女友劉穎帜慢,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體须尚,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡崖堤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了耐床。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片密幔。...
    茶點(diǎn)故事閱讀 39,703評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖撩轰,靈堂內(nèi)的尸體忽然破棺而出胯甩,到底是詐尸還是另有隱情,我是刑警寧澤堪嫂,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布偎箫,位于F島的核電站,受9級特大地震影響皆串,放射性物質(zhì)發(fā)生泄漏淹办。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一恶复、第九天 我趴在偏房一處隱蔽的房頂上張望怜森。 院中可真熱鬧速挑,春花似錦、人聲如沸副硅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽恐疲。三九已至腊满,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間培己,已是汗流浹背碳蛋。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留漱凝,地道東北人疮蹦。 一個(gè)月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像茸炒,于是被迫代替她去往敵國和親愕乎。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容