【问题标题】:Batch insert causing failure批量插入导致失败
【发布时间】:2017-08-23 23:35:19
【问题描述】:

我有一个任务,我需要使用休眠将大量数据插入数据库。我目前正在测试插入 500,000 个实体,每个实体都有一个关系,因此总共插入 1,000,000 个。 基于此guide,我创建了以下实际有效的代码。所有数据都插入并提交,没有错误。

import javax.annotation.Resource;
import javax.ejb.*;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.*;
import javax.xml.stream.XMLStreamException;
import javax.xml.transform.TransformerException;
import java.io.File;
import java.io.IOException;
import java.text.ParseException;

@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
public class StackoverflowExample {
    @PersistenceContext
    private EntityManager entityManager;

    @Resource
    private SessionContext sessionContext;

    @EJB
    private XmlProcessorFactory xmlProcessorFactory;

    @EJB
    private TaskManagerBean taskManagerBean;

    public void processFile(String[] args, Task task) throws HeuristicRollbackException, RollbackException, HeuristicMixedException, SystemException, IOException, TransformerException, ParseException, XMLStreamException, NotSupportedException {
        UserTransaction tx = null;

        XmlProcessor instance = xmlProcessorFactory.getInstance(new File("data.xml"));

        XmlElement nextElement = instance.getNextElement();
        int i = 0;
        int batchSize = 50;
        entityManager.setProperty("hibernate.jdbc.batch_size", batchSize);
        tx = sessionContext.getUserTransaction();
        tx.begin();
        while (nextElement != null) {
            Entry entry = new Entry(nextElement.getUserReference(), nextElement.getXml());

            entityManager.persist(entry);

            if (i % batchSize == 0) {
                entityManager.flush();
                entityManager.clear();
            }

            nextElement = instance.getNextElement();
            i++;
        }

        task.setStatus(status);
        task.setEndTime(now());
        // This gives the OutOfMemoryError
        entityManager.merge(task);

        tx.commit();
    }
}

这将在我调用 taskManagerBean.update() 的行中失败,并出现以下错误:

2017-03-31 08:49:30,212 ERROR [org.jboss.as.ejb3.invocation] (EJB default - 3) WFLYEJB0034:
EJB Invocation failed on component TaskManagerBean for method public void
TaskManagerBean.update(Task,TaskStatus):
javax.ejb.EJBTransactionRolledbackException: org.hibernate.exception.GenericJDBCException:
could not load an entity: [Task#3]
    at org.jboss.as.ejb3.tx.CMTTxInterceptor.handleInCallerTx(CMTTxInterceptor.java:159)
    at org.jboss.as.ejb3.tx.CMTTxInterceptor.invokeInCallerTx(CMTTxInterceptor.java:256)
    ...
    at TaskManagerBean$$$view18.update(Unknown Source)
    at StoreEntriesBean.processFile(StoreEntriesBean.java:117)
    ...
    at org.jboss.threads.JBossThread.run(JBossThread.java:320)
Caused by: javax.persistence.PersistenceException: org.hibernate.exception.GenericJDBCException: could not load an entity: [Task#3]
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1692)
    ...
    at org.jboss.as.ejb3.tx.CMTTxInterceptor.invokeInCallerTx(CMTTxInterceptor.java:254)
    ... 104 more
Caused by: org.hibernate.exception.GenericJDBCException: could not load an entity: [Task#3]
    at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:47)
    ...
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:1161)
    ... 135 more
Caused by: java.sql.SQLException: Error
    at org.jboss.jca.adapters.jdbc.WrappedConnection.checkException(WrappedConnection.java:1972)
    ...
    at org.hibernate.loader.Loader.loadEntity(Loader.java:2204)
    ... 155 more
Caused by: java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOfRange(Arrays.java:3664)
    ...
    at org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:478)

Runtime.getRuntime().freeMemory() 在错误之前返回 71540896。

我用 -Xmx2048m 启动 Wildfly,快速浏览一下内存使用情况,发现它只使用了不到一半的内存。

我尝试在每插入 1,000 次后提交插入的条目。这会触发TaskManagerBean,并且在几次更新后它会以同样的方式失败。 在完成大批量作业并且要更新拥有实体后,我也有几次看到此错误。

我尝试了一个只有 5,000 个条目的文件,整个过程运行良好。

这是 MySQL 驱动程序中的错误还是我在这里做错了什么?

【问题讨论】:

  • 我不明白的是,如果我插入 50,000 或 500,000 个条目,它同样会失败。它实际上可以插入和提交500,000,但是在仅插入50,000时更新任务时失败。

标签: java mysql hibernate jakarta-ee wildfly-10


【解决方案1】:

EntityManger.merge() 的调用是导致错误的原因。 我不是 100% 熟悉 Hibernate,但显然 merge 在这种情况下会获取从 TaskEntry 的整个关系,在这种情况下是一个包​​含 500,000 个条目的集合 - 即使这个关系是 Lasy 加载的。

我用 Entity.find(Task.class, taskId) 替换了合并,并改为在该实例上设置状态,这解决了我的问题。

同时我已经介绍了Java Batch Processing,我只能推荐它。它避免了自己编写批处理作业。

【讨论】:

    【解决方案2】:

    您是否关闭会话或实体管理器?

    我从未使用过 EntityManager,但是当您使用 Java 处理数据库时,您应该始终在作业(事务)完成时“显式”关闭连接

    【讨论】:

    • 容器在 java-ee 场景中处理这个问题。
    • @homax 来看看这个。似乎对清理有一些分歧。我建议尝试关闭它,看看它是否有任何区别。 stackoverflow.com/questions/220374/…
    • @exiter2000 那里没有太大的分歧。被高度接受的答案是正确的。人们在尝试使用自己的连接管理器时会遇到麻烦 - 永远不要在完整的 Java EE 服务器中这样做。
    【解决方案3】:

    尝试通过 EJB 运行批处理几乎是行不通的,因为内存不足或事务超时。

    这是“批量申请”的原因之一 Java 平台”(又名JSR-352)规范已开发完成。

    WildFly 10 为您提供了一个实现。

    您可以在Batch Applications in Java EE 7 - Undertanding JSR 352 Concepts: TOTD #192了解更多信息

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-19
      • 1970-01-01
      • 1970-01-01
      • 2013-04-30
      • 2013-08-11
      相关资源
      最近更新 更多