• Spring Data Jpa 实现批量插入或更新


    1. BatchConsumer

    1. package com.demo.common.hibernate.batch;
    2. import com.demo.common.hibernate.querydsl.QueryParameterBuilder;
    3. /**
    4. * 批量数据消费者接口,用于设置 SQL 参数并执行操作。
    5. *
    6. * @param 记录类型的泛型
    7. * @author xm.z
    8. */
    9. @FunctionalInterface
    10. public interface BatchConsumer {
    11. /**
    12. * 设置 SQL 参数并执行操作。
    13. *
    14. * @param builder 参数构建对象
    15. * @param record 要处理的记录
    16. */
    17. void accept(QueryParameterBuilder builder, T record);
    18. }

    2. QueryParameterBuilder

    1. package com.demo.common.hibernate.querydsl;
    2. import lombok.AccessLevel;
    3. import lombok.Getter;
    4. import lombok.extern.slf4j.Slf4j;
    5. import org.hibernate.jpa.TypedParameterValue;
    6. import org.hibernate.type.*;
    7. import org.springframework.util.Assert;
    8. import javax.persistence.Query;
    9. import java.math.BigDecimal;
    10. import java.time.LocalDate;
    11. import java.time.LocalDateTime;
    12. import java.time.LocalTime;
    13. import java.util.Date;
    14. import java.util.concurrent.atomic.AtomicInteger;
    15. /**
    16. * QueryParameterBuilder
    17. *

    18. * A utility class for building parameters for query.
    19. *
    20. * @author xm.z
    21. */
    22. @Slf4j
    23. @Getter
    24. public class QueryParameterBuilder {
    25. /**
    26. * The native query object to be used for parameter setting
    27. */
    28. private final Query nativeQuery;
    29. /**
    30. * The counter for parameter position
    31. */
    32. @Getter(value = AccessLevel.NONE)
    33. private final AtomicInteger position;
    34. /**
    35. * The current date and time when the QueryParameterBuilder instance is created
    36. */
    37. private final LocalDateTime now;
    38. /**
    39. * Private constructor to initialize QueryParameterBuilder
    40. */
    41. private QueryParameterBuilder(Query nativeQuery, AtomicInteger position) {
    42. this.nativeQuery = nativeQuery;
    43. this.position = position;
    44. this.now = LocalDateTime.now();
    45. }
    46. /**
    47. * Retrieves the current position of the parameter.
    48. *
    49. * @return The current position of the parameter.
    50. */
    51. public Integer obtainCurrentPosition() {
    52. return position.get();
    53. }
    54. /**
    55. * Create an instance of QueryParameterBuilder.
    56. *
    57. * @param nativeQuery The native query object
    58. * @param position The parameter position counter
    59. * @return QueryParameterBuilder instance
    60. */
    61. public static QueryParameterBuilder create(Query nativeQuery, AtomicInteger position) {
    62. Assert.notNull(nativeQuery, "Native query must not be null");
    63. Assert.notNull(position, "Position must not be null");
    64. return new QueryParameterBuilder(nativeQuery, position);
    65. }
    66. /**
    67. * Set a parameter of type Long.
    68. *
    69. * @param value The Long value for the parameter
    70. * @return The current QueryParameterBuilder instance
    71. */
    72. public QueryParameterBuilder setParameter(Long value) {
    73. return this.setParameter(StandardBasicTypes.LONG, value);
    74. }
    75. /**
    76. * Set a parameter of type Integer.
    77. *
    78. * @param value The Integer value for the parameter
    79. * @return The current QueryParameterBuilder instance
    80. */
    81. public QueryParameterBuilder setParameter(Integer value) {
    82. return this.setParameter(StandardBasicTypes.INTEGER, value);
    83. }
    84. /**
    85. * Set a parameter of type BigDecimal.
    86. *
    87. * @param value The BigDecimal value for the parameter
    88. * @return The current QueryParameterBuilder instance
    89. */
    90. public QueryParameterBuilder setParameter(BigDecimal value) {
    91. return this.setParameter(StandardBasicTypes.BIG_DECIMAL, value);
    92. }
    93. /**
    94. * Set a parameter of type String.
    95. *
    96. * @param value The String value for the parameter
    97. * @return The current QueryParameterBuilder instance
    98. */
    99. public QueryParameterBuilder setParameter(String value) {
    100. return this.setParameter(StandardBasicTypes.STRING, value);
    101. }
    102. /**
    103. * Set a parameter of type Boolean.
    104. *
    105. * @param value The Boolean value for the parameter
    106. * @return The current QueryParameterBuilder instance
    107. */
    108. public QueryParameterBuilder setParameter(Boolean value) {
    109. return this.setParameter(StandardBasicTypes.BOOLEAN, value);
    110. }
    111. /**
    112. * Set a parameter of type Date.
    113. *
    114. * @param value The Date value for the parameter
    115. * @return The current QueryParameterBuilder instance
    116. */
    117. public QueryParameterBuilder setParameter(Date value) {
    118. return this.setParameter(StandardBasicTypes.DATE, value);
    119. }
    120. /**
    121. * Set a parameter of type LocalDate.
    122. *
    123. * @param value The LocalDate value for the parameter
    124. * @return The current QueryParameterBuilder instance
    125. */
    126. public QueryParameterBuilder setParameter(LocalDate value) {
    127. return this.setParameter(LocalDateType.INSTANCE, value);
    128. }
    129. /**
    130. * Set a parameter of type LocalTime.
    131. *
    132. * @param value The LocalTime value for the parameter
    133. * @return The current QueryParameterBuilder instance
    134. */
    135. public QueryParameterBuilder setParameter(LocalTime value) {
    136. return this.setParameter(LocalTimeType.INSTANCE, value);
    137. }
    138. /**
    139. * Set a parameter of type LocalDateTime.
    140. *
    141. * @param value The LocalDateTime value for the parameter
    142. * @return The current QueryParameterBuilder instance
    143. */
    144. public QueryParameterBuilder setParameter(LocalDateTime value) {
    145. return this.setParameter(LocalDateTimeType.INSTANCE, value);
    146. }
    147. /**
    148. * Add or include a query condition to the native query object and set the parameter value.
    149. *
    150. * @param type The parameter type
    151. * @param value The parameter value
    152. * @return The current QueryParameterBuilder instance
    153. */
    154. public QueryParameterBuilder setParameter(Type type, Object value) {
    155. return this.setParameter(position.getAndIncrement(), type, value);
    156. }
    157. /**
    158. * Add or include a query condition to the native query object and set the parameter value at the specified position.
    159. *
    160. * @param position The position of the parameter in the query
    161. * @param type The parameter type
    162. * @param value The parameter value
    163. * @return The current QueryParameterBuilder instance
    164. */
    165. public QueryParameterBuilder setParameter(int position, Type type, Object value) {
    166. TypedParameterValue typedParameterValue = new TypedParameterValue(type, value);
    167. if (log.isDebugEnabled()) {
    168. log.debug("Setting parameter at position {}: {}", position, typedParameterValue);
    169. }
    170. nativeQuery.setParameter(position, typedParameterValue);
    171. return this;
    172. }
    173. }

    3. KeyValue

    1. package com.demo.common.model;
    2. import io.swagger.v3.oas.annotations.media.Schema;
    3. import lombok.AllArgsConstructor;
    4. import lombok.Data;
    5. import lombok.NoArgsConstructor;
    6. import java.io.Serializable;
    7. /**
    8. * 用于表示键值对的通用类
    9. *
    10. * @param 键的类型
    11. * @param 值的类型
    12. * @author xm.z
    13. */
    14. @Data
    15. @NoArgsConstructor
    16. @AllArgsConstructor
    17. public class KeyValue implements Serializable {
    18. private static final long serialVersionUID = 1L;
    19. /**
    20. * 键
    21. */
    22. @Schema(title = "键")
    23. private K key;
    24. /**
    25. * 值
    26. */
    27. @Schema(title = "值")
    28. private V value;
    29. }

    3. SqlUtil

    1. package com.demo.common.hibernate.util;
    2. import com.demo.common.hibernate.batch.BatchConsumer;
    3. import com.demo.common.hibernate.querydsl.QueryParameterBuilder;
    4. import com.demo.common.model.KeyValue;
    5. import cn.hutool.extra.spring.SpringUtil;
    6. import cn.hutool.core.collection.CollUtil;
    7. import lombok.extern.slf4j.Slf4j;
    8. import org.jetbrains.annotations.NotNull;
    9. import org.springframework.lang.NonNull;
    10. import org.springframework.lang.Nullable;
    11. import org.springframework.util.Assert;
    12. import javax.persistence.EntityManager;
    13. import javax.persistence.Query;
    14. import java.util.Collections;
    15. import java.util.LinkedHashSet;
    16. import java.util.List;
    17. import java.util.concurrent.atomic.AtomicInteger;
    18. import java.util.stream.Collectors;
    19. /**
    20. * SqlUtil
    21. *
    22. * @author xm.z
    23. */
    24. @Slf4j
    25. @SuppressWarnings("all")
    26. public class SqlUtil {
    27. /**
    28. * Default batch insert size.
    29. */
    30. public static final int DEFAULT_BATCH_SIZE = 100;
    31. /**
    32. * Private constructor.
    33. */
    34. private SqlUtil() {
    35. }
    36. /**
    37. * Batch insert records into the database.
    38. *
    39. * @param tableFields The table fields information
    40. * @param records The list of records to be inserted
    41. * @param consumer The consumer function interface for customizing the insert behavior
    42. * @param The type of records
    43. * @return The number of records successfully inserted
    44. */
    45. public static int batchInsert(@NonNull KeyValue> tableFields,
    46. @NonNull List records, @NonNull BatchConsumersuper T> consumer) {
    47. return batchInsert(DEFAULT_BATCH_SIZE, tableFields, records, consumer);
    48. }
    49. /**
    50. * Perform batch insert operation with the specified batch size.
    51. *
    52. * @param batchSize the size of each batch for insertion
    53. * @param tableFields the key-value pair representing the table fields
    54. * @param records the list of records to be inserted
    55. * @param consumer the batch consumer for processing each batch of records
    56. * @param the type of records
    57. * @return the total number of records successfully inserted
    58. */
    59. public static int batchInsert(int batchSize, @NonNull KeyValue> tableFields,
    60. @NonNull List records, @NonNull BatchConsumersuper T> consumer) {
    61. EntityManager entityManager = SpringUtil.getBean(EntityManager.class);
    62. return batchExecuteUpdate(batchSize, entityManager, tableFields, null, records, consumer);
    63. }
    64. /**
    65. * Batch insert records into the database.
    66. *
    67. * @param entityManager The entity manager
    68. * @param tableFields The table fields information
    69. * @param records The list of records to be inserted
    70. * @param consumer The consumer function interface for customizing the insert behavior
    71. * @param The type of records
    72. * @return The number of records successfully inserted
    73. */
    74. public static int batchInsert(EntityManager entityManager,
    75. @NonNull KeyValue> tableFields,
    76. @NonNull List records, @NonNull BatchConsumersuper T> consumer) {
    77. return batchExecuteUpdate(DEFAULT_BATCH_SIZE, entityManager, tableFields, null, records, consumer);
    78. }
    79. /**
    80. * Executes batch insert or update operations on the database using native SQL with a default batch size.
    81. *
    82. * @param tableFields key-value pair representing the table name and its fields
    83. * @param updateFields set of fields to be updated if a record with matching primary key exists
    84. * @param records the list of records to be inserted or updated
    85. * @param consumer functional interface for accepting batch consumer operations
    86. * @param the type of the records to be inserted or updated
    87. * @return the total number of rows affected by the batch operation
    88. */
    89. public static int batchInsertOrUpdate(@NonNull KeyValue> tableFields,
    90. @NonNull LinkedHashSet updateFields,
    91. @NonNull List records, @NonNull BatchConsumersuper T> consumer) {
    92. return batchInsertOrUpdate(DEFAULT_BATCH_SIZE, tableFields, updateFields, records, consumer);
    93. }
    94. /**
    95. * Executes batch insert or update operations on the database using native SQL with a parameterized batch size.
    96. *
    97. * @param batchSize the size of each batch for insertion
    98. * @param tableFields key-value pair representing the table name and its fields
    99. * @param updateFields set of fields to be updated if a record with matching primary key exists
    100. * @param records the list of records to be inserted or updated
    101. * @param consumer functional interface for accepting batch consumer operations
    102. * @param the type of the records to be inserted or updated
    103. * @return the total number of rows affected by the batch operation
    104. */
    105. public static int batchInsertOrUpdate(int batchSize, @NonNull KeyValue> tableFields,
    106. @NonNull LinkedHashSet updateFields,
    107. @NonNull List records, @NonNull BatchConsumersuper T> consumer) {
    108. EntityManager entityManager = SpringUtil.getBean(EntityManager.class);
    109. return batchExecuteUpdate(batchSize, entityManager, tableFields, updateFields, records, consumer);
    110. }
    111. /**
    112. * Executes batch insert or update operations on the database using native SQL with a default batch size.
    113. *
    114. * @param entityManager The entity manager
    115. * @param tableFields key-value pair representing the table name and its fields
    116. * @param updateFields set of fields to be updated if a record with matching primary key exists
    117. * @param records the list of records to be inserted or updated
    118. * @param consumer functional interface for accepting batch consumer operations
    119. * @param the type of the records to be inserted or updated
    120. * @return the total number of rows affected by the batch operation
    121. */
    122. public static int batchInsertOrUpdate(EntityManager entityManager,
    123. @NonNull KeyValue> tableFields,
    124. @NonNull LinkedHashSet updateFields,
    125. @NonNull List records, @NonNull BatchConsumersuper T> consumer) {
    126. return batchExecuteUpdate(DEFAULT_BATCH_SIZE, entityManager, tableFields, updateFields, records, consumer);
    127. }
    128. /**
    129. * Executes batch updates on the database using native SQL with a parameterized batch size.
    130. *
    131. * @param batchSize the size of each batch for inserting records
    132. * @param entityManager the entity manager for creating and executing queries
    133. * @param tableFields key-value pair representing the table name and its fields
    134. * @param updateFields set of fields to be updated if a record with matching primary key exists (optional)
    135. * @param records the list of records to be inserted
    136. * @param consumer functional interface for accepting batch consumer operations
    137. * @param the type of the records to be inserted
    138. * @return the total number of rows affected by the batch operation
    139. */
    140. private static int batchExecuteUpdate(int batchSize, EntityManager entityManager,
    141. @NonNull KeyValue> tableFields,
    142. @Nullable LinkedHashSet updateFields,
    143. @NonNull List records, @NonNull BatchConsumersuper T> consumer) {
    144. if (records.isEmpty()) {
    145. log.debug("No records to process. The records list is empty.");
    146. return 0;
    147. }
    148. Assert.notNull(entityManager, "The entity manager must not be null.");
    149. Assert.isTrue(batchSize > 0 && batchSize < 500, "The batch size must be between 1 and 500.");
    150. AtomicInteger totalRows = new AtomicInteger(0);
    151. // Split the records into batches based on the specified batch size
    152. List> recordBatches = CollUtil.split(records, batchSize);
    153. for (List batchRecords : recordBatches) {
    154. AtomicInteger position = new AtomicInteger(1);
    155. // Generate the appropriate SQL statement for the batch
    156. String preparedStatementSql = CollUtil.isEmpty(updateFields) ?
    157. generateBatchInsertSql(tableFields, batchRecords.size()) :
    158. generateBatchInsertOrUpdateSql(tableFields, updateFields, batchRecords.size());
    159. // Create a Query instance for executing native SQL statements
    160. Query nativeQuery = entityManager.createNativeQuery(preparedStatementSql);
    161. // Create a parameter builder instance using QueryParameterBuilder
    162. QueryParameterBuilder parameterBuilder = QueryParameterBuilder.create(nativeQuery, position);
    163. for (T record : batchRecords) {
    164. // Set parameters for the prepared statement
    165. consumer.accept(parameterBuilder, record);
    166. }
    167. // Execute the SQL statement and accumulate the affected rows
    168. totalRows.addAndGet(nativeQuery.executeUpdate());
    169. }
    170. // Return the total number of affected rows
    171. return totalRows.get();
    172. }
    173. /**
    174. * Generate batch insert SQL statement.
    175. *
    176. *

    177. * This method generates an SQL statement for batch insertion into a specified table with the provided fields.
    178. * Example SQL statement:
    179. *
    180. * {@code INSERT INTO TABLE_NAME ( field_1, field_2 ) VALUES ( value_1, value_2 ), (value_3, value_4); }
    181. *
  • *

  • *
  • * @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> 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> tableFields,
  • LinkedHashSet 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 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();
  • }
  • }
  • 相关阅读:
    论文阅读——DiffusionDet
    stp文件转stl
    澳鹏Appen重磅发布《2022人工智能与机器学习全景报告》
    数据结构——单链表(C语言实现)
    E. Cross Swapping
    贪心算法小结
    20220929-ArrayList扩容机制源码分析
    使用API Monitor工具巧妙探测C++程序中监听某端口的模块
    Mysql系列一:事物概念及特性
    Java基础教程:多线程(5)-----线程的调度之优先级
  • 原文地址:https://blog.csdn.net/qq_38765404/article/details/139565548