Автор оригинала: Vlad Mihalcea.
Недавно один из моих подписчиков попросил меня ответить на вопрос на Quora о пакетной обработке , и , поскольку вопрос был действительно интересным, я решил превратить его в сообщение в блоге.
В этой статье вы узнаете, что такое пакетная обработка, почему мы ее используем и как правильно использовать ее с JPA и Hibernate.
При написании корпоративного приложения обычно разделяют вашу работу между интерфейсной системой, которая обслуживает типичный OLTP (Онлайн-обработка транзакций) трафик, и одним или несколькими пакетными процессорами, которые используются для ETL (Извлечение, преобразование, Загрузка) целей.
Пакетный процессор, как следует из его названия, разбивает обрабатываемые данные на несколько блоков, что обеспечивает следующие преимущества:
- каждый фрагмент может быть обработан отдельным рабочим потоком, что увеличивает пропускную способность и сокращает общее время обработки
- каждый фрагмент может использовать свою собственную транзакцию базы данных, поэтому в случае сбоя нам не нужно отбрасывать всю проделанную работу, просто изменяется текущая транзакция
Пакетная обработка JPA
При использовании JPA, предполагая, что вы хотите вставить 50 Post
сущностей, вот как вы должны это сделать:
int entityCount = 50; int batchSize = 25; EntityManager entityManager = entityManagerFactory() .createEntityManager(); EntityTransaction entityTransaction = entityManager .getTransaction(); try { entityTransaction.begin(); for (int i = 0; i < entityCount; i++) { if (i > 0 && i % batchSize == 0) { entityTransaction.commit(); entityTransaction.begin(); entityManager.clear(); } Post post = new Post( String.format("Post %d", i + 1) ); entityManager.persist(post); } entityTransaction.commit(); } catch (RuntimeException e) { if (entityTransaction.isActive()) { entityTransaction.rollback(); } throw e; } finally { entityManager.close(); }
Транзакция запускается с самого начала, поскольку каждый переход состояния сущности должен выполняться в рамках транзакции базы данных.
Цикл for сохраняется один Сообщение
за один раз. Однако, поскольку переходы состояний сущностей выполняются только во время сброса, мы можем сгруппировать несколько операторов SQL INSERT в одно PreparedStatement
выполнение, для которого требуется несколько наборов параметров.
Каждый раз, когда счетчик итераций (например, i
) достигает значения, кратного размеру пакета
пороговому значению, мы можем очистить EntityManager
и зафиксировать транзакцию базы данных. Фиксируя транзакцию базы данных после каждого пакетного выполнения, мы получаем следующие преимущества:
- Мы избегаем длительных транзакций, которые наносят ущерб системам реляционных баз данных MVCC .
- Мы следим за тем, чтобы в случае сбоя не потерять работу, выполненную ранее успешно выполненными пакетными заданиями.
EntityManager
очищается после каждого пакетного выполнения, чтобы мы не продолжали накапливать управляемые объекты, которые могут вызвать несколько проблем:
- Если количество сохраняемых объектов огромно, мы рискуем потерять память.
- Чем больше сущностей мы накапливаем в контексте сохранения, тем медленнее становится сброс . Таким образом, хорошей практикой является обеспечение того, чтобы контекст сохранения был как можно более узким.
Если возникает исключение, мы должны обязательно откатить текущую выполняемую транзакцию базы данных. Невыполнение этого требования может вызвать множество проблем, поскольку база данных все еще может думать, что транзакция открыта, и блокировки могут удерживаться до тех пор, пока транзакция не завершится по тайм-ауту или администратором базы данных.
В конце концов, нам нужно закрыть EntityManager
чтобы мы могли очистить контекст и освободить ресурсы уровня Сессии
.
Хотя это правильный способ пакетной обработки с помощью JPA, мы еще не закончили. Как объяснялось ранее, мы также можем извлечь выгоду из пакетных обновлений JDBC . Для этого нам необходимо предоставить следующие свойства конфигурации гибернации:
Эти свойства позволяют нам объединять несколько операторов SQL в один Подготовленное заявление
выполнение, для которого требуется одна база данных туда и обратно. Значение 25 было выбрано в соответствии с пороговым значением EntityManager
пакетного задания.
При выполнении предыдущего тестового случая Hibernate генерирует всего 2 инструкции SQL INSERT:
INSERT INTO post (title, id) values (?, ?)"], Params:[ (Post 1, 1), (Post 2, 2), (Post 3, 3), (Post 4, 4), (Post 5, 5), (Post 6, 6), (Post 7, 7), (Post 8, 8), (Post 9, 9), (Post 10, 10), (Post 11, 11), (Post 12, 12), (Post 13, 13), (Post 14, 14), (Post 15, 15), (Post 16, 16), (Post 17, 17), (Post 18, 18), (Post 19, 19), (Post 20, 20), (Post 21, 21), (Post 22, 22), (Post 23, 23), (Post 24, 24), (Post 25, 25) ] INSERT INTO post (title, id) values (?, ?)"], Params:[ (Post 26, 26), (Post 27, 27), (Post 28, 28), (Post 29, 29), (Post 30, 30), (Post 31, 31), (Post 32, 32), (Post 33, 33), (Post 34, 34), (Post 35, 35), (Post 36, 36), (Post 37, 37), (Post 38, 38), (Post 39, 39), (Post 40, 40), (Post 41, 41), (Post 42, 42), (Post 43, 43), (Post 44, 44), (Post 45, 45), (Post 46, 46), (Post 47, 47), (Post 48, 48), (Post 49, 49), (Post 50, 50) ]
Блестяще!
Знание того, как правильно спроектировать задание пакетной обработки, очень важно при разработке корпоративного приложения. К счастью, с JPA и Hibernate эту задачу очень легко выполнить. Наиболее важным аспектом является учет того, как реляционная база данных работает лучше всего, и именно это должно определять ваши проектные решения по доступу к данным.