hibernate 如何正确/高效地管理大型数据集的实体管理器JPA Spring @Transactional?

i2byvkas  于 2023-01-17  发布在  Spring
关注(0)|答案(1)|浏览(147)

我尝试在数据库中插入大约57,000个实体,但是随着循环的进行,insert方法花费的时间越来越长。我已经实现了25个实体的批处理-每次刷新、清除以及关闭事务(我很确定)没有成功。我需要在下面的代码中做些什么来保持插入率吗?我觉得不应该超过4小时插入57K条记录。
[www.example.com]Migrate.java]
这是循环通过"Xaction"实体并基于每个Xaction添加"XactionParticipant"记录的主类。

// Use hibernate cursor to efficiently loop through all xaction entities
String hql = "select xaction from Xaction xaction";
Query<Xaction> query = session.createQuery(hql, Xaction.class);
query.setFetchSize(100);
query.setReadOnly(true);
query.setLockMode("xaction", LockMode.NONE);
ScrollableResults results = query.scroll(ScrollMode.FORWARD_ONLY);

int count = 0;
Instant lap = Instant.now();
List<Xaction> xactionsBatch = new ArrayList<>();
while (results.next()) {
    count++;

    Xaction xaction = (Xaction) results.get(0);
    xactionsBatch.add(xaction);

    // save new XactionParticipants in batches of 25
    if (count % 25 == 0) {
        xactionParticipantService.commitBatch(xactionsBatch);
        float rate = ChronoUnit.MILLIS.between(lap, Instant.now()) / 25f / 1000;
        System.out.printf("Batch rate: %.4fs per xaction\n", rate);
        xactionsBatch = new ArrayList<>();
        lap = Instant.now();
    }
}
xactionParticipantService.commitBatch(xactionsBatch);
results.close();

[www.example.com]XactionParticipantService.java]
此服务提供了一个带有"REQUIRES_NEW"的方法,用于尝试关闭每个批次的事务

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void commitBatch(List<Xaction> xactionBatch) {
    for (Xaction xaction : xactionBatch) {
        try {
            XactionParticipant xp = new XactionParticipant();
            // ... create xp based off Xaction info ...

            // Use native query for efficiency
            String nativeQueryStr = "INSERT INTO XactionParticipant .... xp info/data";
            Query q = em.createNativeQuery(nativeQueryStr);
            q.executeUpdate();
        } catch (Exception e) {
            log.error("Unable to update", e);
        }
    }
    // Clear just in case??
    em.flush();
    em.clear();
}
3ks5zfa0

3ks5zfa01#

那就是不清楚你的表现问题的根本原因是什么:java内存消耗或数据库性能,请检查以下一些想法:
1.下面的代码实际上并不优化内存消耗:

String hql = "select xaction from Xaction xaction";
Query<Xaction> query = session.createQuery(hql, Xaction.class);
query.setFetchSize(100);
query.setReadOnly(true);
query.setLockMode("xaction", LockMode.NONE);
ScrollableResults results = query.scroll(ScrollMode.FORWARD_ONLY);

由于您检索的是全功能实体,因此这些实体存储在持久性上下文(会话级缓存)中,为了释放内存,您需要在实体处理后(即在xactionsBatch.add(xaction)// ... create xp based off Xaction info ...之后)分离实体,否则在处理结束时,您将消耗与List<> results = query.getResultList();相同的内存量,这里我不确定哪个更好:在事务开始时消耗所需的所有内存并释放所有其它资源或保持游标和JDBC连接打开4小时。
1.以下代码实际上并不优化JDBC交互:

for (Xaction xaction : xactionBatch) {
        try {
            XactionParticipant xp = new XactionParticipant();
            // ... create xp based off Xaction info ...

            // Use native query for efficiency
            String nativeQueryStr = "INSERT INTO XactionParticipant .... xp info/data";
            Query q = em.createNativeQuery(nativeQueryStr);
            q.executeUpdate();
        } catch (Exception e) {
            log.error("Unable to update", e);
        }
    }

是的,一般来说,JDBC应该比JPA API快,但是这不是你的情况--你是一个接一个地插入记录,而不是使用批处理插入。为了利用批处理,你的代码应该看起来像这样:

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void commitBatch(List<Xaction> xactionBatch) {
    session.doWork(connection -> {
        String insert = "INSERT INTO XactionParticipant VALUES (?, ?, ...)";
        try (PreparedStatement ps = connection.prepareStatement(insert)) {
            for (Xaction xaction : xactionBatch) {
                ps.setString(1, "val1");
                ps.setString(2, "val2");
                ps.addBatch();
                ps.clearParameters();
            }
            ps.executeBatch();
        }
    });
}

顺便说一句,如果hibernate.jdbc.batch_size被设置为足够大的正整数,并且实体被正确设计(ID生成由DB序列备份,并且allocationSize足够大),Hibernate也可以做同样的事情。

相关问题