Независимо от того, что я не могу выставлять операторы MySQL INSERT в Hibernate

В настоящее время я сталкиваюсь с известной и распространенной проблемой вставки в Hibernate.

Мне нужно сохранить партии по 5 миллионов строк. Сначала я пытаюсь использовать гораздо более легкую полезную нагрузку. Поскольку я должен вставлять объекты только из двух типов (сначала все записи типа A, затем все записи типа B, все указывают на общий тип C ManyToOne parent), я хотел бы извлечь максимальную выгоду из вставки пакета JDBC.

Я уже прочитал много документации, но ни один из тех, что я пробовал, работал.

  • Я знаю, что для использования пакетных вставок я не должен использовать генератор сущностей. Поэтому я удалил идентификатор AUTO_INCREMENT, и я устанавливаю идентификатор с трюком: SELECT MAX(ID) FROM ENTITIES и каждый раз увеличиваю.
  • Я знаю, что я должен регулярно очищать сессию. Я буду отправлять код вперед, но в любом случае я выполняю транзакцию каждые 500 элементов.
  • Я знаю, что мне нужно установить hibernate.jdbc.batch_size в соответствии с объемом моего приложения, поэтому я установил его в интеграцию LocalSessionFactoryBean (Spring ORM)
  • Я знаю. Мне нужно включить переписывание пакетных операторов в URL-адрес соединения.

Вот мои сущности

Общий родительский объект. Это сначала вставляется в одну транзакцию. Я не забочусь об автоинкрестном столбце здесь. Только запись одна для каждого пакетного задания

@Entity
@Table(...)
@SequenceGenerator(...)
public class Deal
{

    @Id
    @Column(
            name = "DEAL_ID",
            nullable = false)
    @GeneratedValue(
            strategy = GenerationType.AUTO)
    protected Long id;

    ................
}

Один из детей (скажем, 2.5M записей за партию)

@Entity
@Table(
        name = "TA_LOANS")
public class Loan
{

    @Id
    @Column(
            name = "LOAN_ID",
            nullable = false)
    protected Long id;

    @ManyToOne(
            optional = false,
            targetEntity = Deal.class,
            fetch = FetchType.LAZY)
    @JoinColumn(
            name = "DEAL_ID",
            nullable = false)
    protected Deal deal;


    .............
}

Другие типы детей. Скажем, другие 2.5M записи

@Entity
@Table(
        name = "TA_BONDS")
public class Bond
{

    @Id
    @Column(
            name = "BOND_ID")

    @ManyToOne(
            fetch = FetchType.LAZY,
            optional = false,
            targetEntity = Deal.class)
    @JoinColumn(
            name = "DEAL_ID",
            nullable = false,
            updatable = false)
    protected Deal deal;

}

Упрощенный код, который вставляет записи

    long loanIdCounter = loanDao.getMaxId(), bondIdCounter = bondDao.getMaxId(); //Perform SELECT MAX(ID)

    Deal deal = null;

    List<Bond> bondList = new ArrayList<Bond>(COMMIT_BATCH_SIZE); //500 constant value
    List<Loan> loanList = new ArrayList<Loan>(COMMIT_BATCH_SIZE);

    for (String msg: inputStreamReader)
    {
        log.debug(msg.toString());

        if (this is a deal)
        {
            Deal deal = parseDeal(msg.getMessage());

            deal = dealManager.persist(holder.deal); //Called in a separate transaction using Spring annotation @Transaction(REQUIRES_NEW)

        }
        else if (this is a loan)
        {

            Loan loan = parseLoan(msg.getMessage());
            loan.setId(++loanIdCounter);
            loan.setDeal(deal);

            loanList.add(loan);

            if (loanList.size() == COMMIT_BATCH_SIZE)
            {
                loanManager.bulkInsert(loanList); //Perform a bulk insert in a single transaction, not annotated but handled manually this time
                loanList.clear();
            }
        }
        else if (this is a bond)
        {
            Bond bond = parseBond(msg.getMessage());
            bond.setId(++bondIdCounter);
            bond.setDeal(deal);

            bondList.add(bond);



            if (bondList.size() == COMMIT_BATCH_SIZE) //As above
            {
                bondManager.bulkInsert(bondList);
                bondList.clear();

            }
        }
    }

    if (!bondList.isEmpty())
        bondManager.bulkInsert(bondList);
    if (!loanList.isEmpty())
        loanManager.bulkInsert(loanList);
    //Flush remaining items, not important

Реализация bulkInsert:

