使用 EclipseLink 执行本机批量更新
Perform native batch UPDATE with EclipseLink
亲爱的程序员们,
我的任务是每分钟在 Oracle 11g 数据库中更新大约 10 000 - 100 000 条记录。这些记录的当前状态保存在全局 ArrayList 中,因此我不需要 SELECT 来自数据库的每次更新的所有记录。调度程序在每分钟开始更新 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(entity) 以便再次对其进行管理。这是因为实体在提交后变得不受管理(这在调度程序中每分钟发生一次)。
这会导致 10 000 慢 SELECT 秒(30-40 秒)。 SELECT 完成后,EclipseLink 执行快速批量更新(3-4 秒)。
最后几天我试图阻止 EclipseLink 执行那些 SELECTs 并且只是发布更新但没有运气。从另一个 Whosebug post 我找到了一种无需 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 在合并时不执行任何 SELECTs。
经过长时间的搜索并尝试了不同的方法(感谢 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 或更高版本!
亲爱的程序员们,
我的任务是每分钟在 Oracle 11g 数据库中更新大约 10 000 - 100 000 条记录。这些记录的当前状态保存在全局 ArrayList 中,因此我不需要 SELECT 来自数据库的每次更新的所有记录。调度程序在每分钟开始更新 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(entity) 以便再次对其进行管理。这是因为实体在提交后变得不受管理(这在调度程序中每分钟发生一次)。
这会导致 10 000 慢 SELECT 秒(30-40 秒)。 SELECT 完成后,EclipseLink 执行快速批量更新(3-4 秒)。
最后几天我试图阻止 EclipseLink 执行那些 SELECTs 并且只是发布更新但没有运气。从另一个 Whosebug post 我找到了一种无需 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 在合并时不执行任何 SELECTs。
经过长时间的搜索并尝试了不同的方法(感谢 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 或更高版本!