2010-02-17 6 views
1

Je dois supprimer des millions de lignes d'une table dans un minuteur EJB. Le problème est que le temporisateur a un délai d'expiration de transaction de 90 secondes, donc je devrais diviser le travail en morceaux de petite taille. Comme je ne sais pas combien de lignes peuvent être supprimées en 90 secondes, l'algorithme devrait être bouclé et supprimé quelques unes à la fois jusqu'à ce que le temps soit presque écoulé.Supprimer un grand nombre de lignes d'une minuterie EJB

Le problème est: Comment le nombre de lignes à supprimer peut-il être limité élégamment dans JPA? La suppression est effectuée sur toutes les lignes ayant un horodatage antérieur à une certaine date.

Je suppose qu'il est possible de trouver la 1000e ligne la plus ancienne et DELETE WHERE timestamp <= {1000th-oldest-row.timestamp} Ceci, cependant, n'est pas très élégant et je devrais aller à la dernière rangée dans un 1000 pour obtenir l'horodatage. Deuxièmement, le temporisateur devrait se déclencher immédiatement si la table n'est pas propre après les 90 secondes. Cela peut être facilement résolu mais, encore une fois, n'est pas très élégant.

Répondre

2

Vous rencontrerez toujours des problèmes d'expiration de transaction avec la solution dont vous disposez.

L'astuce consiste à exécuter chaque tronçon dans une transaction distincte comme indiqué ci-dessous en code pesudo.

@Entity 

@NamedQueries (value = { 
    @NamedQuery (
     name = pagedDeleteExpiredItems 
     query= DELETE FROM MyTable 
      WHERE (<table key>) IN (
       SELECT <table key> FROM (
       SELECT ROWNUM AS row_num, <table key> FROM MyTable 
       WHERE timestamp <= :currentTime 
       ) 
       WHERE row_num < :pageSize 
      ) 
    ) 
}) 

public class MyEntity { 
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) 
    int doPagedDeleteExpiredItems(Date currentTime, int pageSize) { 
     Query query = em.createNamedQuery("pagedDeleteExpiredItems"); 
     query.setParameter("currentTime", currentTime); 
     query.setParameter("pageSize", pageSize); 
     int deleteCount = query.executeUpdate(); 
     return deleteCount; 
    } 
} 


@EJBTimer 
public class DeleteExpiredItemsTimer { 

    @EJB(beanName = "MyEntity") 
    MyEntity myEntity; 

    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) 
    void handleTimeout(Timer timer) { 
     Date currentTime = getCurrentTime() 
     int pageSize = 100 
     int deleteCount; 
     do { 
      myEntity.doPagedDeleteExpiredItems(currentTime, pageSize); 
     } while(deleteCount>0); 
    } 
} 
+0

Eh bien, les transactions sont résolues pour moi dans ma contrainte. Mais ta solution est belle. – Hugo

+0

Merci Hugo pour votre commentaire. –

1

Nous avions une exigence similaire et voici comment nous l'avons résolu. J'utilisais EJB 3.0.

  1. La minuterie est démarrée lorsque l'application. le serveur démarre (ou le module est déployé) dans un ServletContextListener.
  2. Lorsque la minuterie se déclenche, elle traite jusqu'à 100 lignes en attente. Vous devez ensuite ordonner le résultat de la requête et limiter le nombre de lignes.
  3. S'il y avait 100 lignes, le temporisateur planifie le délai d'attente suivant avec 0ms. Qu'il, la transaction est validée, et le temporisateur se déclenche à nouveau dans une nouvelle transaction.
  4. S'il y avait moins de 100 lignes, le temporisateur programmait le délai d'expiration suivant au 90sec.

S'il y a, disons, 250 lignes, le temporisateur se déclenche trois fois dans une séquence. Il y a seulement un problème mineur s'il y a exactement 100 lignes à traiter, dans ce cas le chronomètre se déclenche deux fois dans une séquence, mais le 2ème tir ne fait en réalité rien. Mais dans l'ensemble, ça fonctionnait bien.

+0

Oui, c'est comme ça que j'aimerais le faire. Mais comment puis-je limiter le nombre de lignes qui sont supprimées avec élégance? – Hugo

+0

Quelque chose comme 'DELETE FROM MyTable ORDER BY horodatage DESC LIMIT 100'. La syntaxe SQL exacte dépend de la base de données que vous utilisez. Avec JDBC, vous pouvez alors obtenir le numéro de la ligne affectée par 'DELETE', et procéder comme je l'ai décrit. – ewernli

1

Un truc que je l'ai utilisé dans SQL est SUPPRIMER TOP 1000 (ou 100 ou 10000, en fonction du nombre moyen de lignes dans une page), comme suit:

DELETE top 1000 WHERE timestamp <= @ExpirationDate 

Appelez ce à plusieurs reprises jusqu'à ce qu'il ne les lignes sont supprimées (vérifiez avec @@ rowcount) ou vous manquez de temps. Cette technique peut-elle être implémentée en JPA?

0

Résolu le problème en obtenant une liste triée de lignes éligibles à nettoyer et en utilisant setFirstResult (int) au même que setMaxResults (int). De cette façon, je reçois la commande d'un article à peu près maxCount étapes de la plus ancienne.

Query expired = dm.createNamedQuery("getExpiredElements"); 
expired.setParameter("currentTime", getCurrentTime()); 
expired.setMaxResults(maxCount); 
expired.setFirstResult(maxCount); 
@SuppressWarnings("unchecked") 
List<Item> expiredChunk = (List<Item>) expired.getResultList(); 
long lastChunkEndTime = expiredChunk.get(0).getEndTime(); 
Query query = em.createNamedQuery("deleteExpiredItems"); 
query.setParameter("currentTime", lastChunkEndTime); 
int result = query.executeUpdate(); 
return result >= maxCount; 

La fonction renvoie true (au moins) si elle doit être réexécutée.

Questions connexes