对于最新的稳定版本,请使用 Spring Framework 7.0.6!spring-doc.cadn.net.cn

JDBC批量操作

大多数 JDBC 驱动程序在您对同一预编译语句进行多次调用时会提供更好的性能。通过将更新分组为批次,您可以减少与数据库之间的往返次数。spring-doc.cadn.net.cn

使用JdbcTemplate进行基本批处理操作

您通过实现特殊接口JdbcTemplate的两个方法,并在您的batchUpdate方法调用中将该实现作为第二个参数传递,来完成BatchPreparedStatementSetter批处理。您可以使用getBatchSize方法来提供当前批处理的大小。您可以使用setValues方法来设置预编译语句的参数值。此方法的调用次数与您在getBatchSize调用中指定的次数相同。下面的示例根据列表中的条目更新t_actor表,并且整个列表用作批处理:spring-doc.cadn.net.cn

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 方法允许您标记一批处理的结束。spring-doc.cadn.net.cn

对对象列表进行批处理操作

JdbcTemplateNamedParameterJdbcTemplate 都提供了一种替代方法来提供批处理更新。不需要实现特殊的批处理接口,您可以在调用中作为列表提供所有参数值。框架会遍历这些值,并使用内部的预编译语句设置器。API 会根据您是否使用命名参数而有所不同。对于命名参数,您需要提供一个 SqlParameterSource 的数组,每个批次成员对应一个条目。您可以使用 SqlParameterSourceUtils.createBatch 的便捷方法来创建此数组,传入一个 bean 样式的对象数组(具有与参数对应的 getter 方法)、String 键的 Map 实例(包含相应的参数作为值),或两者的组合。spring-doc.cadn.net.cn

以下示例显示了使用命名参数的批量更新:spring-doc.cadn.net.cn

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 语句中定义的顺序相同。spring-doc.cadn.net.cn

以下示例与前面的示例相同,只是它使用了经典的 JDBC ? 占位符:spring-doc.cadn.net.cn

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的值。spring-doc.cadn.net.cn

在此场景下,若底层PreparedStatement的值被自动设置,则需要根据给定的Java类型推导出每个值对应的JDBC类型。 虽然通常效果良好,但存在潜在问题(例如包含null值的Map)。Spring默认情况下会调用ParameterMetaData.getParameterType, 这可能对您的JDBC驱动造成较大开销。建议使用较新的驱动版本,若应用程序遇到特定性能问题, 可考虑将spring.jdbc.getParameterType.ignore属性设置为true(作为JVM系统属性或通过 SpringProperties机制设置)。spring-doc.cadn.net.cn

或者,您也可以考虑显式指定相应的JDBC类型, 可以通过 BatchPreparedStatementSetter(如前所示), 通过提供给基于 List<Object[]> 的调用的显式类型数组, 通过在自定义 MapSqlParameterSource 实例上的 registerSqlType 调用, 通过 BeanPropertySqlParameterSource(即使对于空值也从 Java 声明的属性类型推导 SQL 类型), 或通过提供单独的 SqlParameterValue 实例而非普通空值来实现。spring-doc.cadn.net.cn

包含多个批次的批量操作

前面的批处理更新示例涉及非常大的批次,您希望将它们拆分为几个较小的批次。您可以使用前面提到的方法通过多次调用batchUpdate方法来实现,但现在有一个更方便的方法。该方法除了SQL语句外,还接受一个包含参数的Collection,每个批次要进行的更新次数,以及一个ParameterizedPreparedStatementSetter来设置预编译语句的参数值。框架会遍历提供的值,并将更新调用拆分为指定大小的批次。spring-doc.cadn.net.cn

以下示例显示了一个使用批处理大小为100的批量更新:spring-doc.cadn.net.cn

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
}

此调用的批量更新方法返回一个包含数组的数组,每个数组对应一个批次,并包含每个更新的受影响行数数组。 最外层数组的长度表示运行的批次数量,第二层数组的长度表示该批次中的更新数量。每个批次中的更新数量应为所有批次提供的批次大小(最后一个批次可能更少),具体取决于提供的更新对象总数。每个更新语句的更新计数是JDBC驱动程序报告的值。如果无法获得计数,JDBC驱动程序将返回一个值-2spring-doc.cadn.net.cn