I am using SEAM with JPA (implemented as a Seam Managed Persistance Context), in my backing bean I load a collection of entities (ArrayList) into the backing bean.
If a different user modifies one of the entities in a different session I want these changes to be propagated to the collection in my session, I have a method refreshList()
and have tried the following...
@Override
public List<ItemStatus> refreshList(){
itemList = itemStatusDAO.getCurrentStatus();
}
With the following query
@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();
}
Re-executing the query, this just returns the same data I already have (I assume it is using the 1st level cache rather than hitting the database)
@Override
public List<ItemStatus> refreshList(){
itemStatusDAO.refresh(itemList)
}
Calling entityManager.refresh()
, this should refresh from the database however I get a javax.ejb.EJBTransactionRolledbackException: Entity not managed
exception when I use this, normally I would use entityManager.findById(entity.getId)
before calling .refresh() to ensure it is attached to the PC but as I am refreshing a collection of entities I cant do that.
It seems like quite a simple problem, I cant believe there is no way to force JPA/hibernate to bypass the cache and hit the database?!
UPDATE TEST CASE:
I am using two different browsers (1 and 2) to load the same web page, I make a modification in 1 which updates a boolean attribute in one of the ItemStatus entities, the view is refreshed for 1 to display the updated attribute, I check the database via PGAdmin and the row has been updated. I then press refresh in Browser 2 and the attribute has not been updated
I tried using the following method to merge all entities before calling .refresh, but the entities were still not updated from the database.
@Override
public void mergeCollectionIntoEntityManager(List<T> entityCollection){
for(T entity: entityCollection){
if(!this.getEntityManager().contains(entity)){
this.getEntityManager().refresh(this.getEntityManager().merge(entity));
}
}
}
You're having two separate problems here. Let's take the easy one first.
javax.ejb.EJBTransactionRolledbackException: Entity not managed
The List
of objects returned by that query is not itself an Entity
, and so you can't .refresh
it. In fact, that's what the exception is complaining about. You're asking the EntityManager
to do something with an object that is simply not a known Entity
.
If you want to .refresh
a bunch of things, iterate through them and .refresh
them individually.
Refreshing the list of ItemStatus
You're interacting with Hibernate's Session
-level cache in a way that, from your question, you don't expect. From the Hibernate docs:
For objects attached to a particular Session (i.e., in the scope of a Session)... JVM identity for database identity is guaranteed by Hibernate.
The impact of this on Query.getResultList()
is that you do not necessarily get back the most up to date state of the database.
The Query
you run is really getting a list of entity IDs that match that query. Any IDs that are already present in the Session
cache are matched up to known entities, while any IDs that are not are populated based on database state. The previously known entities are not refreshed from the database at all.
What this means is that in the case where, between two executions of the Query
from within the same transaction, some data has changed in the database for a particular known entity, the second Query would not pick up that change. It would, however, pick up a brand new ItemStatus
instance (unless you were using a query cache, which I assume you're not).
Long story short: With Hibernate, whenever you want to, within a single transaction, load an entity and then pick up additional changes to that entity from the database, you must explicitly .refresh(entity)
.
How you want to deal with this depends a bit on your use case. Two options I can think of off the bat:
List<ItemStatus>
. Subsequent calls to DAO.refreshList
iterate through the List
and .refresh(status)
. If you also need newly added entities, you should run the Query
and also refresh the known ItemStatus objects.Some additional notes
There was discussion about using query hints. Here's why they didn't work:
org.hibernate.cacheable=false This would only be relevant if you were using a query cache, which is only recommended in very particular circumstances. Even if you were using it though, it wouldn't affect your situation because the query cache contains object IDs, not data.
org.hibernate.cacheMode=REFRESH This is a directive to Hibernate's second-level cache. If the second-level cache were turned on, AND you were issuing the two queries from different transactions, then you would have gotten stale data in the second query, and this directive would have resolved the problem. But if you're in the same Session in the two queries, the second level cache would only come in to play to avoid database loading for entities that are new to this Session
.