中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

MyBatis-plus批量插入的通用方法是什么

發布時間:2023-04-10 16:15:03 來源:億速云 閱讀:109 作者:iii 欄目:開發技術

這篇文章主要介紹“MyBatis-plus批量插入的通用方法是什么”,在日常操作中,相信很多人在MyBatis-plus批量插入的通用方法是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”MyBatis-plus批量插入的通用方法是什么”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

1. MyBatis-plus 的批量保存方法

MyBatis-plus 中默認提供了一個批量保存數據到數據庫的方法,也就是 IService#saveBatch() 接口方法。這個方法的實現為 ServiceImpl#saveBatch(),其源碼實際處理的關鍵如下,從中可以知道 IService#saveBatch() 并不是一個真正的批量插入數據的方法

  1. 調用 ServiceImpl#sqlStatement() 使用 SqlMethod.INSERT_ONE 枚舉結合實體類確定一個全路徑方法名稱,這個名稱將用于匹配實體對應的庫表的單個插入方法的 MappedStatement 對象

  2. 調用 ServiceImpl#executeBatch() 方法遍歷 Entity 的集合,使用單個插入的方法為每個實體組裝一個 INSERT INTO 語句,遍歷結束后 flush,一次性將所有生成的 INSERT INTO 語句推給數據庫執行

舉例來說,如果調用 IService#saveBatch() 方法保存有2個元素的實體集合 List<Node> 數據到數據庫,其執行的 SQL 語句如下

存在 2 條:
INSERT INTO node (name, version) VALUES (&lsquo;nathan&rsquo;,1);
INSERT INTO node (name, version) VALUES (&lsquo;bob&rsquo;,1);

而如果是數據庫批量插入,其執行的 SQL 語句應該如下

只有 1 條:
INSERT INTO node (name, version) VALUES (&lsquo;nathan&rsquo;,1), (&lsquo;bob&rsquo;,1);

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean saveBatch(Collection<T> entityList, int batchSize) {
        String sqlStatement = sqlStatement(SqlMethod.INSERT_ONE);
        return executeBatch(entityList, batchSize, (sqlSession, entity) -> sqlSession.insert(sqlStatement, entity));
    }
    
    protected String sqlStatement(SqlMethod sqlMethod) {
        return SqlHelper.table(entityClass).getSqlStatement(sqlMethod.getMethod());
    }
    
    protected <E> boolean executeBatch(Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
        Assert.isFalse(batchSize < 1, "batchSize must not be less than one");
        return !CollectionUtils.isEmpty(list) && executeBatch(sqlSession -> {
            int size = list.size();
            int i = 1;
            for (E element : list) {
                consumer.accept(sqlSession, element);
                if ((i % batchSize == 0) || i == size) {
                    sqlSession.flushStatements();
                }
                i++;
            }
        });
    }

2. MyBatis-plus 的批量插入方法

2.1 通用批量插入方法 InsertBatchSomeColumn

事實上 MyBatis-plus 提供了真正的批量插入方法 InsertBatchSomeColumn,只不過這個方法只在 MySQL 數據庫下測試過,所以沒有將其作為默認通用方法添加到 SqlMethod 中

從其源碼實現不難看出,InsertBatchSomeColumn 其實就是提供了一個使用 foreach 標簽的 SQL 腳本,不了解這個標簽的讀者參考自定義批量插入大致理解即可

/**
 * 批量新增數據,自選字段 insert
 * <p> 不同的數據庫支持度不一樣!!!  只在 mysql 下測試過!!!  只在 mysql 下測試過!!!  只在 mysql 下測試過!!! </p>
 * <p> 除了主鍵是 <strong> 數據庫自增的未測試 </strong> 外理論上都可以使用!!! </p>
 * <p> 如果你使用自增有報錯或主鍵值無法回寫到entity,就不要跑來問為什么了,因為我也不知道!!! </p>
 * <p>
 * 自己的通用 mapper 如下使用:
 * <pre>
 * int insertBatchSomeColumn(List<T> entityList);
 * </pre>
 * </p>
 *
 * <li> 注意: 這是自選字段 insert !!,如果個別字段在 entity 里為 null 但是數據庫中有配置默認值, insert 后數據庫字段是為 null 而不是默認值 </li>
 *
 * <p>
 * 常用的 {@link Predicate}:
 * </p>
 *
 * <li> 例1: t -> !t.isLogicDelete() , 表示不要邏輯刪除字段 </li>
 * <li> 例2: t -> !t.getProperty().equals("version") , 表示不要字段名為 version 的字段 </li>
 * <li> 例3: t -> t.getFieldFill() != FieldFill.UPDATE) , 表示不要填充策略為 UPDATE 的字段 </li>
 *
 * @author miemie
 * @since 2018-11-29
 */
