15.4 JDBC批量操作

大多数JDBC驱动在针对同一SQL语句做批处理时能够获得更好的性能。批量更新操作可以节省数据库的来回传输次数。

15.4.1 使用JdbcTemplate来进行基础的批量操作

通过JdbcTemplate 实现批处理需要实现特定接口的两个方法,BatchPreparedStatementSetter,并且将其作为第二个参数传入到batchUpdate方法调用中。使用getBatchSize提供当前批量操作的大小。使用setValues方法设置语句的Value参数。这个方法会按getBatchSize设置中指定的调用次数。下面的例子中通过传入列表来批量更新actor表。在这个例子中整个列表使用了批量操作:

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) {
		int[] updateCounts = jdbcTemplate.batchUpdate("update t_actor set first_name = ?, " +
				"last_name = ? where id = ?",
			new BatchPreparedStatementSetter() {
				public void setValues(PreparedStatement ps, int i) throws SQLException {
						ps.setString(1, actors.get(i).getFirstName());
						ps.setString(2, actors.get(i).getLastName());
						ps.setLong(3, actors.get(i).getId().longValue());
					}

					public int getBatchSize() {
						return actors.size();
					}
				});
		return updateCounts;
	}

	// ... additional methods
}

如果你需要处理批量更新或者从文件中批量读取,你可能需要确定一个合适的批处理大小,但是最后一次批处理可能达不到这个大小。在这种场景下你可以使用InterruptibleBatchPreparedStatementSetter接口,允许在输入流耗尽之后终止批处理,isBatchExhausted方法使得你可以指定批处理结束时间。

15.4.2 对象列表的批量处理

JdbcTemplate和NamedParameterJdbcTemplate都提供了批量更新的替代方案。这个时候不是实现一个特定的批量接口,而是在调用时传入所有的值列表。框架会循环访问这些值并且使用内部的SQL语句setter方法。你是否已声明参数对应API是不一样的。针对已声明参数你需要传入qlParameterSource数组,每项对应单次的批量操作。你可以使用SqlParameterSource.createBatch方法来创建这个数组,传入JavaBean数组或是包含参数值的Map数组。

下面是一个使用已声明参数的批量更新例子:

public class JdbcActorDao implements ActorDao {
	private NamedParameterTemplate namedParameterJdbcTemplate;

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

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

	// ... additional methods
}

对于使用“?”占位符的SQL语句,你需要传入带有更新值的对象数组。对象数组每一项对应SQL语句中的一个占位符,并且传入顺序需要和SQL语句中定义的顺序保持一致。

下面是使用经典JDBC“?”占位符的例子:

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<Object[]>();
		for (Actor actor : actors) {
			Object[] values = new Object[] {
					actor.getFirstName(),
					actor.getLastName(),
					actor.getId()};
			batch.add(values);
		}
		int[] updateCounts = jdbcTemplate.batchUpdate(
				"update t_actor set first_name = ?, last_name = ? where id = ?",
				batch);
		return updateCounts;
	}

	// ... additional methods

}

上面所有的批量更新方法都返回一个数组,包含具体成功的行数。这个计数是由JDBC驱动返回的。如果拿不到计数。JDBC驱动会返回-2。

15.4.3 多个批处理操作
上面最后一个例子更新的批处理数量太大,最好能再分割成更小的块。最简单的方式就是你多次调用batchUpdate来实现,但是可以有更优的方法。要使用这个方法除了SQL语句,还需要传入参数集合对象,每次Batch的更新数和一个ParameterizedPreparedStatementSetter去设置预编译SQL语句的参数值。框架会循环调用提供的值并且将更新操作切割成指定数量的小批次。

下面的例子设置了更新批次数量为100的批量更新操作:

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,
				new ParameterizedPreparedStatementSetter<Actor>() {
					public void setValues(PreparedStatement ps, Actor argument) throws SQLException {
						ps.setString(1, argument.getFirstName());
						ps.setString(2, argument.getLastName());
						ps.setLong(3, argument.getId().longValue());
					}
				});
		return updateCounts;
	}

	// ... additional methods

}

这个调用的批量更新方法返回一个包含int数组的二维数组,包含每次更新生效的行数。第一层数组长度代表批处理执行的数量,第二层数组长度代表每个批处理生效的更新数。每个批处理的更新数必须和所有批处理的大小匹配,除非是最后一次批处理可能小于这个数,具体依赖于更新对象的总数。每次更新语句生效的更新数由JDBC驱动提供。如果更新数量不存在,JDBC驱动会返回-2


书籍推荐