Comment rafraîchir les entités JPA lorsque la base de données backend change de manière asynchrone?

j'ai une base de données PostgreSQL 8.4 avec quelques tables et vues qui sont essentiellement jointures sur certaines des tables. J'ai utilisé NetBeans 7.2 (comme décrit ici ) pour créer des services basés sur le repos dérivés de ces vues et tableaux et déployé ceux-ci sur un serveur Glassfish 3.1.2.2.

il y a un autre processus qui met à jour de façon asynchrone le contenu de certaines tables utilisées pour construire les vues. Je peux interroger directement les vues et les tables et de voir ces des changements ont eu lieu correctement. Toutefois, lorsqu'elles sont extraites des services basés sur le repos, les valeurs ne sont pas les mêmes que celles de la base de données. Je suppose que C'est parce que JPA a mis en cache des copies locales du contenu de la base de données sur le serveur Glassfish et JPA doit rafraîchir les entités associées.

j'ai essayé d'ajouter quelques méthodes à la classe AbstractFacade que NetBeans génère:

public abstract class AbstractFacade<T> {
    private Class<T> entityClass;
    private String entityName;
    private static boolean _refresh = true;

    public static void refresh() { _refresh = true; }

    public AbstractFacade(Class<T> entityClass) {
        this.entityClass = entityClass;
        this.entityName = entityClass.getSimpleName();
    }

    private void doRefresh() {
        if (_refresh) {
            EntityManager em = getEntityManager();
            em.flush();

            for (EntityType<?> entity : em.getMetamodel().getEntities()) {
                if (entity.getName().contains(entityName)) {
                    try {
                        em.refresh(entity);
                        // log success
                    }
                    catch (IllegalArgumentException e) {
                        // log failure ... typically complains entity is not managed
                    }
                }
            }

            _refresh = false;
        }
    }

...

}

j'appelle alors doRefresh() de chacun des find méthodes que NetBeans génère. Ce qui se passe normalement, c'est que le IllegalArgumentsException est lancé en disant quelque chose comme Can not refresh not managed object: EntityTypeImpl@28524907:MyView [ javaType: class org.my.rest.MyView descriptor: RelationalDescriptor(org.my.rest.MyView --> [DatabaseTable(my_view)]), mappings: 12].

donc je suis à la recherche de quelques suggestions sur la façon de rafraîchir correctement les entités associées aux vues afin qu'il soit à jour.

mise à jour: il S'avère que ma compréhension du problème sous-jacent n'était pas correcte. Il est quelque peu lié à une autre question que j'ai posté plus tôt , à savoir que la vue ne comportait pas de champ unique pouvant servir d'identificateur unique. NetBeans m'oblige à sélectionner un champ ID, donc j'ai juste choisi une partie de ce qui devait être un multi-partie de la clé. Ceci a montré le comportement que tous les enregistrements avec un champ ID particulier étaient identiques, même si la base de données avait des enregistrements avec le même champ ID mais le reste était différent. JPA n'est pas allé plus loin que de regarder ce que j'ai dit qu'il était l'identificateur unique et a simplement tiré le premier record qu'il trouve.

