Spring Data Jpa如何實(shí)現(xiàn)批量插入或更新
Spring Data Jpa批量插入或更新
1. BatchConsumer
package com.demo.common.hibernate.batch;
import com.demo.common.hibernate.querydsl.QueryParameterBuilder;
/**
* 批量數(shù)據(jù)消費(fèi)者接口,用于設(shè)置 SQL 參數(shù)并執(zhí)行操作。
*
* @param <T> 記錄類型的泛型
* @author xm.z
*/
@FunctionalInterface
public interface BatchConsumer<T> {
/**
* 設(shè)置 SQL 參數(shù)并執(zhí)行操作。
*
* @param builder 參數(shù)構(gòu)建對(duì)象
* @param record 要處理的記錄
*/
void accept(QueryParameterBuilder builder, T record);
}2. QueryParameterBuilder
package com.demo.common.hibernate.querydsl;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.jpa.TypedParameterValue;
import org.hibernate.type.*;
import org.springframework.util.Assert;
import javax.persistence.Query;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;
/**
* QueryParameterBuilder
* <p>
* A utility class for building parameters for query.
*
* @author xm.z
*/
@Slf4j
@Getter
public class QueryParameterBuilder {
/**
* The native query object to be used for parameter setting
*/
private final Query nativeQuery;
/**
* The counter for parameter position
*/
@Getter(value = AccessLevel.NONE)
private final AtomicInteger position;
/**
* The current date and time when the QueryParameterBuilder instance is created
*/
private final LocalDateTime now;
/**
* Private constructor to initialize QueryParameterBuilder
*/
private QueryParameterBuilder(Query nativeQuery, AtomicInteger position) {
this.nativeQuery = nativeQuery;
this.position = position;
this.now = LocalDateTime.now();
}
/**
* Retrieves the current position of the parameter.
*
* @return The current position of the parameter.
*/
public Integer obtainCurrentPosition() {
return position.get();
}
/**
* Create an instance of QueryParameterBuilder.
*
* @param nativeQuery The native query object
* @param position The parameter position counter
* @return QueryParameterBuilder instance
*/
public static QueryParameterBuilder create(Query nativeQuery, AtomicInteger position) {
Assert.notNull(nativeQuery, "Native query must not be null");
Assert.notNull(position, "Position must not be null");
return new QueryParameterBuilder(nativeQuery, position);
}
/**
* Set a parameter of type Long.
*
* @param value The Long value for the parameter
* @return The current QueryParameterBuilder instance
*/
public QueryParameterBuilder setParameter(Long value) {
return this.setParameter(StandardBasicTypes.LONG, value);
}
/**
* Set a parameter of type Integer.
*
* @param value The Integer value for the parameter
* @return The current QueryParameterBuilder instance
*/
public QueryParameterBuilder setParameter(Integer value) {
return this.setParameter(StandardBasicTypes.INTEGER, value);
}
/**
* Set a parameter of type BigDecimal.
*
* @param value The BigDecimal value for the parameter
* @return The current QueryParameterBuilder instance
*/
public QueryParameterBuilder setParameter(BigDecimal value) {
return this.setParameter(StandardBasicTypes.BIG_DECIMAL, value);
}
/**
* Set a parameter of type String.
*
* @param value The String value for the parameter
* @return The current QueryParameterBuilder instance
*/
public QueryParameterBuilder setParameter(String value) {
return this.setParameter(StandardBasicTypes.STRING, value);
}
/**
* Set a parameter of type Boolean.
*
* @param value The Boolean value for the parameter
* @return The current QueryParameterBuilder instance
*/
public QueryParameterBuilder setParameter(Boolean value) {
return this.setParameter(StandardBasicTypes.BOOLEAN, value);
}
/**
* Set a parameter of type Date.
*
* @param value The Date value for the parameter
* @return The current QueryParameterBuilder instance
*/
public QueryParameterBuilder setParameter(Date value) {
return this.setParameter(StandardBasicTypes.DATE, value);
}
/**
* Set a parameter of type LocalDate.
*
* @param value The LocalDate value for the parameter
* @return The current QueryParameterBuilder instance
*/
public QueryParameterBuilder setParameter(LocalDate value) {
return this.setParameter(LocalDateType.INSTANCE, value);
}
/**
* Set a parameter of type LocalTime.
*
* @param value The LocalTime value for the parameter
* @return The current QueryParameterBuilder instance
*/
public QueryParameterBuilder setParameter(LocalTime value) {
return this.setParameter(LocalTimeType.INSTANCE, value);
}
/**
* Set a parameter of type LocalDateTime.
*
* @param value The LocalDateTime value for the parameter
* @return The current QueryParameterBuilder instance
*/
public QueryParameterBuilder setParameter(LocalDateTime value) {
return this.setParameter(LocalDateTimeType.INSTANCE, value);
}
/**
* Add or include a query condition to the native query object and set the parameter value.
*
* @param type The parameter type
* @param value The parameter value
* @return The current QueryParameterBuilder instance
*/
public QueryParameterBuilder setParameter(Type type, Object value) {
return this.setParameter(position.getAndIncrement(), type, value);
}
/**
* Add or include a query condition to the native query object and set the parameter value at the specified position.
*
* @param position The position of the parameter in the query
* @param type The parameter type
* @param value The parameter value
* @return The current QueryParameterBuilder instance
*/
public QueryParameterBuilder setParameter(int position, Type type, Object value) {
TypedParameterValue typedParameterValue = new TypedParameterValue(type, value);
if (log.isDebugEnabled()) {
log.debug("Setting parameter at position {}: {}", position, typedParameterValue);
}
nativeQuery.setParameter(position, typedParameterValue);
return this;
}
}3. KeyValue
package com.demo.common.model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 用于表示鍵值對(duì)的通用類
*
* @param <K> 鍵的類型
* @param <V> 值的類型
* @author xm.z
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class KeyValue<K, V> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 鍵
*/
@Schema(title = "鍵")
private K key;
/**
* 值
*/
@Schema(title = "值")
private V value;
}4. SqlUtil
package com.demo.common.hibernate.util;
import com.demo.common.hibernate.batch.BatchConsumer;
import com.demo.common.hibernate.querydsl.QueryParameterBuilder;
import com.demo.common.model.KeyValue;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.core.collection.CollUtil;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
/**
* SqlUtil
*
* @author xm.z
*/
@Slf4j
@SuppressWarnings("all")
public class SqlUtil {
/**
* Default batch insert size.
*/
public static final int DEFAULT_BATCH_SIZE = 100;
/**
* Private constructor.
*/
private SqlUtil() {
}
/**
* Batch insert records into the database.
*
* @param tableFields The table fields information
* @param records The list of records to be inserted
* @param consumer The consumer function interface for customizing the insert behavior
* @param <T> The type of records
* @return The number of records successfully inserted
*/
public static <T> int batchInsert(@NonNull KeyValue<String, LinkedHashSet<String>> tableFields,
@NonNull List<T> records, @NonNull BatchConsumer<? super T> consumer) {
return batchInsert(DEFAULT_BATCH_SIZE, tableFields, records, consumer);
}
/**
* Perform batch insert operation with the specified batch size.
*
* @param batchSize the size of each batch for insertion
* @param tableFields the key-value pair representing the table fields
* @param records the list of records to be inserted
* @param consumer the batch consumer for processing each batch of records
* @param <T> the type of records
* @return the total number of records successfully inserted
*/
public static <T> int batchInsert(int batchSize, @NonNull KeyValue<String, LinkedHashSet<String>> tableFields,
@NonNull List<T> records, @NonNull BatchConsumer<? super T> consumer) {
EntityManager entityManager = SpringUtil.getBean(EntityManager.class);
return batchExecuteUpdate(batchSize, entityManager, tableFields, null, records, consumer);
}
/**
* Batch insert records into the database.
*
* @param entityManager The entity manager
* @param tableFields The table fields information
* @param records The list of records to be inserted
* @param consumer The consumer function interface for customizing the insert behavior
* @param <T> The type of records
* @return The number of records successfully inserted
*/
public static <T> int batchInsert(EntityManager entityManager,
@NonNull KeyValue<String, LinkedHashSet<String>> tableFields,
@NonNull List<T> records, @NonNull BatchConsumer<? super T> consumer) {
return batchExecuteUpdate(DEFAULT_BATCH_SIZE, entityManager, tableFields, null, records, consumer);
}
/**
* Executes batch insert or update operations on the database using native SQL with a default batch size.
*
* @param tableFields key-value pair representing the table name and its fields
* @param updateFields set of fields to be updated if a record with matching primary key exists
* @param records the list of records to be inserted or updated
* @param consumer functional interface for accepting batch consumer operations
* @param <T> the type of the records to be inserted or updated
* @return the total number of rows affected by the batch operation
*/
public static <T> int batchInsertOrUpdate(@NonNull KeyValue<String, LinkedHashSet<String>> tableFields,
@NonNull LinkedHashSet<String> updateFields,
@NonNull List<T> records, @NonNull BatchConsumer<? super T> consumer) {
return batchInsertOrUpdate(DEFAULT_BATCH_SIZE, tableFields, updateFields, records, consumer);
}
/**
* Executes batch insert or update operations on the database using native SQL with a parameterized batch size.
*
* @param batchSize the size of each batch for insertion
* @param tableFields key-value pair representing the table name and its fields
* @param updateFields set of fields to be updated if a record with matching primary key exists
* @param records the list of records to be inserted or updated
* @param consumer functional interface for accepting batch consumer operations
* @param <T> the type of the records to be inserted or updated
* @return the total number of rows affected by the batch operation
*/
public static <T> int batchInsertOrUpdate(int batchSize, @NonNull KeyValue<String, LinkedHashSet<String>> tableFields,
@NonNull LinkedHashSet<String> updateFields,
@NonNull List<T> records, @NonNull BatchConsumer<? super T> consumer) {
EntityManager entityManager = SpringUtil.getBean(EntityManager.class);
return batchExecuteUpdate(batchSize, entityManager, tableFields, updateFields, records, consumer);
}
/**
* Executes batch insert or update operations on the database using native SQL with a default batch size.
*
* @param entityManager The entity manager
* @param tableFields key-value pair representing the table name and its fields
* @param updateFields set of fields to be updated if a record with matching primary key exists
* @param records the list of records to be inserted or updated
* @param consumer functional interface for accepting batch consumer operations
* @param <T> the type of the records to be inserted or updated
* @return the total number of rows affected by the batch operation
*/
public static <T> int batchInsertOrUpdate(EntityManager entityManager,
@NonNull KeyValue<String, LinkedHashSet<String>> tableFields,
@NonNull LinkedHashSet<String> updateFields,
@NonNull List<T> records, @NonNull BatchConsumer<? super T> consumer) {
return batchExecuteUpdate(DEFAULT_BATCH_SIZE, entityManager, tableFields, updateFields, records, consumer);
}
/**
* Executes batch updates on the database using native SQL with a parameterized batch size.
*
* @param batchSize the size of each batch for inserting records
* @param entityManager the entity manager for creating and executing queries
* @param tableFields key-value pair representing the table name and its fields
* @param updateFields set of fields to be updated if a record with matching primary key exists (optional)
* @param records the list of records to be inserted
* @param consumer functional interface for accepting batch consumer operations
* @param <T> the type of the records to be inserted
* @return the total number of rows affected by the batch operation
*/
private static <T> int batchExecuteUpdate(int batchSize, EntityManager entityManager,
@NonNull KeyValue<String, LinkedHashSet<String>> tableFields,
@Nullable LinkedHashSet<String> updateFields,
@NonNull List<T> records, @NonNull BatchConsumer<? super T> consumer) {
if (records.isEmpty()) {
log.debug("No records to process. The records list is empty.");
return 0;
}
Assert.notNull(entityManager, "The entity manager must not be null.");
Assert.isTrue(batchSize > 0 && batchSize < 500, "The batch size must be between 1 and 500.");
AtomicInteger totalRows = new AtomicInteger(0);
// Split the records into batches based on the specified batch size
List<List<T>> recordBatches = CollUtil.split(records, batchSize);
for (List<T> batchRecords : recordBatches) {
AtomicInteger position = new AtomicInteger(1);
// Generate the appropriate SQL statement for the batch
String preparedStatementSql = CollUtil.isEmpty(updateFields) ?
generateBatchInsertSql(tableFields, batchRecords.size()) :
generateBatchInsertOrUpdateSql(tableFields, updateFields, batchRecords.size());
// Create a Query instance for executing native SQL statements
Query nativeQuery = entityManager.createNativeQuery(preparedStatementSql);
// Create a parameter builder instance using QueryParameterBuilder
QueryParameterBuilder parameterBuilder = QueryParameterBuilder.create(nativeQuery, position);
for (T record : batchRecords) {
// Set parameters for the prepared statement
consumer.accept(parameterBuilder, record);
}
// Execute the SQL statement and accumulate the affected rows
totalRows.addAndGet(nativeQuery.executeUpdate());
}
// Return the total number of affected rows
return totalRows.get();
}
/**
* Generate batch insert SQL statement.
*
* <p>
* This method generates an SQL statement for batch insertion into a specified table with the provided fields.
* Example SQL statement:
* <pre>
* {@code INSERT INTO TABLE_NAME ( field_1, field_2 ) VALUES ( value_1, value_2 ), (value_3, value_4); }
* </pre>
* </p>
*
* @param tableFields The key-value pair representing the table name and its associated field set
* @param batchSize The batch size for insertion
* @return The batch insert SQL statement
*/
private static String generateBatchInsertSql(@NonNull KeyValue<String, LinkedHashSet<String>> tableFields, int batchSize) {
String preparedStatementSql = generateInsertStatement(tableFields.getKey(), tableFields.getValue(), batchSize);
if (log.isDebugEnabled()) {
log.debug("[Batch Insert] Prepared {} records SQL: {}", batchSize, preparedStatementSql);
}
return preparedStatementSql;
}
/**
* Generates SQL statement for batch insert with on duplicate key update.
*
* @param tableFields Key-value pair representing table name and its corresponding fields.
* @param updateFields Fields to be updated in case of duplicate key.
* @param batchSize Number of records to be inserted in a single batch.
* @return SQL statement for batch insert with on duplicate key update.
* @throws IllegalArgumentException if updateFields collection is empty.
*/
private static String generateBatchInsertOrUpdateSql(@NonNull KeyValue<String, LinkedHashSet<String>> tableFields,
LinkedHashSet<String> updateFields, int batchSize) {
Assert.notEmpty(updateFields, "Update field collection cannot be empty.");
// Generate the insert statement
String insertStatement = generateInsertStatement(tableFields.getKey(), tableFields.getValue(), batchSize);
// Initialize StringBuilder with initial capacity
StringBuilder builder = new StringBuilder(insertStatement.length() + 100);
// Append insert statement
builder.append(insertStatement).append(" ON DUPLICATE KEY UPDATE ");
// Append update clause
String updateClause = updateFields.stream()
.map(updateField -> updateField + " = VALUES(" + updateField + ")")
.collect(Collectors.joining(", "));
builder.append(updateClause);
String preparedStatementSql = builder.toString();
if (log.isDebugEnabled()) {
log.debug("[Batch Insert On Duplicate Key Update] Prepared {} records SQL: {}", batchSize, preparedStatementSql);
}
return preparedStatementSql;
}
@NotNull
private static String generateInsertStatement(@NonNull String tableName, @NonNull LinkedHashSet<String> fields, int batchSize) {
Assert.hasText(tableName, "Table name cannot be empty.");
Assert.notNull(fields, "Field collection cannot be empty.");
// Set a reasonable initial capacity
StringBuilder builder = new StringBuilder(fields.size() * 100);
// Concatenate field names
String fieldNames = String.join(", ", fields);
String intoTemplate = String.format("INSERT INTO %s (%s) VALUES ", tableName, fieldNames);
// Generate placeholders
String placeholders = "(" + String.join(", ", Collections.nCopies(fields.size(), "?")) + ")";
// Construct the insert statement
builder.append(intoTemplate);
for (int i = 0; i < batchSize; i++) {
if (i > 0) {
builder.append(", ");
}
builder.append(placeholders);
}
return builder.toString();
}
}總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
RocketMQ4.5.2 修改mqnamesrv 和 mqbroker的日志路徑操作
這篇文章主要介紹了RocketMQ 4.5.2 修改mqnamesrv 和 mqbroker的日志路徑操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
Java?Shell?springboot通用Shell啟動(dòng)腳本方式
這篇文章主要介紹了Java?Shell?springboot通用Shell啟動(dòng)腳本方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05
java通過(guò)jacob實(shí)現(xiàn)office在線預(yù)覽功能
這篇文章主要為大家詳細(xì)介紹了java通過(guò)jacob實(shí)現(xiàn)office在線預(yù)覽功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-08-08
Java控制臺(tái)版五子棋的簡(jiǎn)單實(shí)現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于Java控制臺(tái)版五子棋的簡(jiǎn)單實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01
spring boot配置ssl(多cer格式)超詳細(xì)教程
這篇文章主要介紹了spring boot配置ssl(多cer格式)超詳細(xì)教程,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2023-11-11
spring boot 監(jiān)聽(tīng)容器啟動(dòng)代碼實(shí)例
這篇文章主要介紹了spring boot 監(jiān)聽(tīng)容器啟動(dòng)代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10
java小程序之控制臺(tái)字符動(dòng)畫的實(shí)現(xiàn)
這篇文章主要給大家介紹了java小程序之控制臺(tái)字符動(dòng)畫實(shí)現(xiàn)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
java 從int數(shù)組中獲取最大數(shù)的方法
這篇文章主要介紹了java 從int數(shù)組中獲取最大數(shù)的方法,需要的朋友可以參考下2017-02-02
Java實(shí)現(xiàn)多個(gè)單張tif文件合并成一個(gè)多頁(yè)tif文件
業(yè)務(wù)部門需要將多個(gè)單張的tiff文件,合并成一個(gè)多頁(yè)的tiff文件,本文就來(lái)介紹一下如何實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2023-09-09