@NoArgsConstructor
@AllArgsConstructor
public class InsertBatchSomeColumn extends AbstractMethod {

    /**
     * 字段篩選條件
     */
    @Setter
    @Accessors(chain = true)
    private Predicate<TableFieldInfo> predicate;

    @SuppressWarnings("Duplicates")
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        KeyGenerator keyGenerator = new NoKeyGenerator();
        SqlMethod sqlMethod = SqlMethod.INSERT_ONE;
        List<TableFieldInfo> fieldList = tableInfo.getFieldList();
        String insertSqlColumn = tableInfo.getKeyInsertSqlColumn(false) +
            this.filterTableFieldInfo(fieldList, predicate, TableFieldInfo::getInsertSqlColumn, EMPTY);
        String columnScript = LEFT_BRACKET + insertSqlColumn.substring(0, insertSqlColumn.length() - 1) + RIGHT_BRACKET;
        String insertSqlProperty = tableInfo.getKeyInsertSqlProperty(ENTITY_DOT, false) +
            this.filterTableFieldInfo(fieldList, predicate, i -> i.getInsertSqlProperty(ENTITY_DOT), EMPTY);
        insertSqlProperty = LEFT_BRACKET + insertSqlProperty.substring(0, insertSqlProperty.length() - 1) + RIGHT_BRACKET;
        String valuesScript = SqlScriptUtils.convertForeach(insertSqlProperty, "list", null, ENTITY, COMMA);
        String keyProperty = null;
        String keyColumn = null;
        // 表包含主鍵處理邏輯,如果不包含主鍵當普通字段處理
        if (StringUtils.isNotBlank(tableInfo.getKeyProperty())) {
            if (tableInfo.getIdType() == IdType.AUTO) {
                /* 自增主鍵 */
                keyGenerator = new Jdbc3KeyGenerator();
                keyProperty = tableInfo.getKeyProperty();
                keyColumn = tableInfo.getKeyColumn();
            } else {
                if (null != tableInfo.getKeySequence()) {
                    keyGenerator = TableInfoHelper.genKeyGenerator(getMethod(sqlMethod), tableInfo, builderAssistant);
                    keyProperty = tableInfo.getKeyProperty();
                    keyColumn = tableInfo.getKeyColumn();
                }
            }
        }
        String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), columnScript, valuesScript);
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return this.addInsertMappedStatement(mapperClass, modelClass, getMethod(sqlMethod), sqlSource, keyGenerator, keyProperty, keyColumn);
    }

    @Override
    public String getMethod(SqlMethod sqlMethod) {
        // 自定義 mapper 方法名
        return "insertBatchSomeColumn";
    }
}

2.2 InsertBatchSomeColumn 的使用

由于InsertBatchSomeColumn 是框架已經定義好的通用方法,所以使用者只要引入即可,簡單來說只需要進行以下幾個步驟:

  • 新增 SQL 注入器

  • 新增配置類將 SQL 注入器添加到容器

  • 新增基類 Mapper,注意這個基類中的批量插入方法名稱要和 InsertBatchSomeColumn#getMethod() 方法返回的字符串一致,也就是 insertBatchSomeColumn

