JDBC 批處理操作

如果將多個對同一預處理語句的呼叫批處理,大多數 JDBC 驅動程式會提供更好的效能。透過將更新分組到批處理中,可以限制到資料庫的往返次數。

使用 JdbcTemplate 進行基本批處理操作

您可以透過實現特殊介面 BatchPreparedStatementSetter 的兩個方法,並將該實現作為第二個引數傳遞到 batchUpdate 方法呼叫中來完成 JdbcTemplate 批處理。您可以使用 getBatchSize 方法提供當前批處理的大小。您可以使用 setValues 方法設定預處理語句的引數值。此方法將按照您在 getBatchSize 呼叫中指定的次數被呼叫。以下示例根據列表中的條目更新 t_actor 表,並將整個列表用作批處理。

  • Java

  • Kotlin

public class JdbcActorDao implements ActorDao {

	private JdbcTemplate jdbcTemplate;

	public void setDataSource(DataSource dataSource) {
		this.jdbcTemplate = new JdbcTemplate(dataSource);
	}

	public int[] batchUpdate(final List<Actor> actors) {
		return this.jdbcTemplate.batchUpdate(
				"update t_actor set first_name = ?, last_name = ? where id = ?",
				new BatchPreparedStatementSetter() {
					public void setValues(PreparedStatement ps, int i) throws SQLException {
						Actor actor = actors.get(i);
						ps.setString(1, actor.getFirstName());
						ps.setString(2, actor.getLastName());
						ps.setLong(3, actor.getId().longValue());
					}
					public int getBatchSize() {
						return actors.size();
					}
				});
	}

	// ... additional methods
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {

	private val jdbcTemplate = JdbcTemplate(dataSource)

	fun batchUpdate(actors: List<Actor>): IntArray {
		return jdbcTemplate.batchUpdate(
				"update t_actor set first_name = ?, last_name = ? where id = ?",
				object: BatchPreparedStatementSetter {
					override fun setValues(ps: PreparedStatement, i: Int) {
						ps.setString(1, actors[i].firstName)
						ps.setString(2, actors[i].lastName)
						ps.setLong(3, actors[i].id)
					}

					override fun getBatchSize() = actors.size
				})
	}

	// ... additional methods
}

如果您處理更新流或從檔案中讀取,您可能有一個首選的批處理大小,但最後一個批處理可能沒有該數量的條目。在這種情況下,您可以使用 InterruptibleBatchPreparedStatementSetter 介面,它允許您在輸入源耗盡後中斷批處理。isBatchExhausted 方法允許您發出批處理結束的訊號。

使用物件列表進行批處理操作

JdbcTemplateNamedParameterJdbcTemplate 都提供了一種提供批處理更新的替代方法。您不必實現特殊的批處理介面,而是在呼叫中將所有引數值作為列表提供。該框架遍歷這些值並使用內部預處理語句設定器。API 會根據您是否使用命名引數而有所不同。對於命名引數,您需要提供一個 SqlParameterSource 陣列,批處理中的每個成員對應一個條目。您可以使用 SqlParameterSourceUtils.createBatch 便利方法來建立此陣列,傳入一個 bean 樣式物件陣列(具有與引數對應的 getter 方法)、String 鍵控的 Map 例項(包含相應的引數作為值),或兩者的混合。

以下示例展示了使用命名引數進行批處理更新。

  • Java

  • Kotlin

public class JdbcActorDao implements ActorDao {

	private NamedParameterTemplate namedParameterJdbcTemplate;

	public void setDataSource(DataSource dataSource) {
		this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
	}

	public int[] batchUpdate(List<Actor> actors) {
		return this.namedParameterJdbcTemplate.batchUpdate(
				"update t_actor set first_name = :firstName, last_name = :lastName where id = :id",
				SqlParameterSourceUtils.createBatch(actors));
	}

	// ... additional methods
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {

	private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)

	fun batchUpdate(actors: List<Actor>): IntArray {
		return this.namedParameterJdbcTemplate.batchUpdate(
				"update t_actor set first_name = :firstName, last_name = :lastName where id = :id",
				SqlParameterSourceUtils.createBatch(actors));
	}

		// ... additional methods
}

對於使用經典 ? 佔位符的 SQL 語句,您需要傳入一個包含物件陣列的列表,其中包含更新值。此物件陣列必須為 SQL 語句中的每個佔位符包含一個條目,並且它們必須與 SQL 語句中定義的順序相同。

以下示例與前面的示例相同,只是它使用經典的 JDBC ? 佔位符。

  • Java

  • Kotlin

public class JdbcActorDao implements ActorDao {

	private JdbcTemplate jdbcTemplate;

	public void setDataSource(DataSource dataSource) {
		this.jdbcTemplate = new JdbcTemplate(dataSource);
	}

	public int[] batchUpdate(final List<Actor> actors) {
		List<Object[]> batch = new ArrayList<>();
		for (Actor actor : actors) {
			Object[] values = new Object[] {
					actor.getFirstName(), actor.getLastName(), actor.getId()};
			batch.add(values);
		}
		return this.jdbcTemplate.batchUpdate(
				"update t_actor set first_name = ?, last_name = ? where id = ?",
				batch);
	}

