Как повысить производительность при сохранении больших коллекций с помощью Spring EntityManager Hibernate
Я использую Spring/Hibernate сделано JPA с помощью org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean
и настраивается с использованием Spring xml, persistence.xml и JPA 2 аннотаций.
Функционально это нормально и сохраняется правильно. Однако у меня есть требование как можно быстрее сохранить сущность A, которая имеет двунаправленную OneToMany с большой коллекцией B.
Я использую различные опции в persistence.xml, чтобы попытаться ускорить вставки и уменьшить использование памяти (приложение пишет столько же, сколько и читает)
<property name="hibernate.id.new_generator_mappings" value="true" />
<property name="hibernate.jdbc.batch_size" value="50" />
<property name="hibernate.order_inserts" value="true" />
<property name="hibernate.order_updates" value="true" />
<property name="hibernate.cache.use_query_cache" value="false" />
<property name="hibernate.cache.use_second_level_cache" value="false" />
и упорство делается с помощью
entityManager.persist(instanceOfA)
Редактировать Дополнительная информация:
Каждая сущность имеет сгенерированный идентификатор, например
@Id
@Column(name="ID")
@GeneratedValue(strategy=GenerationType.AUTO, generator="SEQUENCE_GENERATOR")
@SequenceGenerator(name="SEQUENCE_GENERATOR", sequenceName="MY_SEQUENCE", allocationSize=50)
private Long id;
который относится к последовательности Oracle
CREATE SEQUENCE MY_SEQUENCE MINVALUE 1 MAXVALUE 999999999999999999999999999 START WITH 1 INCREMENT BY 50 NOCYCLE NOCACHE NOORDER;
Когда я запускаю код с включенным show sql, я вижу, как много операторов вставки занимают довольно много времени.
Я прочитал, что мне нужно позвонить entityManager.flush(); entityManager.clear();
каждые 50 строк вставляются.
Означает ли это, что мне нужно разбить сохраняются в
entityManager.persist(instanceOfA);
instanceOfA.addB(instanceOfB);
entityManager.persist(instanceofB);
добавление сброса каждые 50 вызовов persist()
?
Есть ли более чистый способ сделать это? (моя фактическая иерархия объектов имеет около 7 уровней отношений, таких как A и B)
Я думал об использовании JDBC для вставок, но я ненавижу писать картографы строк:)
Я слышал о org.hibernate.StatelessSession
но нет способа получить это от менеджера сущностей JPA без приведения к SessionFactory в какой-то момент - опять же, не очень чистый.
Заранее спасибо!
2 ответа
Я столкнулся с той же проблемой в одном из моих проектов. Я использовал Hibernate с бэкэндом MySQL с identity
Генератор идентификаторов. Проблема заключается в том, что Hibernate должен поразить базу данных один раз для каждой сохраненной сущности, чтобы фактически получить для нее идентификатор. Я перешел на increment
генератор и увидел немедленную выгоду (все вкладыши были в пакетном режиме).
@Id
@GeneratedValue(generator = "increment")
@GenericGenerator(name = "increment", strategy = "increment")
@Column(name = "id", nullable = false)
private long id;
increment
Генератор генерирует идентификаторы в памяти и не должен попадать в базу данных. Я предполагаю, что sequence
Генератор также должен попасть в базу данных, как это определено в базе данных. Недостаток использования increment
Это значит, что Hibernate должен иметь эксклюзивный доступ для вставки в базу данных, и это может привести к сбою в кластерной установке.
Еще один трюк, который я использовал, чтобы добавить rewriteBatchedStatements=true
на URL JDBC. Это специфично для MySQL, но я думаю, что для Oracle может существовать аналогичная директива.
И этот трюк "вызов флеша после каждого n вставок" тоже работает. Вот пример кода для этого (с использованием классов google-guava):
public List<T> saveInBatches(final Iterable<? extends T> entities, final int batchSize) {
return ImmutableList.copyOf(
Iterables.concat(
Iterables.transform(
Iterables.partition(entities, batchSize),
new Function<List<? extends T>, Iterable<? extends T>>() {
@Override
public Iterable<? extends T> apply(final List<? extends T> input) {
List<T> saved = save(input); flush(); return saved;
}})));
}
public List<T> save(Iterable<? extends T> entities) {
List<T> result = new ArrayList<T>();
for (T entity : entities) {
entityManager.persist(entity);
result.add(entity);
}
return result;
}
Используйте чистый JDBC для больших / больших вставок. Не используйте ORM Framework для этого.