2017-08-16 13 views
0

親愛なる仲間のプログラマー、EclipseLinkの

とネイティブのバッチ更新を実行私は約10 000更新するタスクが与えられている - Oracle 11gのデータベース毎分で100 000レコードを。これらのレコードの現在の状態はグローバルArrayListに保持されているので、DBからすべてのレコードをすべて選択する必要はありません。スケジューラは、各分の始めにArrayList内のレコードを更新し、データベース内のレコードの更新を開始します。

私はこの事実を変更することはできません、それは顧客の要件です。 高性能を達成するには、ネイティブのバッチ更新機能を使用して更新を行う必要があります。

EclipseLink 2.6.3(このバージョンはTomEEに含まれています)でTomEE plume 7.0.2アプリケーションサーバーを使用しています。

コード:

@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秒かかります。 私は、ループごとに1つの中で複数の更新を実行する場合、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> 

、私の代わりにネイティブのSQL UPDATEの管理JPAエンティティを使用するようにコードをリファクタリング。ここでの問題は、エンティティごとにem.merge(エンティティ)を実行して、それを再度管理する必要があることです。これは、エンティティがコミット後に管理されなくなるためです(スケジューラで1分ごとに発生しています)。

これにより、10,000回の遅いSELECT(30〜40秒)が発生します。これらのSELECTが完了すると、EclipseLinkは高速バッチ更新(3〜4秒)を実行します。

最後の日、私はEclipseLinkがこれらの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を別のStackOverflowのポストから私はSELECTせずに更新を行うための方法を発見しました。 IsolatedClientSessionはorg.eclipse.persistence.internal.sessions.UnitOfWorkImplにキャストすることができません

私は今オプションがありません。うまくいけばあなたの誰かが私にこの問題を見て解決するヒントを与えることができます。大変感謝しています。

私はむしろMerge時にSELECTを実行しないように、操作しているEclipseLinkよりネイティブのバッチ更新を行いたいと思います。あなたはJPAのネイティブ側に滞在したい場合

+0

JPAは、updateObject呼び出しごとに1つずつステートメントを実行するように強制しているため、より大きなバッチにそれらを収集することはできません。スケジューラーが読み取りと更新に同じEntityManagerインスタンスを使用し、不要な選択を避けるように操作を変更する必要があります。それ以外の場合は、この操作にネイティブSQLを使用する必要があり(読み込みとは別にする必要がある)、JPAが適切でない可能性があり、接続を取得してバッチ・ステートメントの実行を直接管理する必要があります。 – Chris

+0

ご意見ありがとうございます@クリス。あなたのコメントに関する質問があります:スケジューラが同じEntityManagerを使用している場合、エンティティはコミット後にデタッチされ、次のスケジューラコールで再度選択またはマージする必要がありますか?私の意図はdbから一度だけデータを読み込み、最初にそれらを選択またはマージすることなく毎分更新することです。 –

+0

を使用すると、拡張されたEntityManagerコンテキストを使用してEntityManagerを保持することができます。このEntityManagerは、読み込まれたすべてのものを管理しておき、トランザクションにアタッチして、管理対象エンティティの変更を取得することができます。また、EclipseLinkは共有キャッシュを使用するため、マージ時に読み込みが必要になることがあります。 – Chris

答えて

1

は、長い時間を検索し、異なるアプローチ(クリスのおかげで)しようとした後、私は最も簡単な解決策を見つけた:

@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(); 
} 

警告:重要な部分(全角.unwrap)は、EclipseLink固有のもので、JPA 2.1以上が必要です。