使用EclipseLink执行本机批量更新

时间:2017-08-16 09:20:24

标签: jpa eclipselink native

亲爱的程序员,

我被赋予了在每分钟更新Oracle 11g数据库中大约10 000 - 100 000条记录的任务。这些记录的当前状态保存在全局ArrayList中,因此我不需要从DB的每次更新中选择所有记录。调度程序在每分钟开始时更新ArrayList中的记录,然后开始更新数据库中的记录。

我无法改变这一事实,这是客户的要求。 要实现高性能,应使用本机批量更新功能完成这些更新。

我正在使用带有EclipseLink 2.6.3的TomEE plume 7.0.2应用服务器(此版本包含在TomEE中)。

代码:

@PersistenceContext(unitName = "MES_Tables")
private EntityManager em;

...

@Schedule(second="0", minute="*", hour="*", persistent=false)
public void startUpdate(){
    Query q = em.createNativeQuery(
    "UPDATE " + 
        "SCHEMA.PROPERTIES_GRP_CONT " + 
    "SET " + 
        "STRVAL = ? " + //<-- SQL-Param 
    "WHERE " + 
        "STATES_ID = 1 " +
        "AND PROPERTIES_ID = ? " + //<-- SQL-Param
        "AND PROPERTIES_GRP_ID = ?"); //<-- SQL-Param

    for(BatchInfo bi : biList){ 
        int rowsUpdated = q
        .setParameter(1, Long.toString(bi.getLifetime()))
        .setParameter(2, bi.getPropertiesId())
        .setParameter(3, bi.getBatchId())
        .executeUpdate();
    }
}

不幸的是,这些更新是作为单个更新执行的,并且没有发生批处理。因此,10 000次更新大约需要40-50秒。 据我所知,如果在每个循环中为一个单独执行多个更新,EntityManager(em)应该自动创建批量更新。 甚至将SQL UPDATE简化为没有任何参数的语句,以便始终执行相同的更新,并没有改变执行单个更新的事实。

的persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1"
    xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
    <persistence-unit name="MES_Tables" transaction-type="JTA">
        <jta-data-source>MES_Connection</jta-data-source>
        <exclude-unlisted-classes>false</exclude-unlisted-classes>
        <properties>
            <property name="javax.persistence.schema-generation.database.action" value="none" />
            <property name="eclipselink.ddl-generation" value="none" />
            <property name="eclipselink.logging.level" value="WARNING" />
            <property name="eclipselink.logging.level.sql" value="FINE" />
            <property name="eclipselink.logging.parameters" value="true" />

            <property name="javax.persistence.query.timeout" value="1800000" />
            <property name="eclipselink.jdbc.connections.wait-timeout" value="1800000" />
            <property name="eclipselink.jdbc.batch-writing" value="JDBC" />
            <property name="eclipselink.jdbc.batch-writing.size" value="600" />

            <property name="eclipselink.logging.logger" value="mes.core.logging.EclipseLinkLogger"/>
        </properties>
    </persistence-unit>
</persistence>

为了测试批量更新是否正常工作,我重构了代码以使用托管JPA实体而不是本机SQL UPDATE。这里的问题是,我需要在每个实体上执行em.merge(实体),以便再次进行管理。这是因为实体在提交后变得不受管理(在调度程序中每分钟发生一次)。

这导致10000个慢速SELECT(30-40秒)。完成这些SELECT后,EclipseLink会执行快速批量更新(3-4秒)。

最后几天我试图阻止EclipseLink执行那些SELECT并发布更新但没有运气。从另一个stackoverflow帖子我找到了一个没有SELECT的方法来进行更新:

Perform UPDATE without SELECT in eclipselink

    EntityManagerImpl emImpl = ((EntityManagerImpl) em.getDelegate());
    UnitOfWork uow = emImpl.getUnitOfWork();
    AbstractSession as = uow.getParent();

    for(BatchInfo bi : biList)
        as.updateObject(bi);

遗憾的是,由于以下异常,这也不起作用: org.eclipse.persistence.internal.sessions.IsolatedClientSession无法强制转换为org.eclipse.persistence.internal.sessions.UnitOfWorkImpl

我现在没有选择,希望你们中的某个人可以给我一个提示,在哪里查看并解决这个问题。非常感谢。

我宁愿让本机批处理更新比操作EclipseLink更难以在合并时执行任何SELECT。

1 个答案:

答案 0 :(得分:1)

在搜索了很长时间并尝试不同的方法后(感谢Chris),如果你想留在JPA的原生方面,我找到了最简单的解决方案:

@Schedule(second="0", minute="*", hour="*", persistent=false)
public void startUpdate(){
    String updateSql =
    "UPDATE " + 
        "SCHEMA.PROPERTIES_GRP_CONT " + 
    "SET " + 
        "STRVAL = ? " + //<-- SQL-Param 
    "WHERE " + 
        "STATES_ID = 1 " +
        "AND PROPERTIES_ID = ? " + //<-- SQL-Param
        "AND PROPERTIES_GRP_ID = ?"; //<-- SQL-Param

    java.sql.Connection connection = em.unwrap(java.sql.Connection.class);
    PreparedStatement prepStatement = connection.prepareStatement(updateSql);

    for(BatchInfo bi : biList){ 
        prepStatement.setString(1, Long.toString(bi.getLifetime()));
        prepStatement.setLong(2, bi.getPropertiesId());
        prepStatement.setLong(3, bi.getBatchId());

        prepStatement.addBatch();
    }

    prepStatement.executeBatch();
}

警告:重要部分(em.unwrap)可能是EclipseLink特定的,需要JPA 2.1或更高版本!