В настоящее время я сталкиваюсь с известной и распространенной проблемой вставки в 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&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
операторов
- qaru.site/info/353856/...
- qaru.site/info/353864/...
- https://forum.hibernate.org/viewtopic.php?p=2374413
- qaru.site/info/339801/...
Что мне не хватает?