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

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

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

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

  • Я знаю, что для использования пакетных вставок я не должен использовать генератор сущностей. Поэтому я удалил AUTO_INCREMENT ID и я устанавливаю ID с помощью трюка: 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,5 миллиона записей в партии)

@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();
    }

}

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

мой 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 отправит несколько операторов вставки в драйвер, а затем драйвер перезапишет их. Таким образом, журналы 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.

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