mybatis SelectKey解析

来源:https://www.cnblogs.com/monianxd/archive/2022/07/15/16481802.html
-Advertisement-
Play Games

1.selectKey介紹及作用 <selectKey>標簽有如下屬性 resultType:sql返回的java類型 statementType:STATEMENT|PREPARED|CALLABLE三種預設PREPARED keyProperty:列名對應的java屬性名,可逗號分隔 keyCo ...


1.selectKey介紹及作用 

<selectKey>標簽有如下屬性 

resultType:sql返回的java類型

statementType:STATEMENT|PREPARED|CALLABLE三種預設PREPARED

keyProperty:列名對應的java屬性名,可逗號分隔

keyColumn:列名,可逗號分隔

order:BEFORE|AFTER,BEFORE表示<selectKey>里的sql先執行然後再把獲取到的值進行設置,AFTER則表示後執行,獲取自增主鍵並設置肯定是需要用AFTER的,畢竟先等主sql插入才能獲取到自增Id~

databaseId:資料庫Id一般不需要填

mybatis的<selectKey>標簽主要可以用來獲取自增主鍵id的值併進行設置,SELECT LAST_INSERT_ID() 該sql的作用返回最近一次插入的id通常用來配合<selectKey>標簽來使用 ,但要註意假如用insert同時插入多條sql,其只能返回插入的第一條記錄的自增主鍵id因此<selectKey>是不支持批量插入獲取主鍵值的

 

2.selectKey測試及解析

測試代碼

#mapper
int insert(UserDO userDO);

#mapper.xml
  <insert id="insert">
     <selectKey keyProperty="userId" keyColumn="user_id" order="AFTER" resultType="integer">
       select last_insert_id()
     </selectKey>
     insert into user(username, password, nickname)
     values(#{username}, #{password}, #{nickname})
  </insert>
  

#java測試代碼
public class Test {

  public static void main(String[] args) throws IOException {

    try (InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml")) {
      // 構建session工廠 DefaultSqlSessionFactory
      SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
      SqlSession sqlSession = sqlSessionFactory.openSession();
      UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
      UserDO userDO = new UserDO();
      userDO.setUsername("monian");
      userDO.setPassword("123");
      userDO.setNickname("monianx");
      userMapper.insert(userDO);
      System.out.println("自增主鍵userId:" + userDO.getUserId());
    }
  }

}

從輸出可以看到成功獲取到自增主鍵userId並已經設置到userDO參數對象中了,下麵來看看<selectKey>具體解析

public class PreparedStatementHandler extends BaseStatementHandler {

  public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
  }

  @Override
  public int update(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    int rows = ps.getUpdateCount();
    Object parameterObject = boundSql.getParameterObject();
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
    return rows;
  }
}

當為<insert><delete><update>標簽時會調用此update方法,執行完sql後調用 keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);  而根據<selectKey>標簽解析出來的keyGenerator為SelectKeyGenerator,下麵具體分析下這個類它是怎麼獲取主鍵值並設置的。

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) {
    // 主sql前面執行還是後面執行
    this.executeBefore = executeBefore;
    this.keyStatement = keyStatement;
  }

  @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) {
        // 獲取需要設置的屬性值 如id
        String[] keyProperties = keyStatement.getKeyProperties();
        final Configuration configuration = ms.getConfiguration();
        final MetaObject metaParam = configuration.newMetaObject(parameter);
        // Do not close keyExecutor.
        // The transaction will be closed by parent executor.
        Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE);
        // 查詢sql如 select last_insert_id()獲取主鍵id
        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));
          // 將主鍵id的值設置到parameter參數中
          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);
    }
  }

  private void handleMultipleProperties(String[] keyProperties,
      MetaObject metaParam, MetaObject metaResult) {
    String[] keyColumns = keyStatement.getKeyColumns();

    if (keyColumns == null || keyColumns.length == 0) {
      // no key columns specified, just use the property names
      for (String keyProperty : keyProperties) {
        setValue(metaParam, keyProperty, metaResult.getValue(keyProperty));
      }
    } else {
      if (keyColumns.length != keyProperties.length) {
        throw new ExecutorException("If SelectKey has key columns, the number must match the number of key properties.");
      }
      for (int i = 0; i < keyProperties.length; i++) {
        setValue(metaParam, keyProperties[i], metaResult.getValue(keyColumns[i]));
      }
    }
  }

  private void setValue(MetaObject metaParam, String property, Object value) {
    if (metaParam.hasSetter(property)) {
      metaParam.setValue(property, value);
    } else {
      throw new ExecutorException("No setter found for the keyProperty '" + property + "' in " + metaParam.getOriginalObject().getClass().getName() + ".");
    }
  }
}

可以看到上述代碼會先查詢sql獲取返回結果之後再把值設置到參數對象中,但可以看到當查詢結果value.size() > 1的時候就會拋出異常,因此<selectKey>標簽中的sql返回行數不能大於1。從這邊也能看出<selectKey>不支持批量獲取主鍵值

