Domanda

Sto usando Hibernate per aggiornare 20K prodotti nel mio database.

A partire da ora sto tirando in 20K prodotti, loop attraverso di loro e modificando le proprietà e quindi l'aggiornamento del database.

così:

load products

foreach products
   session begintransaction
   productDao.MakePersistant(p);
   session commit();

A partire da ora le cose sono abbastanza lento rispetto al tuo standard JDBC, cosa posso fare per accelerare le cose?

Sono sicuro che sto facendo qualcosa di sbagliato qui.

È stato utile?

Soluzione

Il posto giusto per guardare nella documentazione per questo tipo di trattamento è l'intero Capitolo 13. L'elaborazione in batch .

Qui, ci sono diversi errori evidenti nel vostro approccio attuale:

  • non si dovrebbe avviare / commit della transazione per ogni aggiornamento.
  • è consigliabile attivare JDBC dosaggio e impostarlo su un numero ragionevole (10-50):

    hibernate.jdbc.batch_size 20
    
  • si dovrebbe flush() e poi clear() la sessione a intervalli regolari (ogni n record dove n è uguale al parametro hibernate.jdbc.batch_size) o sarà continuare a crescere e potrebbe esplodere (con una OutOfMemoryException) a un certo punto.

Di seguito, l'esempio dato nella sezione 13.2. aggiornamenti batch che illustra questo:

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

ScrollableResults customers = session.getNamedQuery("GetCustomers")
    .setCacheMode(CacheMode.IGNORE)
    .scroll(ScrollMode.FORWARD_ONLY);
int count=0;
while ( customers.next() ) {
    Customer customer = (Customer) customers.get(0);
    customer.updateStuff(...);
    if ( ++count % 20 == 0 ) {
        //flush a batch of updates and release memory:
        session.flush();
        session.clear();
    }
}

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

Si può anche considerare l'utilizzo del StatelessSession .

Un'altra opzione sarebbe quella di utilizzare operazioni in stile DML (in HQL!): UPDATE FROM? EntityName (WHERE where_conditions)?. Questo l'esempio HQL UPDATE:

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

String hqlUpdate = "update Customer c set c.name = :newName where c.name = :oldName";
// or String hqlUpdate = "update Customer set name = :newName where name = :oldName";
int updatedEntities = s.createQuery( hqlUpdate )
        .setString( "newName", newName )
        .setString( "oldName", oldName )
        .executeUpdate();
tx.commit();
session.close();

Anche in questo caso, fare riferimento alla documentazione per i dettagli (in particolare come affrontare i valori delle proprietà version o timestamp utilizzando la parola chiave VERSIONED).

Altri suggerimenti

Se questa è la pseudo-codice, io consiglierei di spostare la transazione al di fuori del ciclo, o almeno avere un doppio loop se avere tutti i 20K i prodotti in una singola transazione è troppo:

load products
foreach (batch)
{
   try
   {
      session beginTransaction()
      foreach (product in batch)
      {
          product.saveOrUpdate()
      }
      session commit()
   }
   catch (Exception e)
   {
       e.printStackTrace()
       session.rollback()
   }
}

Inoltre, vi consiglio che si lotti gli aggiornamenti invece di inviare ciascuno individualmente al database. C'è troppo traffico di rete in quel modo. Bundle ogni blocco in un unico lotto e mandarli tutti in una volta.

Sono d'accordo con la risposta di cui sopra di guardare al capitolo sulla elaborazione in batch.

Volevo anche aggiungere che è necessario assicurarsi che si carica solo ciò che è neccessary per i cambiamenti che è necessario fare per il prodotto.

Quello che voglio dire è che, se il prodotto viene caricato con entusiasmo un gran numero di altri oggetti che non sono importanti per questa transazione, si dovrebbe considerare non caricando gli oggetti uniti - sarà accelerare il caricamento dei prodotti e in base alla loro strategia di persistenza , può anche risparmiare tempo quando si effettua nuovamente il prodotto persistente.

Il modo più veloce possibile fare un aggiornamento batch sarebbe quello di convertirlo in una singola istruzione SQL ed eseguirlo come SQL prime sulla sessione. Qualcosa di simile

update TABLE set (x=y) where w=z;

In mancanza di questo si può provare a fare al netto dei rapporti e fare gli aggiornamenti in lotti:

start session
start transaction

products = session.getNamedQuery("GetProducs")
    .setCacheMode(CacheMode.IGNORE)
    .scroll(ScrollMode.FORWARD_ONLY);
count=0;
foreach product
    update product
    if ( ++count % 20 == 0 ) {
        session.flush();
        session.clear();
    }
}

commit transaction
close session

Per ulteriori informazioni consultare il Hibernate Community Docs

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top