Как повысить производительность при сохранении больших коллекций с помощью 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 строк вставляются.

http://abramsm.wordpress.com/2008/04/23/hibernate-batch-processing-why-you-may-not-be-using-it-even-if-you-think-you-are/

Означает ли это, что мне нужно разбить сохраняются в

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 для этого.

Другие вопросы по тегам