Por que minha consulta do Hibernate está retornando dados obsoletos?
-
16-11-2019 - |
Pergunta
Versão rápida
Basicamente, estou atualizando uma tabela do Hibernate e as consultas subsequentes estão carregando um valor obsoleto.
Versão detalhada
Hibernate (3.3.1.GA) e EhCache (2.4.2).
Persiste Book
objeto com um List<PageContent>
de páginas e estou adicionando uma página no meio deste livro.Estou usando o Databinder/Wicket, embora não ache que isso esteja relacionado.
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
}
Os campos/métodos aplicáveis em Book
são:
@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);
}
O resultado final é que há uma nova página adicionada a uma lista e o banco de dados é atualizado de acordo e eu confirmei isso em meu armazenamento de dados.No entanto, a próxima consulta para uma página, digamos "Página #4", carrega a "antiga" Página #4 em vez da nova Página #4:
criteria.add(Restrictions.eq("book", book));
criteria.add(Restrictions.eq("pageNum", pageNum));
criteria.setCacheable(true);
Portanto, removo a contragosto o cache dos critérios.Ele consulta o armazenamento de dados, mas ainda retorna o valor errado.Porém, em ambos os casos, se eu esperar cerca de 2 minutos, tudo estará funcionando conforme o esperado.Presumo que o cache ainda esteja envolvido.Ambos PageContent
e Book
use esta estratégia de cache:
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
Confesso que sou novo em cache e acabei de configurar esse arquivo pela primeira vez.Aqui está meu 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" />
ATUALIZAR:Removendo o @Cache
anotações em meus objetos de armazenamento de dados eliminam o problema.Claro, eu gostaria de armazenar esses objetos em cache porque a modificação da página é muito menos frequente que o acesso.
Então, pensamentos?Existem vários outros problemas relacionados também, inclusive com a exclusão de páginas.Tudo atualiza o banco de dados conforme esperado, mas o comportamento real é instável.
Desde já, obrigado!
ATUALIZAÇÃO #2:Através da depuração, posso confirmar que o Datastore possui as informações corretas e quando a consulta é executada, ela recorre ao cache de segundo nível - que contém informações sujas.Presumo que não cabe a mim despejar do cache toda vez que os dados mudam?
Solução 2
Eu descobri o problema, mas ele introduz outra coisa.
Basicamente, ao modificar um Book
objeto List<PageContent>
campo, o Hibernate faz três coisas:
- Expira a entrada de cache TimeStamp para ambos
Book
ePageContent
- Faz muitas consultas para redefinir o
pageNum
campo em cadaPageContent
objeto - Remove o
Book
objeto do cache de segundo nível.
Isso garante que as consultas subsequentes procurarão novos objetos, etc.No entanto:
- O Hibernate falha ao remover cada renumerado
PageContent
objeto do cache de segundo nível
Como resultado, qualquer consulta à lista de páginas será executada corretamente, mas recorrerá a valores obsoletos do cache de segundo nível para os dados reais.
Presumo que isso seja porque o Hibernate sente uma pageNum
a mudança não é uma mudança nos dados, mas uma mudança na gestão nos bastidores.No entanto, esses são os dados que eu gostaria de ler e exibir.
A solução é atualizar manualmente cada página após a inserção/exclusão.
Outras dicas
Depois CwmService.get().flushChanges(); // commits the transaction
faça um commit explícito.flush()
apenas libera as alterações no banco de dados, mas não as confirma.não tenho certeza sobre flushChanges()
no entanto.