Loading...
Spring Framework Reference Documentation 7.0.2의 JDBC Batch Operations의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
대부분의 JDBC driver는 동일한 prepared statement에 대한 여러 호출을 batch 처리하면 성능이 향상됩니다. update를 batch로 그룹화하면 database로의 round trip 횟수를 줄일 수 있습니다.
JdbcTemplateJdbcTemplate batch 처리는 특수 인터페이스인 BatchPreparedStatementSetter의 두 가지 메서드를 구현하고, 그 구현체를 batchUpdate 메서드 호출의 두 번째 파라미터로 전달함으로써 수행합니다. getBatchSize 메서드를 사용하여 현재 batch의 크기를 제공할 수 있습니다.
setValues 메서드를 사용하여 prepared statement의 파라미터에 대한 값을 설정할 수 있습니다. 이 메서드는 getBatchSize 호출에서 지정한 횟수만큼 호출됩니다. 다음 예제는 list의 entry를 기반으로 t_actor table을 update하며, 전체 list가 batch로 사용됩니다:
1public class JdbcActorDao implements ActorDao { 2 3 private JdbcTemplate jdbcTemplate; 4 5 public void setDataSource(DataSource dataSource) { 6 this.jdbcTemplate = new JdbcTemplate(dataSource); 7 } 8 9 public int[] batchUpdate(final List<Actor> actors) { 10 return this.jdbcTemplate.batchUpdate( 11 "update t_actor set first_name = ?, last_name = ? where id = ?", 12 new BatchPreparedStatementSetter() { 13 public void setValues(PreparedStatement ps, int i) throws SQLException { 14 Actor actor = actors.get(i); 15 ps.setString(1, actor.getFirstName()); 16 ps.setString(2, actor.getLastName()); 17 ps.setLong(3, actor.getId().longValue()); 18 } 19 public int getBatchSize() { 20 return actors.size(); 21 } 22 }); 23 } 24 25 // ... additional methods 26}
1class JdbcActorDao(dataSource: DataSource) : ActorDao { 2 3 private val jdbcTemplate = JdbcTemplate(dataSource) 4 5 fun batchUpdate(actors: List<Actor>): IntArray { 6 return jdbcTemplate.batchUpdate( 7 "update t_actor set first_name = ?, last_name = ? where id = ?", 8 object: BatchPreparedStatementSetter { 9 override fun setValues(ps: PreparedStatement, i: Int) { 10 ps.setString(1, actors[i].firstName) 11 ps.setString(2, actors[i].lastName) 12 ps.setLong(3, actors[i].id) 13 } 14 15 override fun getBatchSize() = actors.size 16 }) 17 } 18 19 // ... additional methods 20}
update stream을 처리하거나 file에서 읽어들이는 경우, 선호하는 batch 크기가 있을 수 있지만, 마지막 batch는 해당 개수만큼 entry를 갖지 못할 수도 있습니다. 이 경우, InterruptibleBatchPreparedStatementSetter 인터페이스를 사용할 수 있으며, 이를 통해 입력 소스가 소진되면 batch를 중단할 수 있습니다.
isBatchExhausted 메서드를 사용하면 batch의 끝을 신호할 수 있습니다.
JdbcTemplate와 NamedParameterJdbcTemplate는 모두 batch update를 제공하는 또 다른 방법을 제공합니다. 특수 batch 인터페이스를 구현하는 대신, 호출 시 파라미터 값 전체를 list로 제공합니다. 프레임워크는 이 값들을 루프 처리하면서 내부 prepared statement setter를 사용합니다.
named parameter 사용 여부에 따라 API는 달라집니다. named parameter의 경우, batch의 각 member에 대해 하나의 entry를 갖는 SqlParameterSource 배열을 제공합니다. SqlParameterSourceUtils.createBatch 편의 메서드를 사용하여 이 배열을 생성할 수 있으며, 파라미터에 대응하는 getter 메서드를 가진 bean-style 객체 배열, 해당 파라미터를 값으로 포함하는 String key의 Map 인스턴스, 또는 이 둘을 섞어서 전달합니다.
다음 예제는 named parameter를 사용한 batch update를 보여줍니다:
1public class JdbcActorDao implements ActorDao { 2 3 private NamedParameterJdbcTemplate namedParameterJdbcTemplate; 4 5 public void setDataSource(DataSource dataSource) { 6 this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); 7 } 8 9 public int[] batchUpdate(List<Actor> actors) { 10 return this.namedParameterJdbcTemplate.batchUpdate( 11 "update t_actor set first_name = :firstName, last_name = :lastName where id = :id", 12 SqlParameterSourceUtils.createBatch(actors)); 13 } 14 15 // ... additional methods 16}
1class JdbcActorDao(dataSource: DataSource) : ActorDao { 2 3 private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource) 4 5 fun batchUpdate(actors: List<Actor>): IntArray { 6 return this.namedParameterJdbcTemplate.batchUpdate( 7 "update t_actor set first_name = :firstName, last_name = :lastName where id = :id", 8 SqlParameterSourceUtils.createBatch(actors)) 9 } 10 11 // ... additional methods 12}
classic ? placeholder를 사용하는 SQL statement의 경우, update 값을 가진 객체 배열을 포함하는 list를 전달합니다. 이 객체 배열은 SQL statement의 각 placeholder에 대해 하나의 entry를 가져야 하며, SQL statement에 정의된 순서와 동일해야 합니다.
다음 예제는 앞의 예제와 동일하지만, classic JDBC ? placeholder를 사용합니다:
1public class JdbcActorDao implements ActorDao { 2 3 private JdbcTemplate jdbcTemplate; 4 5 public void setDataSource(DataSource dataSource) { 6 this.jdbcTemplate = new JdbcTemplate(dataSource); 7 } 8 9 public int[] batchUpdate(final List<Actor> actors) { 10 List<Object[]> batch = new ArrayList<>(); 11 for (Actor actor : actors) { 12 Object[] values = new Object[] { 13 actor.getFirstName(), actor.getLastName(), actor.getId()}; 14 batch.add(values); 15 } 16 return this.jdbcTemplate.batchUpdate( 17 "update t_actor set first_name = ?, last_name = ? where id = ?", 18 batch); 19 } 20 21 // ... additional methods 22}
1class JdbcActorDao(dataSource: DataSource) : ActorDao { 2 3 private val jdbcTemplate = JdbcTemplate(dataSource) 4 5 fun batchUpdate(actors: List<Actor>): IntArray { 6 val batch = mutableListOf<Array<Any>>() 7 for (actor in actors) { 8 batch.add(arrayOf(actor.firstName, actor.lastName, actor.id)) 9 } 10 return jdbcTemplate.batchUpdate( 11 "update t_actor set first_name = ?, last_name = ? where id = ?", batch) 12 } 13 14 // ... additional methods 15}
앞에서 설명한 모든 batch update 메서드는 각 batch entry에 대해 영향을 받은 row 수를 담고 있는 int 배열을 반환합니다. 이 개수는 JDBC driver에 의해 보고됩니다. 개수를 사용할 수 없는 경우, JDBC driver는 -2 값을 반환합니다.
In such a scenario, with automatic setting of values on an underlying
PreparedStatement, the corresponding JDBC type for each value needs to be derived from the given Java type. While this usually works well, there is a potential for issues (for example, with Map-containednullvalues). Spring, by default, callsParameterMetaData.getParameterTypein such a case, which can be expensive with your JDBC driver. You should use a recent driver version and consider setting thespring.jdbc.getParameterType.ignoreproperty totrue(as a JVM system property or via theSpringPropertiesmechanism) if you encounter a specific performance issue for your application. As of 6.1.2, Spring bypasses the defaultgetParameterTyperesolution on PostgreSQL and MS SQL Server. This is a common optimization to avoid further roundtrips to the DBMS just for parameter type resolution which is known to make a very significant difference on PostgreSQL and MS SQL Server specifically, in particular for batch operations. If you happen to see a side effect, for example, when setting a byte array to null without specific type indication, you may explicitly set thespring.jdbc.getParameterType.ignore=falseflag as a system property (see above) to restore fullgetParameterTyperesolution. Alternatively, you could consider specifying the corresponding JDBC types explicitly, either through aBatchPreparedStatementSetter(as shown earlier), through an explicit type array given to aList<Object[]>based call, throughregisterSqlTypecalls on a customMapSqlParameterSourceinstance, through aBeanPropertySqlParameterSourcethat derives the SQL type from the Java-declared property type even for a null value, or through providing individualSqlParameterValueinstances instead of plain null values.
앞의 batch update 예제는 batch가 너무 커서 여러 개의 더 작은 batch로 나누고 싶은 경우를 다룹니다. 앞에서 언급한 메서드에 대해 batchUpdate 메서드를 여러 번 호출함으로써 이를 수행할 수 있지만, 이제 더 편리한 메서드가 있습니다.
이 메서드는 SQL statement 외에, 파라미터를 포함하는 객체의 Collection, 각 batch마다 수행할 update 수, 그리고 prepared statement의 파라미터에 대한 값을 설정하기 위한 ParameterizedPreparedStatementSetter를 인자로 받습니다. 프레임워크는 제공된 값들을 루프 처리하면서 지정된 크기의 batch로 update 호출을 분할합니다.
다음 예제는 batch 크기 100을 사용하는 batch update를 보여줍니다:
1public class JdbcActorDao implements ActorDao { 2 3 private JdbcTemplate jdbcTemplate; 4 5 public void setDataSource(DataSource dataSource) { 6 this.jdbcTemplate = new JdbcTemplate(dataSource); 7 } 8 9 public int[][] batchUpdate(final Collection<Actor> actors) { 10 int[][] updateCounts = jdbcTemplate.batchUpdate( 11 "update t_actor set first_name = ?, last_name = ? where id = ?", 12 actors, 13 100, 14 (PreparedStatement ps, Actor actor) -> { 15 ps.setString(1, actor.getFirstName()); 16 ps.setString(2, actor.getLastName()); 17 ps.setLong(3, actor.getId().longValue()); 18 }); 19 return updateCounts; 20 } 21 22 // ... additional methods 23}
1class JdbcActorDao(dataSource: DataSource) : ActorDao { 2 3 private val jdbcTemplate = JdbcTemplate(dataSource) 4 5 fun batchUpdate(actors: List<Actor>): Array<IntArray> { 6 return jdbcTemplate.batchUpdate( 7 "update t_actor set first_name = ?, last_name = ? where id = ?", 8 actors, 100) { ps, argument -> 9 ps.setString(1, argument.firstName) 10 ps.setString(2, argument.lastName) 11 ps.setLong(3, argument.id) 12 } 13 } 14 15 // ... additional methods 16}
이 호출을 위한 batch update 메서드는 각 batch에 대한 배열 entry와 각 update에 대해 영향을 받은 row 수의 배열을 포함하는 int 배열의 배열을 반환합니다. 최상위 배열의 길이는 실행된 batch의 수를 나타내며, 두 번째 수준 배열의 길이는 해당 batch에 포함된 update 수를 나타냅니다.
각 batch의 update 수는 제공된 전체 update 객체 수에 따라 (마지막 batch는 더 적을 수 있지만) 모든 batch에 대해 제공된 batch 크기와 동일해야 합니다. 각 update statement에 대한 update count는 JDBC driver에 의해 보고된 값입니다. count를 사용할 수 없는 경우, JDBC driver는 -2 값을 반환합니다.
Controlling Database Connections
Simplifying JDBC Operations with the SimpleJdbc Classes