Question

Je dois souvent implémenter des DAO pour des données de référence qui ne changent pas très souvent. Je mets parfois en cache ce champ de collection sur le DAO - de sorte qu'il ne soit chargé qu'une seule fois et mis à jour explicitement si nécessaire.

Cependant, cela soulève de nombreux problèmes de simultanéité - que se passe-t-il si un autre thread tente d'accéder aux données pendant le chargement ou la mise à jour.

Évidemment, cela peut être géré en synchronisant les accesseurs et les installateurs de données - mais pour une application Web volumineuse, cela représente un surcoût.

J'ai inclus un exemple trivial et imparfait de ce dont j'ai besoin en tant qu'homme de paille. S'il vous plaît suggérer d'autres moyens pour mettre en œuvre cela.

public class LocationDAOImpl implements LocationDAO {

private List<Location> locations = null;

public List<Location> getAllLocations() {
    if(locations == null) {
        loadAllLocations();
    }
    return locations;
}

Pour plus d'informations, j'utilise Hibernate et Spring, mais cette exigence s'appliquerait à de nombreuses technologies.

Quelques réflexions supplémentaires:

Est-ce que cela ne devrait pas du tout être traité dans le code - à la place, laissez ehcache ou similaire le gérer? Y at-il un modèle commun pour cela qui me manque? Cela peut évidemment être réalisé de nombreuses façons, mais je n’ai jamais trouvé de modèle simple et facile à maintenir.

Merci d'avance!

Était-ce utile?

La solution

Si vous souhaitez simplement une solution de mise en cache rapide, consultez this article sur JavaSpecialist, qui est une critique du livre La concurrence Java en pratique par Brian Goetz .

Il explique comment implémenter un cache sécurisé de thread de base à l'aide d'un FutureTask et un ConcurrentHashMap .

La façon dont cela est fait garantit qu'un seul thread simultané déclenche le calcul long (dans votre cas, votre base de données appelle dans votre DAO).

Vous devrez modifier cette solution pour ajouter l'expiration du cache si vous en avez besoin.

L’autre idée à propos de la mise en cache vous-même est la collecte des ordures ménagères. Sans utiliser un WeakHashMap pour votre cache, le GC ne pourrait pas libérer la mémoire utilisée par le cache si nécessaire. Si vous mettez en cache des données rarement consultées (mais des données qui valaient encore la peine d’être cachées car il est difficile à calculer), vous voudrez peut-être aider le collecteur de mémoire en cas de manque de mémoire en utilisant un WeakHashMap.

Autres conseils

Le moyen le plus simple et le plus sûr consiste à inclure la bibliothèque ehcache dans votre projet et à l'utiliser pour la configuration. une cache. Ces personnes ont résolu tous les problèmes que vous pouvez rencontrer et ont rendu la bibliothèque aussi rapide que possible.

Dans les cas où j'ai déployé mon propre cache de données de référence, j'ai généralement utilisé un ReadWriteLock pour réduire les conflits de threads. Chacun de mes accesseurs prend alors la forme:

public PersistedUser getUser(String userName) throws MissingReferenceDataException {
    PersistedUser ret;

    rwLock.readLock().lock();
    try {
        ret = usersByName.get(userName);

        if (ret == null) {
            throw new MissingReferenceDataException(String.format("Invalid user name: %s.", userName));
        }
    } finally {
        rwLock.readLock().unlock();
    }

    return ret;
}

La seule méthode permettant de supprimer le verrou en écriture est refresh(), ce que j'expose généralement via un MBean:

public void refresh() {
    logger.info("Refreshing reference data.");
    rwLock.writeLock().lock();
    try {
        usersById.clear();
        usersByName.clear();

        // Refresh data from underlying data source.

    } finally {
        rwLock.writeLock().unlock();
    }
}

J'ai d'ailleurs opté pour l'implémentation de mon propre cache car:

  • Mes collections de données de référence sont petites, je peux donc toujours les stocker toutes en mémoire.
  • Mon application doit être simple / rapide. Je veux le moins possible de dépendances sur des bibliothèques externes.
  • Les données sont rarement mises à jour et quand c'est l'appel à refresh (), c'est assez rapide. J'initialise donc mes caches avec impatience (contrairement à votre exemple, l'homme de paille), ce qui signifie que les accesseurs n'ont jamais besoin de retirer le verrou en écriture.

Si vos données de référence sont immuables, le cache de second niveau de hibernate pourrait constituer une solution raisonnable.

  

Évidemment, cela peut être géré en synchronisant à la fois les accesseurs et les installateurs de données - mais pour une application Web volumineuse, cela représente un surcoût.

     

J'ai inclus un exemple trivial et imparfait de ce dont j'ai besoin en tant qu'homme de paille. Veuillez suggérer d'autres moyens de mettre en œuvre cela.

Bien que cela puisse être un peu vrai, vous devriez noter que l'exemple de code que vous avez fourni doit certainement être synchronisé pour éviter tout problème de simultanéité lors du chargement paresseux du locations. Si cet accesseur n'est pas synchronisé, vous aurez alors:

  • Plusieurs threads accèdent à la méthode loadAllLocations() en même temps
  • Certains threads peuvent entrer <=> même après qu'un autre thread ait terminé la méthode et affecté le résultat à <=> - dans le modèle de mémoire Java, rien ne garantit que d'autres threads verront la modification de la variable sans synchronisation.

Faites attention lorsque vous utilisez le chargement / initialisation différé, cela semble être un simple gain de performances, mais cela peut entraîner de nombreux problèmes de threads.

Je pense qu'il est préférable de ne pas le faire vous-même, car il est très difficile de bien faire les choses. Utiliser EhCache ou OSCache avec Hibernate et Spring est une bien meilleure idée.

En outre, vos DAO sont mis en état, ce qui peut poser problème. Vous ne devriez avoir aucun état à part les objets connexion, fabrique ou modèle que Spring gère pour vous.

UPDATE: Si vos données de référence ne sont pas trop volumineuses et ne changent jamais, une autre solution consisterait peut-être à créer des énumérations et à se passer complètement de la base de données. Pas de cache, pas d'Hibernate, pas de soucis. Peut-être le point de vue d'oxbow_lakes mérite-il d'être pris en compte: il pourrait s'agir d'un système très simple.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top