3. useGeneratedKeys 

那麼有什麼辦法可以獲取到批量插入的主鍵id呢,答案是有的可以使用<insert>標簽中的useGeneratedKeys、keyProperty、keyColumn屬性進行設置 

#mapper
void batchInsert(@Param("userDOList") List<UserDO> userDOList);

#mapper.xml
    insert into user(username, password, nickname)
    values
    <foreach collection="userDOList" item="userDO" separator=",">
      (#{userDO.username}, #{userDO.password}, #{userDO.nickname})
    </foreach>
  </insert>
  
#java測試代碼 
public class Test {

  public static void main(String[] args) throws IOException {

    try (InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml")) {
      // 構建session工廠 DefaultSqlSessionFactory
      SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
      SqlSession sqlSession = sqlSessionFactory.openSession();
      UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
      UserDO userDO = new UserDO();
      userDO.setUsername("monian");
      userDO.setPassword("123");
      userDO.setNickname("monianx");
      UserDO userDO1 = new UserDO();
      userDO1.setUsername("monian");
      userDO1.setPassword("123");
      userDO1.setNickname("monianx");
      userMapper.batchInsert(Arrays.asList(userDO, userDO1));
      System.out.println("自增主鍵userId:" + Arrays.asList(userDO.getUserId(), userDO1.getUserId()));
    }
  }

}

輸出結果可以看到批量插入成功獲取到主鍵userId的值了,原理的話感興趣的同學可以去閱讀下Jdbc3KeyGenerator這個類的源碼,這裡就不細說啦

 

4.selectKey和useGeneratedKeys

最後談談筆者對這兩個的理解,selectKey可以自定義查詢的sql更加的靈活不單單隻是獲取自增主鍵但查詢結果行數不能有多行否則會拋出異常,而useGeneratorKeys主要是獲取自動生成主鍵且能支持多行支持批量插入獲取主鍵值,至於在實際開發中使用哪一種就看業務需求是怎樣的了。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 覓長生 for Mac是一款以傳統道家文化與修真為題材的修仙角色扮演游戲,玩家在覓長生mac版游戲中可以體驗從零開始,一步步積攢修為突破境界的逆天修仙之旅。覓長生mac版玩法非常簡單,戰鬥目前採用了卡牌對戰方式,讓玩家能夠輕鬆駕馭游戲! 詳情:覓長生 for Mac(修仙角色扮演游戲) 修仙小說是我 ...
  • FontLab VI Mac版是Mac平臺上的一款字體編輯器應用。FontLab VI Mac版是專為字體鑄造廠、專業設計師、印刷和平面設計工作室設計的軟體。 詳情:FontLab 8 for mac(字體編輯器應用) 功能介紹 1、下一代繪圖 創造Bézier曲線不再是“精通或神秘”。 2、刷子和 ...
  • AIrBuddy Mac版是Mac平臺上的一款可以幫助用戶更好的體驗和使用AIrPods及Beats無線耳機的應用。只需點擊一下,即可連接並將Mac的音頻播放到AIrPods。它還可以確保Mac的音頻輸入不會切換到AIrPods,這樣您就可以獲得最佳質量。 詳情:AirBuddy for Mac(A ...
  • 鏡像下載、功能變數名稱解析、時間同步請點擊 阿裡雲開源鏡像站 一、paramiko模塊簡介 paramiko是一個用於做遠程式控制制的模塊,使用該模塊可以對遠程伺服器進行命令或文件操作,基於linux中的ssh服務 。paramiko是第三方模塊,需要我們單獨安裝。通過paramiko模塊,我們可以利用pyth ...
  • 鏡像下載、功能變數名稱解析、時間同步請點擊 阿裡雲開源鏡像站 前言 出現這種bug Unit mysql.service could not be found 因為bug被我修複好了,在這裡引用一下網友的bug截圖 1. 原理 之所以使用命令行service mysqld status出現Unit mysq ...
  • SecureFX for Mac是一款跨平臺文件傳輸客戶端軟體,有著易用的、類似於資源管理器的用戶界面。securefx mac可以更加有效的實現文件的安全傳輸,您可以使用其新的拖放功能直接將文件拖至Windows Explorer和其他程式中,用戶也可以充分利用SecureFX for mac的自 ...
  • 基礎知識 進程 內核的功能和作用:文件系統管理、網路管理、進程管理、記憶體管理等,屬於linux最基礎的功能 進程:process,正在運行中的程式的一個副本。允許有多個進程同時執行。 #操作系統負責分配cpu運行進程的順序和時間 #副本:把磁碟上的指定文件載入到記憶體進行運行 運行多次就會有多個副本 ...
  • 在SQL Server中有幾種方法可以找到活動的 SQL 連接。讓我們看看一些使用 T-SQL 查詢的簡單快捷的方法。 SP_WHO SP_WHO 是 SQL Server 內置的系統存儲過程, 其他方法相比,SP_WHO 將具有最少的列,但是一種快速列出活動連接的方法。 以下是在 SQL Serv ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...