Більшість драйверів JDBC забезпечують підвищення продуктивності при пакетному множинному зверненні до одного й того ж скомпільованого стейтменту. Групуючи оновлення пакетів, ви обмежуєте кількість циклічних звернень до бази даних.

Основні пакетні операції за допомогою JdbcTemplate

Ви виконуєте пакетну обробку JdbcTemplate, реалізуючи два методи спеціального інтерфейсу BatchPreparedStatementSetter і передаючи цю реалізацію як другий параметр у виклику методу batchUpdate. Метод getBatchSize можна використовувати для встановлення значень параметрів скомпільованого стейтменту. Метод setValues можна використовувати для встановлення значень параметрів підготовленої інструкції. Цей метод викликається та кількість разів, яка буде вказана у виклику getBatchSize. У наступному прикладі таблиця t_actor оновлюється на основі записів у списку, причому як пакет використовується весь список:

Java
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( , int i) throws SQLException { Actor actor = actors.get(i); ps.setString(1, actor.getFirstName()); ps.setString(2, actor.getLastName()); .getId().longValue());} public int getBatchSize() { return actors.size(); } }); } // ... додаткові методи }
Kotlin
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 = ? alues (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 }) } // ... додаткові методи }

Якщо ви обробляєте потік оновлень або читання з файлу, вас вже може бути переважний розмір пакета, але в останньому пакеті може не виявитися такої кількості записів. У цьому випадку можна використовувати інтерфейс InterruptibleBatchPreparedStatementSetter, який дозволяє перервати пакет після вичерпання джерела вхідних даних. Метод isBatchExhausted дозволяє сигналізувати про завершення пакета.

Пакетні операції з використанням списку об'єктів

Шаблон JdbcTemplate та NamedParameterJdbcTemplate надають альтернативний спосіб забезпечення пакетного оновлення. Замість реалізації спеціалізованого пакетного інтерфейсу, ви надаєте всі значення параметрів у виклику у вигляді списку. Фреймворк перебирає ці значення та використовує внутрішній сеттер скомпільованих стейтментів. API-інтерфейс залежить від того, використовуєте ви іменовані параметри. Для іменованих параметрів ви вказуєте масив SqlParameterSource по одному запису для кожного члена групи. Можна використовувати допоміжні методи SqlParameterSourceUtils.createBatch для створення цього масиву, передаючи масив об'єктів на основі бина (з використанням гетерів, що відповідають параметрам), екземплярів Map з String-ключом (що містить відповідні параметри як значення), або комбінації і того, і іншого.

У наступному прикладі показано пакетне оновлення з використанням іменованих параметрів:

Java
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", Sq. } // ... додаткові методи }
Kotlin
class JdbcActorDao(dataSource: DataSource) : ActorDao { private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource) fun batchUpdate(actors: List<Actor>): IntArray { return this.namedParameterJdbcTemplate.batchUpdate( "update t_actor set fir :id", SqlParameterSourceUtils. createBatch(actors)); } // ... додаткові методи }

Для SQL-стейтмента, що використовує класичні плейсхолдери ?, ви передаєте список, що містить масив об'єктів із значеннями оновлення. Цей масив об'єктів повинен містити по одному елементу для кожного плейсхолдера в SQL-стейтменті, і вони повинні розташовуватися в тому ж порядку, в якому вони визначені в SQL-стейтменті. Наступний приклад аналогічний попередньому, за винятком того що в ньому використовуються класичні для JDBC плейсхолдери ?:

Java
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); } return this.jdbcTemplate.batchUpdate( "update t_actor set first_name = ?, last_name = ? where id = ?", batch); } // ... додаткові методи }
Kotlin
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. , actor.lastName, actor.id)) } return jdbcTemplate.batchUpdate( "update t_actor set first_name = ?, last_name = ? where id = ?", batch) } // ... додаткові методи }

Всі методи пакетного оновлення, які ми описали раніше, повертають масив int, який містить кількість порушених рядків для кожного пакетного запису. Цей лічильник повідомляє драйвер JDBC. Якщо лічильник недоступний, драйвер JDBC повертає значення -2.

У такому сценарії, при автоматичному встановленні значень у базовий PreparedStatement, відповідний тип JDBC для кожного значення повинен бути похідним від цього типу Java. Хоча зазвичай це добре працює, існує ймовірність виникнення проблем (наприклад, з null значеннями, що містяться в Map). У такому випадку Spring за промовчанням викликає ParameterMetaData.getParameterType, що може бути витратним при використанні драйвера JDBC. Слід використовувати останню версію драйвера та розглянути можливість встановлення властивості spring.jdbc.getParameterType.ignore у true (як системна властивість JVM або через механізм SpringProperties ), якщо ви стикаєтеся з проблемою з продуктивністю (як повідомляється в Oracle 12c, JBoss і PostgreSQL).

Крім того, можна розглянути варіант явної вказівки відповідних типів JDBC або через BatchPreparedStatementSetter (як показано раніше), або через явний масив типів, що передається виклику на основі List<Object[]>, або через виклик registerSqlType для екземпляра користувача MapSqlParameterSource, або через BeanPropertySqlParameterSource, який виводить тип SQL з оголошеного в Java типу властивості навіть для порожнього значення.

Пакетні операції з використанням декількох пакетів

У попередньому прикладі пакетного оновлення йшлося про пакети, які настільки об'ємні, що потрібно розбити їх на кілька менших пакетів. Здійснити це можна за допомогою методів, згаданих раніше, виконавши кілька викликів методу batchUpdate, але тепер є і зручніший метод. Цей метод приймає, крім SQL-інструкції, Collection об'єктів, що містить параметри, кількість оновлень для кожного пакета та ParameterizedPreparedStatementSetter для встановлення значень параметрів скомпілюваного стейтменту. Фреймворк перебирає надані значення та розбиває виклики оновлення на пакети зазначеного розміру.

У наступному прикладі показано пакетне оновлення, в якому використовується розмір пакета 100:

Java
 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 PreparedStatement ps, Actor actor) -> { ps.setString(1, actor.getFirstName()); ps.setString(2, actor.getLastName()); ));}); return updateCounts; } // ... додаткові методи }
Kotlin
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) } } // ... додаткові методи }

Метод пакетного оновлення для цього виклику повертає масив масивів int, який містить запис масиву для кожного пакета з масивом кількості порушених рядків для кожного оновлення. Довжина масиву верхнього рівня вказує на кількість запущених пакетів, а довжина масиву другого рівня – кількість оновлень у цьому пакеті. Кількість оновлень у кожному пакеті повинна відповідати розміру пакета, вказаному для всіх пакетів (за винятком останнього, який може бути меншим), залежно від загальної кількості зазначених об'єктів, що оновлюються. Лічильник оновлень для кожної інструкції оновлення – це лічильник, який повідомляє драйвер JDBC. Якщо лічильник недоступний, драйвер JDBC повертає значення -2.