web-dev-qa-db-fra.com

Forcer l'actualisation de la collection JPA entityManager

J'utilise SEAM avec JPA (implémenté en tant que contexte Seam Managed Persistance), dans mon bean de support, je charge un ensemble d'entités (ArrayList) dans le bean de support.

Si un utilisateur différent modifie l'une des entités dans une session différente. Je souhaite que ces modifications soient propagées à la collection de ma session. J'ai une méthode refreshList() et j'ai essayé la procédure suivante ...

@Override
public List<ItemStatus> refreshList(){
    itemList = itemStatusDAO.getCurrentStatus();
}

Avec la requête suivante

@SuppressWarnings("unchecked")
@Override
public List<ItemStatus> getCurrentStatus(){
    String s = "SELECT DISTINCT iS FROM ItemStatus iS ";
    s+="ORDER BY iS.dateCreated ASC";
    Query q = this.getEntityManager().createQuery(s);
    return q.getResultList();
}

En ré-exécutant la requête, cela retourne simplement les mêmes données que j'ai déjà (je suppose qu'il utilise le cache de premier niveau plutôt que de toucher la base de données)

@Override
public List<ItemStatus> refreshList(){
    itemStatusDAO.refresh(itemList)
}

En appelant entityManager.refresh(), cela devrait être actualisé à partir de la base de données. Cependant, lorsque je l'utilise, une exception javax.ejb.EJBTransactionRolledbackException: Entity not managed apparaît. Normalement, j'utilise entityManager.findById(entity.getId) avant d'appeler .refresh () pour m'assurer qu'il est connecté au PC, mais l'actualisation d'une collection d'entités ne peut pas être remplacée. fais ça.

Cela semble être un problème assez simple, je ne peux pas croire qu’il n’ya aucun moyen de forcer JPA/hibernate à contourner le cache et à accéder à la base de données?!

MISE À JOUR DU CAS DE TEST:

J'utilise deux navigateurs différents (1 et 2) pour charger la même page Web, je fais une modification dans 1 qui met à jour un attribut booléen dans l'une des entités ItemStatus, la vue est actualisée pour 1 afin d'afficher l'attribut mis à jour, je vérifie la base de données via PGAdmin et la ligne a été mise à jour. J'appuie ensuite sur Actualiser dans le navigateur 2 et l'attribut n'a pas été mis à jour

J'ai essayé d'utiliser la méthode suivante pour fusionner toutes les entités avant d'appeler .refresh, mais les entités n'étaient toujours pas mises à jour à partir de la base de données.

@Override
public void mergeCollectionIntoEntityManager(List<T> entityCollection){
    for(T entity: entityCollection){
        if(!this.getEntityManager().contains(entity)){
            this.getEntityManager().refresh(this.getEntityManager().merge(entity));
        }
    }
}
19
DaveB

Vous avez deux problèmes distincts ici. Prenons d'abord le plus facile.


javax.ejb.EJBTransactionRolledbackException: entité non gérée

La List des objets renvoyés par cette requête n'est pas elle-même une Entity, et vous ne pouvez donc pas .refresh. En fait, c'est de cela que l'exception se plaint. Vous demandez à la EntityManager de faire quelque chose avec un objet qui n'est tout simplement pas connu Entity.

Si vous voulez .refresh un tas de choses, parcourez-les et .refresh individuellement.


Actualisation de la liste de ItemStatus

Vous interagissez avec le cache de niveau Session- d'Hibernate d'une manière qui, d'après votre question, ne vous attendez pas. À partir de la documentation Hibernate :

Pour les objets attachés à une session particulière (c'est-à-dire dans le cadre d'une session), l'identité de la JVM pour l'identité de la base de données est garantie par Hibernate.

L'impact de ceci sur Query.getResultList() est que vous ne récupérez pas nécessairement l'état le plus récent de la base de données.

La Query que vous exécutez obtient en réalité une liste d'ID d'entités correspondant à cette requête. Tous les ID déjà présents dans le cache Session sont mis en correspondance avec des entités connues, tandis que tous les ID qui ne le sont pas sont renseignés en fonction de l'état de la base de données. Les entités connues précédemment ne sont pas actualisées à partir de la base de données.

Cela signifie que dans le cas où, entre deux exécutions de la Query au sein d'une même transaction, certaines données ont été modifiées dans la base de données pour une entité connue particulière, la seconde requête pas prendre en compte ce changement. Cependant, cela prendrait une toute nouvelle instance ItemStatus (sauf si vous utilisiez un cache de requêtes , ce qui, je suppose, n'est pas le cas).