具體做法讀者請參考 MyBatis-plus 自定義通用方法及其實現原理,本文不再贅述

 經過以上配置,最終具體的業務類 Mapper 只要繼承新增的基類 Mapper 就具備了批量插入的功能,筆者習慣將 Mapper 封裝在一個 RepositoryService 中對外提供能力,則各個業務類只需要實現類似如下的 NodeRepositoryServiceImpl#insertBatch() 方法即可以對外提供批量插入的功能

    @Override
    public int insertBatch(List<Node> entityList) {
        if (CollectionUtils.isEmpty(entityList)) {
            return 0;
        }
        return getBaseMapper().insertBatchSomeColumn(entityList);
    }

3. 批量插入 MySQL 數據庫的坑

3.1 MySQL 對非 NULL 字段插入 NULL 值的處理

使用 MyBatis-plus 批量插入的方法插入 MySQL 記錄時需要注意,調用批量插入的方法一定要保證確實是要插入多條數據,如果調用批量插入的方法只插入了單條數據,非常有可能遇到非 NULL 字段插入 NULL 值的錯誤:

com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'xxx_name' cannot be null

這是因為我們借助 Entity 插入數據時經常會忽略一些表中有默認值的非 NULL 字段對應的屬性的賦值,而從批量插入的 SQL 語句的執行角度來看,這樣做也就是往非 NULL 字段插入了 NULL 值。實際上 MySQL 對于非 NULL 字段插入 NULL 值是有兼容處理的,感興趣的讀者可前往 官方傳送門,本文摘錄如下:

簡單來說,對于插入 NULL 值到非 NULL 字段的情況分為兩種處理方式:

  • 如果是批量插入多條數據,則會將 NULL 值轉化為默認值插到非 NULL 字段(也就是本文批量插入方法插入多條數據的情形)

  • 如果是單條數據插入,則拋出異常,失敗結束(對應本文批量插入方法只插入了單條數據的情形)

Inserting NULL into a column that has been declared NOT NULL. For multiple-row INSERT statements or 
INSERT INTO ... SELECT statements, the column is set to the implicit default value for the column
data type. This is 0 for numeric types, the empty string ('') for string types, and the “zero” value
for date and time types. INSERT INTO ... SELECT statements are handled the same way as multiple-row
inserts because the server does not examine the result set from the SELECT to see whether it returns
a single row. (For a single-row INSERT, no warning occurs when NULL is inserted into a NOT NULL column.
Instead, the statement fails with an error.)

3.2 解決方法

解決方法很簡單,只要在批量插入的時候判斷一下 Entity 集合的大小即可,如果集合中只有一條數據,則調用插入單條數據的方法

  • MyBatis-plus 單條數據插入之所以不會有往非 NULL 字段插入 NULL 值的問題,是因為其單條插入數據的 SQL 腳本能根據 Entity 的屬性賦值情況動態調整,對于 Entity 中值為 NULL 的屬性,默認不會將其對應的字段添加到執行的 SQL 語句中

舉例來說,如 Node 含有兩個屬性,分別是 name 和 version,則對于屬性值不同的情況最終執行的 SQL 語句也不一樣

1. version 為 NULL
INSERT INTO node (name) VALUES (&lsquo;nathan&rsquo;);
2. version 不為 NULL
INSERT INTO node (name, version) VALUES (&lsquo;nathan&rsquo;,1);

    @Override
    public int insertBatch(List<Node> entityList) {
        if (CollectionUtils.isEmpty(entityList)) {
            return 0;
        }
        if (1 == entityList.size()) {
            return getBaseMapper().insert(entityList.get(0));
        }
        return getBaseMapper().insertBatchSomeColumn(entityList);
    }

到此,關于“MyBatis-plus批量插入的通用方法是什么”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

洪湖市| 满洲里市| 凉山| 南康市| 新密市| 广宁县| 西畴县| 汕头市| 宝山区| 乳源| 额济纳旗| 古蔺县| 吉木乃县| 嵊州市| 朝阳县| 尉犁县| 桐城市| 遂平县| 浪卡子县| 关岭| 武安市| 卢龙县| 大城县| 尚义县| 元氏县| 吐鲁番市| 都安| 邻水| 莒南县| 桃江县| 天全县| 罗源县| 平定县| 乐至县| 顺平县| 平罗县| 东阿县| 巴林左旗| 蒙自县| 调兵山市| 青河县|