Массивная вставка с JPA + Hibernate
Мне нужно сделать массивную вставку, используя EJB 3, Hibernate, Spring Data и Oracle. Первоначально я использую Spring Данные и код ниже:
talaoAITDAO.save(taloes);
Где talaoAITDAO - это Spring Data JpaRepository подкласс, а taloes - это коллекция TalaoAIT. В этом объекте его соответствующий идентификатор имеет следующую форму:
@Id
@Column(name = "ID_TALAO_AIT")
@SequenceGenerator(name = "SQ_TALAO_AIT", sequenceName = "SQ_TALAO_AIT", allocationSize = 1000)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SQ_TALAO_AIT")
private Long id;
Также этот объект не имеет связанных объектов для выполнения каскадной вставки.
Моя проблема здесь в том, что все объекты вставляются отдельно (например, INSERT INTO TABLE(col1, col2) VALUES (val1, val2)
). Иногда это может привести к таймауту, и все вставки будут отброшены назад. Я хотел бы преобразовать эти отдельные вставки в пакетные вставки (например, INSERT INTO TABLE(col1, col2) VALUES (val11, val12), (val21, val22), (val31, val32), ...
).
Изучая альтернативы для повышения производительности, я нашел эту страницу в документации на гибернацию, кроме
путаница размера партии Hibernate и эта другая страница. Основываясь на них, я написал этот код:
Session session = super.getEntityManager().unwrap(Session.class);
int batchSize = 1000;
for (int i = 0; i < taloes.size(); i++) {
TalaoAIT talaoAIT = taloes.get(i);
session.save(talaoAIT);
if(i % batchSize == 0) {
session.flush();
session.clear();
}
taloes.add(talaoAIT);
}
session.flush();
session.clear();
Кроме того, в peristence.xml, я добавил эти свойства:
<property name="hibernate.jdbc.batch_size" value="1000" />
<property name="order_inserts" value="true" />
Однако, хотя в моих тестах я ощущал тонкую разницу (в основном с большими коллекциями и большими размерами партии), она была не такой большой, как желательно. В консоли ведения журнала я увидел, что Hibernate продолжал делать отдельные вставки, не заменяя их на массивную вставку. Как и в моей сущности, я использую генератор последовательности, я считаю, что это не проблема (согласно документации Hibernate, у меня была бы проблема, если бы я использовал генератор Identity).
Итак, мой вопрос в том, чего здесь не хватает. Некоторая конфигурация? Какой-то метод не используется?
Спасибо,
Рафаэль Афонсо.
Ответы
Ответ 1
Несколько вещей.
Сначала ваши свойства конфигурации неверны. order_inserts
должен быть hibernate.order_inserts
. В настоящее время ваши настройки игнорируются, и вы ничего не изменили.
Затем используйте EntityManager
вместо того, чтобы делать все эти неприятные вещи в спячке. EntityManager
также имеет метод flush
и clear
. Это должно по крайней мере очистить ваш метод. Без порядка это поможет немного очистить сеанс и предотвратить грязные проверки всех объектов там.
EntityManager em = getEntityManager();
int batchSize = 1000;
for (int i = 0; i < taloes.size(); i++) {
TalaoAIT talaoAIT = taloes.get(i);
em.persist(talaoAIT);
if(i % batchSize == 0) {
em.flush();
em.clear();
}
taloes.add(talaoAIT);
}
em.flush();
em.clear();
Затем вы не должны делать свои партии большими, поскольку это может вызвать проблемы с памятью, начать с чего-то вроде 50 и проверить, что/что работает лучше всего. Существует точка, в которой грязная проверка будет занимать больше времени, чем размывание и очистка базы данных. Вы хотите найти это сладкое пятно.
Ответ 2
Решение, отправленное M. Deinum, отлично поработало для меня, если я установил следующие свойства Hibernate в файле JPA persistence.xml
:
<property name="hibernate.jdbc.batch_size" value="50" />
<property name="hibernate.jdbc.batch_versioned_data" value="true" />
<property name="hibernate.order_inserts" value="true" />
<property name="hibernate.order_updates" value="true" />
<property name="hibernate.cache.use_second_level_cache" value="false" />
<property name="hibernate.connection.autocommit" value="false" />
Я использую базу данных Oracle, поэтому я также определил это:
<property name="hibernate.dialect" value="org.hibernate.dialect.Oracle10gDialect" />