@Override
public void bulkInsert(Collection<Bond> bonds)
{
    // StatelessSession session = sessionFactory.openStatelessSession();
    Session session = sessionFactory.openSession();
    try
    {
        Transaction t = session.beginTransaction();
        try
        {
            for (Bond bond : bonds)
                // session.persist(bond);
                // session.insert(bond);
                session.save(bond);
        }
        catch (RuntimeException ex)
        {
            t.rollback();
        }
        finally
        {
            t.commit();
        }
    }
    finally
    {
        session.close();
    }

}

Как вы можете видеть из комментариев, я пробовал несколько комбинаций stateful/stateless session. Ничего не работало.

My dataSource - ComboPooledDataSource со следующим URL

<b:property name="jdbcUrl" value="jdbc:mysql://server:3306/db?autoReconnect=true&amp;rewriteBatchedStatements=true" />

Мой SessionFactory

<b:bean id="sessionFactory" class="class.that.extends.org.springframework.orm.hibernate3.LocalSessionFactoryBean" lazy-init="false" depends-on="dataSource">
        <b:property name="dataSource" ref="phoenixDataSource" />
        <b:property name="hibernateProperties">
            <b:props>
                <b:prop key="hibernate.dialect">${hibernate.dialect}</b:prop> <!-- MySQL5InnoDb-->
                <b:prop key="hibernate.show_sql">${hibernate.showSQL}</b:prop>
                <b:prop key="hibernate.jdbc.batch_size">500</b:prop>
                <b:prop key="hibernate.jdbc.use_scrollable_resultset">false</b:prop>
                <b:prop key="hibernate.cache.use_second_level_cache">false</b:prop>
                <b:prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</b:prop>
                <b:prop key="hibernate.cache.use_query_cache">false</b:prop>
                <b:prop key="hibernate.validator.apply_to_ddl">false</b:prop>
                <b:prop key="hibernate.validator.autoregister_listeners">false</b:prop>
                <b:prop key="hibernate.order_inserts">true</b:prop>
                <b:prop key="hibernate.order_updates">true</b:prop>
            </b:props>
        </b:property>
</b:bean>

Даже если мой класс по всему проекту расширяется LocalSessionFactoryBean, он не выполняет переопределять свои методы (добавляет только несколько методов для всего проекта)

Я с ума сошел с нескольких дней. Я прочитал несколько статей, и ни один из них не помог мне включить пакетные вставки. Я запускаю весь свой код из тестов JUnit, оснащенных контекстом Spring (поэтому я могу @Autowire мои классы). Все мои попытки производят только множество отдельных INSERT операторов

Что мне не хватает?

Ответы

Ответ 1

Вероятно, ваши запросы переписываются, но вы не знаете, если посмотреть на журналы Hibernate SQL. Hibernate не переписывает инструкции вставки - драйвер MySQL переписывает их. Другими словами, Hibernate отправит несколько драйверов insert в драйвер, а затем драйвер перепишет их. Таким образом, журналы Hibernate показывают только то, что SQL Hibernate отправлено в драйвер, а не то, что SQL-драйвер отправил в базу данных.

Вы можете проверить это, включив параметр MySQL profileSQL в URL-адрес соединения:

<b:property name="jdbcUrl" value="jdbc:mysql://server:3306/db?autoReconnect=true&amp;rewriteBatchedStatements=true&amp;profileSQL=true" />

Используя пример, подобный вашему, это выглядит так:

insert into Person (firstName, lastName, id) values (?, ?, ?)
insert into Person (firstName, lastName, id) values (?, ?, ?)
insert into Person (firstName, lastName, id) values (?, ?, ?)
insert into Person (firstName, lastName, id) values (?, ?, ?)
insert into Person (firstName, lastName, id) values (?, ?, ?)
insert into Person (firstName, lastName, id) values (?, ?, ?)
insert into Person (firstName, lastName, id) values (?, ?, ?)
insert into Person (firstName, lastName, id) values (?, ?, ?)
insert into Person (firstName, lastName, id) values (?, ?, ?)
insert into Person (firstName, lastName, id) values (?, ?, ?)
Wed Feb 05 13:29:52 MST 2014 INFO: Profiler Event: [QUERY]  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) duration: 1 ms, connection-id: 81, statement-id: 33, resultset-id: 0, message: insert into Person (firstName, lastName, id) values ('person1', 'Name', 1),('person2', 'Name', 2),('person3', 'Name', 3),('person4', 'Name', 4),('person5', 'Name', 5),('person6', 'Name', 6),('person7', 'Name', 7),('person8', 'Name', 8),('person9', 'Name', 9),('person10', 'Name', 10)

Первые 10 строк регистрируются Hibernate, хотя это не то, что фактически отправляется в базу данных MySQL. Последняя строка исходит от драйвера MySQL, и на нем явно отображается одна вставка пакета с несколькими значениями, и это то, что фактически отправляется в базу данных MySQL.