	// ... additional methods
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {

	private val jdbcTemplate = JdbcTemplate(dataSource)

	fun batchUpdate(actors: List<Actor>): IntArray {
		val batch = mutableListOf<Array<Any>>()
		for (actor in actors) {
			batch.add(arrayOf(actor.firstName, actor.lastName, actor.id))
		}
		return jdbcTemplate.batchUpdate(
				"update t_actor set first_name = ?, last_name = ? where id = ?", batch)
	}

	// ... additional methods
}

我們前面描述的所有批處理更新方法都返回一個 int 陣列,其中包含每個批處理條目受影響的行數。此計數由 JDBC 驅動程式報告。如果計數不可用,JDBC 驅動程式將返回 -2

在這種情況下,當對底層 PreparedStatement 自動設定值時,每個值的相應 JDBC 型別需要從給定的 Java 型別派生。雖然這通常工作良好,但可能存在問題(例如,Map 中包含的 null 值)。Spring 預設在這種情況下呼叫 ParameterMetaData.getParameterType,這在您的 JDBC 驅動程式上可能很昂貴。如果您遇到應用程式的特定效能問題,您應該使用最新的驅動程式版本並考慮將 spring.jdbc.getParameterType.ignore 屬性設定為 true(作為 JVM 系統屬性或透過 SpringProperties 機制)。

自 6.1.2 版本起,Spring 繞過了 PostgreSQL 和 MS SQL Server 上的預設 getParameterType 解析。這是一種常見的最佳化,旨在避免為了引數型別解析而進一步往返於 DBMS,這在 PostgreSQL 和 MS SQL Server 上尤其是在批處理操作中已知會產生非常顯著的差異。如果您遇到副作用,例如,在沒有特定型別指示的情況下將位元組陣列設定為 null,您可以顯式地將 spring.jdbc.getParameterType.ignore=false 標誌設定為系統屬性(如上所述),以恢復完整的 getParameterType 解析。

或者,您可以考慮顯式指定相應的 JDBC 型別,方法是透過 BatchPreparedStatementSetter(如前所示),透過提供給基於 List<Object[]> 的呼叫的顯式型別陣列,透過自定義 MapSqlParameterSource 例項上的 registerSqlType 呼叫,透過 BeanPropertySqlParameterSource,即使是 null 值,它也能從 Java 宣告的屬性型別中推斷出 SQL 型別,或者透過提供單個 SqlParameterValue 例項而不是純 null 值。

多批次操作

前面批處理更新的例子處理的是批處理量非常大,以至於您希望將其分解為幾個較小的批處理。您可以透過多次呼叫 batchUpdate 方法來使用前面提到的方法來實現這一點,但現在有一個更方便的方法。除了 SQL 語句之外,此方法還接受一個包含引數的 Collection 物件,每個批處理要進行的更新數量,以及一個 ParameterizedPreparedStatementSetter 用於設定預處理語句的引數值。該框架遍歷提供的值並將更新呼叫分解為指定大小的批處理。

以下示例顯示了使用批處理大小為 100 的批處理更新。

  • Java

  • Kotlin

public class JdbcActorDao implements ActorDao {

	private JdbcTemplate jdbcTemplate;

	public void setDataSource(DataSource dataSource) {
		this.jdbcTemplate = new JdbcTemplate(dataSource);
	}

	public int[][] batchUpdate(final Collection<Actor> actors) {
		int[][] updateCounts = jdbcTemplate.batchUpdate(
				"update t_actor set first_name = ?, last_name = ? where id = ?",
				actors,
				100,
				(PreparedStatement ps, Actor actor) -> {
					ps.setString(1, actor.getFirstName());
					ps.setString(2, actor.getLastName());
					ps.setLong(3, actor.getId().longValue());
				});
		return updateCounts;
	}

	// ... additional methods
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {

	private val jdbcTemplate = JdbcTemplate(dataSource)

	fun batchUpdate(actors: List<Actor>): Array<IntArray> {
		return jdbcTemplate.batchUpdate(
					"update t_actor set first_name = ?, last_name = ? where id = ?",
					actors, 100) { ps, argument ->
			ps.setString(1, argument.firstName)
			ps.setString(2, argument.lastName)
			ps.setLong(3, argument.id)
		}
	}

	// ... additional methods
}

此呼叫的批處理更新方法返回一個 int 陣列的陣列,其中包含每個批處理的陣列條目,以及每個更新受影響的行數陣列。頂級陣列的長度表示執行的批處理數量,第二級陣列的長度表示該批處理中的更新數量。每個批處理中的更新數量應為所有批處理提供的批處理大小(除了最後一個可能較少),具體取決於提供的更新物件的總數。每個更新語句的更新計數是由 JDBC 驅動程式報告的。如果計數不可用,JDBC 驅動程式將返回 -2

© . This site is unofficial and not affiliated with VMware.