j'ai résolu ce problème en ajoutant un champ d'identification unique (je n'ai jamais réussi à faire fonctionner correctement la touche multipart).

30
demandé sur Community 2012-11-07 00:36:52

4 réponses

je recommande d'ajouter une classe @Startup @Singleton qui établit une connexion JDBC à la base de données PostgreSQL et utilise LISTEN et NOTIFY pour gérer l'invalidation de cache.

Update : Voici une autre approche intéressante, en utilisant pgq et un ensemble de travailleurs pour invalidation .

signalisation D'Invalidation

Ajouter un déclencheur sur la table en cours de mise à jour qui envoie un NOTIFY chaque fois qu'une entité est mise à jour. Sur PostgreSQL 9.0 et au-dessus de ce NOTIFY peut contenir une charge utile, habituellement un ID de ligne, donc vous n'avez pas à invalider votre cache entier, juste l'entité qui a changé. Sur les versions plus anciennes où une charge utile n'est pas prise en charge , vous pouvez soit ajouter les entrées invalidées à une table de journalisation horodatée que votre classe helper interroge lorsqu'elle reçoit un NOTIFY , soit juste invalider le ensemble cache.

votre classe helper maintenant LISTEN s sur les événements NOTIFY que le déclencheur envoie. Lorsqu'il reçoit un événement NOTIFY , il peut invalider des entrées de cache individuelles (voir ci-dessous), ou vider le cache entier. Vous pouvez écouter les notifications depuis la base de données avec le support de PgJDBC "listen/notify . Vous aurez besoin de déballer toute connexion pooler géré java.sql.Connection pour obtenir L'implémentation PostgreSQL sous-jacente afin que vous puissiez lancer il à org.postgresql.PGConnection et appeler getNotifications() sur elle.

une alternative à LISTEN et NOTIFY , vous pouvez interroger une table de journal de changement sur une minuterie, et avoir un déclencheur sur la table de problème ajouter les ID de ligne changés et changer les horodateurs à la table de journal de changement. Cette approche sera portable sauf pour le besoin d'un déclencheur différent pour chaque type de DB, mais il est inefficace et moins opportun. Il faudra de fréquents sondages inefficaces, et encore un retard de temps que la listen/notify approche ne fonctionne pas. Dans PostgreSQL vous pouvez utiliser un tableau UNLOGGED pour réduire un peu les coûts de cette approche.

niveaux de Cache

EclipseLink / JPA a quelques niveaux de cache.

le cache du premier niveau est au niveau EntityManager . Si une entité est rattachée à un EntityManager par persist(...) , merge(...) , find(...) , etc, puis le EntityManager est requis de retourner la même instance de cette entité lorsqu'elle est à nouveau consultée au cours de la même session, que votre application y fasse ou non encore référence. Cette instance jointe ne sera pas à jour si le contenu de votre base de données a changé depuis.

le cache de deuxième niveau, qui est facultatif, est au niveau" 1519620920 EntityManagerFactory 1519390920 "et est un cache plus traditionnel. Il n'est pas clair si vous avez le cache de deuxième niveau permettre. Vérifiez vos logs EclipseLink et votre persistence.xml . Vous pouvez accéder au cache du 2e niveau avec EntityManagerFactory.getCache() ; voir Cache .

@thedayofcondor a montré comment vider le cache du 2e niveau avec:

em.getEntityManagerFactory().getCache().evictAll();

mais vous pouvez également expulser des objets individuels avec le evict(java.lang.Class cls, java.lang.Object primaryKey) appel:

em.getEntityManagerFactory().getCache().evict(theClass, thePrimaryKey);

que vous pouvez utiliser à partir de votre @Startup @Singleton NOTIFY écouteur d'invalider seules les entrées qui ont changé.

le cache de premier niveau n'est pas si facile, parce qu'il fait partie de la logique de votre application. Vous voulez en savoir plus sur le fonctionnement des EntityManager , entités attachées et détachées, etc. Une option est d'utiliser toujours des entités détachées pour la table en question, où vous utilisez un nouveau EntityManager chaque fois que vous récupérez l'entité. Cette question:

Invalidating JPA EntityManager session

a une discussion utile sur le traitement de l'invalidation du cache du gestionnaire d'entité. Cependant, il est peu probable qu'une cache EntityManager soit votre problème, car un service Web reposant est généralement implémenté en utilisant de courtes sessions EntityManager . Cela ne sera probablement un problème que si vous utilisez des contextes de persistance prolongée, ou si vous créez et gérez vos propres sessions EntityManager plutôt que d'utiliser "container-managed persistence".

49
répondu Craig Ringer 2017-05-23 10:31:17

vous pouvez désactiver complètement la mise en cache (voir: http://wiki.eclipse.org/EclipseLink/FAQ/How_to_disable_the_shared_cache%3F ), mais être préparé à une perte de performance assez importante.

sinon, vous pouvez exécuter un cache clair par programmation avec

em.getEntityManagerFactory().getCache().evictAll();

vous pouvez le mapper à un servlet de sorte que vous pouvez l'appeler extérieurement - c'est mieux si votre base de données est Modifier extérieurement très rarement et vous voulez juste être sûr JPS va prendre la nouvelle version

7
répondu thedayofcondor 2012-11-06 22:30:24

Juste une pensée, mais comment voulez-vous recevoir votre EntityManager/Session/whatever?

Si vous interroge sur l'entité au cours d'une session, il sera détaché dans le suivant et vous aurez l'intégrer dans le contexte de persistance pour l'obtenir, il parvint.

essayer de travailler avec des entités détachées peut conduire à ces exceptions non-gérées, vous devriez interroger à nouveau l'entité ou vous pourriez l'essayer avec la fusion (ou des méthodes similaires).

0
répondu Volker 2012-11-06 20:57:47

JPA ne fait pas de mise en cache par défaut. Vous devez configurer explicitement. Je crois que c'est l'effet secondaire du style architectural que vous avez choisi: le repos. Je pense que la mise en cache a lieu sur les serveurs web, les serveurs proxy, etc. Je vous suggère de lire ce et de déboguer plus.

-4
répondu Aravind R. Yarram 2012-11-06 20:44:25