Pourquoi ma requête Hibernate renvoie-t-elle des données obsolètes ?
-
16-11-2019 - |
Question
Version rapide
Fondamentalement, je mets à jour une table Hibernate et les requêtes suivantes chargent une valeur obsolète.
Version détaillée
Mise en veille prolongée (3.3.1.GA) et EhCache (2.4.2).
Persisté Book
objet avec un List<PageContent>
de pages et j'ajoute une page au milieu de ce livre.J'utilise Databinder/Wicket, même si je ne pense pas que cela soit lié.
public void createPageContent(Book book, int index) {
Databinder.getHibernateSession().lock(book, LockMode.UPGRADE);
PageContent page = new PageContent(book);
book.addPage(page, index);
CwmService.get().flushChanges(); // commits the transaction
}
Les champs/méthode applicables dans Book
sont:
@OneToMany
@JoinColumn(name="book_id")
@IndexColumn(name="pageNum")
@Cascade({CascadeType.ALL, CascadeType.DELETE_ORPHAN})
private List<PageContent> pages = new ArrayList<PageContent>();
public synchronized void addPage(PageContent page, int index) {
pages.add(index, page);
}
Le résultat final est qu'une nouvelle page est ajoutée à une liste et que la base de données est mise à jour en conséquence et je l'ai confirmé dans ma banque de données.Cependant, la requête suivante pour une page, par exemple « Page n°4 », charge « l’ancienne » page n°4 au lieu de la nouvelle page n°4 :
criteria.add(Restrictions.eq("book", book));
criteria.add(Restrictions.eq("pageNum", pageNum));
criteria.setCacheable(true);
Donc, je supprime à contrecœur la mise en cache des critères.Il interroge la banque de données, mais renvoie toujours la mauvaise valeur.Cependant, dans les deux cas, si j'attends environ 2 minutes, tout fonctionne comme prévu.Je suppose que la mise en cache est toujours impliquée.Les deux PageContent
et Book
utilisez cette stratégie de mise en cache :
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
J'avoue que je suis nouveau dans la mise en cache et que je viens de configurer ce fichier pour la première fois.Voici mon ehcache.xml :
<defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" diskSpoolBufferSizeMB="30" maxElementsOnDisk="10000000" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU" statistics="false"/>
<!-- Hibernate's Cache for keeping 'lastUpdated' data on each table. Should never expire. -->
<cache name="org.hibernate.cache.UpdateTimestampsCache" eternal="true" />
<!-- Hibernate's Query Cache - should probably be limited -->
<cache name="org.hibernate.cache.StandardQueryCache" maxElementsInMemory="1000" />
MISE À JOUR:Suppression du @Cache
les annotations sur mes objets de banque de données suppriment le problème.Bien entendu, j'aimerais mettre en cache ces objets car la modification de page est beaucoup moins fréquente que l'accès.
Alors, des idées ?Il existe également plusieurs autres problèmes liés, notamment à la suppression de pages.Tout met à jour la base de données comme prévu, mais le comportement réel est bancal.
Merci d'avance!
MISE À JOUR #2:Via le débogage, je peux confirmer que la banque de données contient les informations correctes et que lorsque la requête s'exécute, elle se rabat sur le cache de deuxième niveau, qui contient des informations sales.Je suppose que ce n'est pas à moi d'expulser du cache à chaque fois que les données changent ?
La solution 2
J'ai découvert le problème, mais il introduit autre chose.
Fondamentalement, lors de la modification d'un Book
objets List<PageContent>
champ, Hibernate fait trois choses :
- Fait expirer l'entrée du cache TimeStamp pour les deux
Book
etPageContent
- Effectue de nombreuses requêtes pour réinitialiser le
pageNum
champ sur chaquePageContent
objet - Supprime le
Book
objet du cache de deuxième niveau.
Cela garantit que les requêtes ultérieures rechercheront de nouveaux objets, etc.Cependant:
- Hibernate ne parvient pas à supprimer chaque renuméroté
PageContent
objet du cache de deuxième niveau
Par conséquent, toute requête portant sur la liste des pages s'exécutera correctement, mais reviendra ensuite sur les valeurs obsolètes du cache de deuxième niveau pour les données réelles.
Je suppose que c'est parce qu'Hibernate ressent un pageNum
le changement n’est pas un changement dans les données mais un changement dans la gestion en coulisses.Cependant, ce sont les données que j'aimerais lire et afficher.
La solution consiste à actualiser manuellement chaque page après l'insertion/suppression.
Autres conseils
Après CwmService.get().flushChanges(); // commits the transaction
faire un commit explicite.flush()
vide uniquement les modifications apportées à la base de données mais ne les valide pas.je ne suis pas sûr de flushChanges()
cependant.