2017-08-16 61 views
0

亲爱的同学程序员,与执行的EclipseLink本土批量更新

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

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

我正在使用EclipseEE 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个慢速选择(30-40秒)。这些SELECT完成后,EclipseLink执行快速批量更新(3-4秒)。

最后一天,我试图阻止EclipseLink执行这些SELECT并发出更新,但没有运气。从另一个计算器后我发现了一个方法来执行更新,而无需在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。

+0

JPA被迫在每个updateObject调用中逐一执行您的语句,因此无法将它们收集到较大的批次中。您应该考虑更改操作,以便调度程序使用相同的EntityManager实例进行读取和更新,从而避免不必要的选择。否则,如果您必须使用本机SQL进行此操作(并将其与读取分开),那么JPA可能不适合您,您将希望获得连接并直接管理批处理语句执行。 – Chris

+0

感谢您对@Chris的评论。我有一个关于你的评论的问题:如果调度程序使用相同的EntityManager,那么实体将在提交后被分离,我将需要在下一个调度程序调用中重新选择或合并它们。我的意图是只从数据库中读取一次数据,然后每分钟更新这些记录,而不先选择或合并它们。 –

+0

你可以使用一个扩展的EntityManager上下文,并保持EntityManager。这个EntityManager会将所有读入的内容都保存为托管的,这样您就可以将其附加到事务中,以获取对这些托管实体所做的更改。另请注意,EclipseLink使用共享缓存,这可以防止在某些情况下需要读取合并。 – Chris

回答

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或更高版本!