java - dynamicupdate - Forzar la actualización de la colección JPA entityManager
update jpa entitymanager (5)
Estoy utilizando SEAM con JPA (implementado como un Contexto de persistencia administrado), en mi bean de respaldo, cargué una colección de entidades (ArrayList) en el bean de respaldo.
Si un usuario diferente modifica una de las entidades en una sesión diferente, quiero que estos cambios se propaguen a la colección en mi sesión, tengo un método refreshList()
y he intentado lo siguiente ...
@Override
public List<ItemStatus> refreshList(){
itemList = itemStatusDAO.getCurrentStatus();
}
Con la siguiente consulta
@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();
}
Al volver a ejecutar la consulta, esto solo devuelve los mismos datos que ya tengo (asumo que está usando el caché de primer nivel en lugar de ir a la base de datos)
@Override
public List<ItemStatus> refreshList(){
itemStatusDAO.refresh(itemList)
}
Al llamar a entityManager.refresh()
, esto debería actualizarse de la base de datos; sin embargo, obtengo una javax.ejb.EJBTransactionRolledbackException: Entity not managed
excepción javax.ejb.EJBTransactionRolledbackException: Entity not managed
cuando uso esto, normalmente usaría entityManager.findById(entity.getId)
antes de llamar .refresh () para asegurar que esté conectado a la PC, pero como estoy actualizando una colección de entidades, no puedo hacer eso.
Parece un problema bastante simple, ¡¿no puedo creer que no haya forma de forzar a JPA / hibernar para que eviten el caché y lleguen a la base de datos ?!
ACTUALIZAR EL CASO DE PRUEBA:
Estoy usando dos navegadores diferentes (1 y 2) para cargar la misma página web, hago una modificación en 1 que actualiza un atributo booleano en una de las entidades ItemStatus, la vista se actualiza en 1 para mostrar el atributo actualizado, verifico La base de datos a través de PGAdmin y la fila se ha actualizado. Luego presiono actualizar en el navegador 2 y el atributo no se ha actualizado
Intenté usar el siguiente método para fusionar todas las entidades antes de llamar a .refresh, pero las entidades aún no estaban actualizadas desde la base de datos.
@Override
public void mergeCollectionIntoEntityManager(List<T> entityCollection){
for(T entity: entityCollection){
if(!this.getEntityManager().contains(entity)){
this.getEntityManager().refresh(this.getEntityManager().merge(entity));
}
}
}
Debe referir la entidad que fue devuelta por el método entityManager.merge()
, algo como:
@Override
public void refreshCollection(List<T> entityCollection){
for(T entity: entityCollection){
if(!this.getEntityManager().contains(entity)){
this.getEntityManager().refresh(this.getEntityManager().merge(entity));
}
}
}
De esta manera debería deshacerse de javax.ejb.EJBTransactionRolledbackException: Entity not managed
Excepción de javax.ejb.EJBTransactionRolledbackException: Entity not managed
.
ACTUALIZAR
Tal vez sea más seguro devolver una nueva colección:
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;
}
o puede ser más efectivo si puede acceder a los identificadores de entidades de esta manera:
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;
}
Después de depurarlo correctamente, me encontré con que uno de los desarrolladores de mi equipo lo inyectó en la capa DAO como
@Repository
public class SomeDAOImpl
@PersistenceContext(type = PersistenceContextType.EXTENDED)
private EntityManager entityManager;
Por lo tanto, para consolidarlo, se almacenó en caché y la consulta se utiliza para devolver los mismos datos obsoletos, aunque los datos se hayan modificado en una de las columnas de las tablas involucradas en la consulta SQL nativa.
Estás teniendo dos problemas separados aquí. Tomemos la fácil primero.
javax.ejb.EJBTransactionRolledbackException: entidad no administrada
La List
de objetos devueltos por esa consulta no es en sí misma una Entity
, por lo que no puede .refresh
a .refresh
. De hecho, de eso es de lo que se queja la excepción. Le está pidiendo a EntityManager
que haga algo con un objeto que simplemente no es una Entity
conocida.
Si desea .refresh
un montón de cosas, .refresh
y .refresh
individualmente.
Actualizar la lista de ItemStatus
Estás interactuando con la caché de nivel de Session
de Hibernate de una manera que, según tu pregunta, no esperas. De los documentos de Hibernate :
Para los objetos adjuntos a una sesión particular (es decir, en el ámbito de una sesión) ... la identidad de JVM para la identidad de la base de datos está garantizada por Hibernate.
El impacto de esto en Query.getResultList()
es que no necesariamente recuperas el estado más actualizado de la base de datos.
La Query
que ejecuta realmente está obteniendo una lista de ID de entidad que coinciden con esa consulta. Todos los identificadores que ya están presentes en la caché de la Session
se comparan con entidades conocidas, mientras que los identificadores que no están completados se basan en el estado de la base de datos. Las entidades conocidas anteriormente no se actualizan en absoluto en la base de datos.
Lo que esto significa es que, en el caso de que, entre dos ejecuciones de la Query
desde la misma transacción, algunos datos hayan cambiado en la base de datos para una entidad conocida en particular, la segunda consulta no detectaría ese cambio. Sin embargo, recogería una nueva instancia de ItemStatus
(a menos que estuviera usando un caché de consulta , que supongo que no).
En pocas palabras: con Hibernate, cuando quiera, en una sola transacción, cargar una entidad y luego recoger cambios adicionales a esa entidad desde la base de datos, debe explícitamente .refresh(entity)
.
La forma en que desea lidiar con esto depende un poco de su caso de uso. Dos opciones que se me ocurren al bate:
-
List<ItemStatus>
tener el DAO vinculado a la vida útil de la transacción e inicializar laList<ItemStatus>
. Las llamadas subsiguientes aDAO.refreshList
en laList
y.refresh(status)
. Si también necesita entidades recién agregadas, debe ejecutar laQuery
y también actualizar los objetos conocidos del Estado del elemento. - Iniciar una nueva transacción. Suena como en tu chat con @Perception, aunque eso no es una opción.
Algunas notas adicionales
Hubo discusión sobre el uso de sugerencias de consulta. Aquí es por qué no funcionaron:
org.hibernate.cacheable = false Esto solo sería relevante si estuviera usando un caché de consulta , que solo se recomienda en circunstancias muy particulares. Sin embargo, incluso si lo estuviera usando, no afectaría su situación porque el caché de consulta contiene ID de objetos, no datos.
org.hibernate.cacheMode = REFRESH Esta es una directiva para el caché de segundo nivel de Hibernate. Si la memoria caché de segundo nivel estuviera activada Y Y estuviera emitiendo las dos consultas de diferentes transacciones, entonces habría obtenido datos obsoletos en la segunda consulta y esta directiva habría resuelto el problema. Pero si está en la misma sesión en las dos consultas, el caché de segundo nivel solo entraría en juego para evitar la carga de la base de datos para las entidades que son nuevas en esta Session
.
Intenta marcarlo @Transactional
@Override
@org.jboss.seam.annotations.Transactional
public void refreshList(){
itemList = em.createQuery("...").getResultList();
}
Una de las opciones: omitir la caché JPA para los resultados de una consulta específica:
// force refresh results and not to use cache
query.setHint("javax.persistence.cache.storeMode", "REFRESH");
Se pueden encontrar muchos otros consejos de ajuste y configuración en este sitio http://docs.oracle.com/javaee/6/tutorial/doc/gkjjj.html