Longue histoire: Avec Hibernate, chaque fois que vous souhaitez, au sein d'une transaction unique, charger une entité, puis extraire des modifications supplémentaires de cette entité dans la base de données, vous devez explicitement .refresh(entity).

La façon dont vous voulez gérer cela dépend un peu de votre cas d'utilisation. Deux options auxquelles je peux penser dès le départ:

  1. Vous devez avoir le DAO lié à la durée de vie de la transaction et initialiser paresseux le List<ItemStatus>. Les appels suivants à DAO.refreshList se répètent via List et .refresh(status). Si vous avez également besoin d'entités nouvellement ajoutées, vous devez exécuter la commande Query et également actualiser les objets ItemStatus connus.
  2. Commencer une nouvelle transaction. Cela ressemble à votre conversation avec @Perception, bien que cela ne soit pas une option.

Quelques notes supplémentaires

Il a été question d'utiliser des indicateurs de requête. Voici pourquoi ils ne travaillaient pas:

org.hibernate.cacheable = false Cela ne serait pertinent que si vous utilisiez un cache de requêtes , qui n'est recommandé que dans des circonstances très particulières. Même si vous l'utilisiez, cela n'aurait aucune incidence sur votre situation, car le cache de requêtes contient des ID d'objet et non des données.

org.hibernate.cacheMode = REFRESH Ceci est une directive du cache Hibernate cache de deuxième nivea . Si le cache de second niveau était activé ET que vous émettiez les deux requêtes à partir de transactions différentes, vous auriez obtenu des données périmées dans la seconde requête et cette directive aurait résolu le problème. Mais si vous êtes dans la même session dans les deux requêtes, le cache de second niveau ne sera lu que pour éviter le chargement de la base de données pour les nouvelles entités de cette Session.

26
sharakan

Vous devez faire référence à l'entité qui a été renvoyée par la méthode entityManager.merge(), quelque chose comme:

@Override
public void refreshCollection(List<T> entityCollection){
    for(T entity: entityCollection){
        if(!this.getEntityManager().contains(entity)){
            this.getEntityManager().refresh(this.getEntityManager().merge(entity));
        }
    }
}

De cette façon, vous devriez vous débarrasser de l'exception javax.ejb.EJBTransactionRolledbackException: Entity not managed.

METTRE &AGRAVE; JOUR

Peut-être qu'il est plus sûr de retourner une nouvelle collection:

public List<T> refreshCollection(List<T> entityCollection)
    {
        List<T> result = new ArrayList<T>();
        if (entityCollection != null && !entityCollection.isEmpty()) {
            getEntityManager().getEntityManagerFactory().getCache().evict(entityCollection.get(0).getClass());
            T mergedEntity;
            for (T entity : entityCollection) {
                mergedEntity = entityManager.merge(entity);
                getEntityManager().refresh(mergedEntity);
                result.add(mergedEntity);
            }
        }
        return result;
    }

ou vous pouvez être plus efficace si vous pouvez accéder à des ID d'entité comme ceci:

public List<T> refreshCollection(List<T> entityCollection)
    {
        List<T> result = new ArrayList<T>();
        T mergedEntity;
        for (T entity : entityCollection) {
            getEntityManager().getEntityManagerFactory().getCache().evict(entity.getClass(), entity.getId());
            result.add(getEntityManager().find(entity.getClass(), entity.getId()));
        }
        return result;
    }
1
Ondrej Bozek

Une des options - ignorer le cache JPA pour un résultat de requête spécifique:

    // force refresh results and not to use cache

    query.setHint("javax.persistence.cache.storeMode", "REFRESH");

de nombreux autres conseils de réglage et de configuration sont disponibles sur ce site http://docs.Oracle.com/javaee/6/tutorial/doc/gkjjj.html

1
Zaitsev

Essayez de le marquer @Transactional

@Override
@org.jboss.seam.annotations.Transactional
public void refreshList(){
    itemList = em.createQuery("...").getResultList();
}
1
Tair

Après avoir correctement débogué, je suis tombé sur un des développeurs de mon équipe qui l’injectait dans la couche DAO, comme

@Repository
public class SomeDAOImpl

    @PersistenceContext(type = PersistenceContextType.EXTENDED)
    private EntityManager entityManager;

Ainsi, pour le consolider, il était mis en cache et la requête utilisait pour renvoyer les mêmes données périmées, même si les données avaient été modifiées dans l'une des colonnes des tables impliquées dans la requête SQL native.

0
Shirgill Farhan