it-swarm.it

Inserimento in blocco o aggiornamento con Hibernate?

Ho bisogno di consumare grandi quantità di dati da un file CSV giornaliero. Il CSV contiene circa 120K di record. Si sta rallentando fino a una ricerca per indicizzazione quando si utilizza l'ibernazione. Fondamentalmente, sembra che l'ibernazione stia facendo una SELECT prima di ogni singolo INSERT (o UPDATE) quando si usa saveOrUpdate (); per ogni istanza che viene mantenuta con saveOrUpdate (), viene emesso un SELECT prima dell'INSERTO effettivo o di un UPDATE. Posso capire perché lo sta facendo, ma è terribilmente inefficiente per l'elaborazione di massa e sto cercando alternative

Sono fiducioso che il problema di prestazioni sta nel modo in cui sto usando l'ibernazione per questo, dato che ho un'altra versione che funziona con SQL nativo (che analizza il CSV nello stesso excat) e che circonda letteralmente i cerchi attorno a questa nuova versione)

Quindi, alla domanda vera, esiste un'alternativa in ibernazione alla sintassi mysqls "INSERT ... ON DUPLICATE"?

O, se scelgo di fare SQL nativo per questo, posso fare SQL nativo all'interno di una transazione di ibernazione? Significa, supporterà commit/rollbacks?

19
JustDanyul

Secondo una risposta a una domanda simile , può essere fatto da configurando Hibernate per inserire oggetti usando una stored procedure personalizzata che utilizza la funzione upsert del database. Non è carino, però.

5
Tom Anderson

Ci sono molti possibili colli di bottiglia nelle operazioni di carico. L'approccio migliore dipende in larga misura dall'aspetto dei tuoi dati. Dai un'occhiata alla sezione Hibernate Manual sull'elaborazione batch.

Come minimo, assicurati di utilizzare il seguente modello (copiato dal manuale):

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

for ( int i=0; i<100000; i++ ) {
Customer customer = new Customer(.....);
session.save(customer);
    if ( i % 20 == 0 ) { //20, same as the JDBC batch size
        //flush a batch of inserts and release memory:
        session.flush();
        session.clear();
    }
}

tx.commit();
session.close();

Se stai mappando un file flat su un grafico di un oggetto molto complesso, potresti dover diventare più creativo, ma il principio di base è che devi trovare un equilibrio tra la spinta di blocchi di dati di buona dimensione al database con ogni flush/commit ed evitare esplodendo la dimensione della cache a livello di sessione.

Infine, se non è necessario utilizzare Hibernate per gestire eventuali raccolte o collegamenti in cascata affinché i dati vengano inseriti correttamente, prendere in considerazione l'utilizzo di StatelessSession .

31
jcwayne

Da Elaborazione batch di ibernazione Per l'aggiornamento ho usato quanto segue:

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

ScrollableResults employeeCursor = session.createQuery("FROM EMPLOYEE")
                                   .scroll();
int count = 0;

while ( employeeCursor.next() ) {
   Employee employee = (Employee) employeeCursor.get(0);
   employee.updateEmployee();
   seession.update(employee); 
   if ( ++count % 50 == 0 ) {
      session.flush();
      session.clear();
   }
}
tx.commit();
session.close();

Ma per l'inserimento vorrei andare per jcwayne risposta

3
shareef

Se usi la sequenza o il generatore nativo, Hibernate utilizzerà una selezione per ottenere l'id:

<id name="id" column="ID">
    <generator class="native" />
</id>

Dovresti usare il generatore hilo o seqHiLo:

<id name="id" type="long" column="id">  
    <generator class="seqhilo">
        <param name="sequence">SEQ_NAME</param>
        <param name="max_lo">100</param>
    </generator>
</id>
1
Gabriel

Se si desidera importare solo i dati senza eseguire alcuna elaborazione o trasformazione, uno strumento come PostgreSQL COPY è il modo più veloce per importare i dati.

Tuttavia, se è necessario eseguire la trasformazione, l'aggregazione dei dati, la correlazione/fusione tra i dati esistenti e quelli in entrata, è necessario l'elaborazione batch a livello di applicazione.

In questo caso, come ho spiegato in questo articolo , si desidera flush-clear-commit regolarmente:

int entityCount = 50;
int batchSize = 25;

EntityManager entityManager = entityManagerFactory()
    .createEntityManager();

EntityTransaction entityTransaction = entityManager
    .getTransaction();

try {
    entityTransaction.begin();

    for (int i = 0; i < entityCount; i++) {
        if (i > 0 && i % batchSize == 0) {
            entityTransaction.commit();
            entityTransaction.begin();

            entityManager.clear();
        }

        Post post = new Post(
            String.format("Post %d", i + 1)
        );

        entityManager.persist(post);
    }

    entityTransaction.commit();
} catch (RuntimeException e) {
    if (entityTransaction.isActive()) {
        entityTransaction.rollback();
    }
    throw e;
} finally {
    entityManager.close();
}

Inoltre, assicurati di abilitare il batching JDBC utilizzando le seguenti proprietà di configurazione:

<property
    name="hibernate.jdbc.batch_size"
    value="25"
/>

<property
    name="hibernate.order_inserts"  
    value="true"
/>

<property
    name="hibernate.order_updates"  
    value="true"
/>

Per ulteriori dettagli su queste proprietà di configurazione di Hibernate, consulta questo articolo .

1
Vlad Mihalcea

La selezione "extra" serve a generare l'identificativo univoco per i tuoi dati.

Passa alla generazione di sequenze HiLo e puoi ridurre i roundtrip di sequenza al database per il numero della dimensione di allocazione. Notare che vi sarà una lacuna nelle chiavi primarie a meno che non si regoli il valore della sequenza per il generatore HiLo

0
szucsz