Принудительное обновление коллекции JPA entityManager
Я использую SEAM с JPA (реализованный как Контекст управляемого столкновений), в моем бэкэн-бэк-блоке я загружаю коллекцию сущностей (ArrayList) в бэк файл.
Если другой пользователь изменяет одно из объектов в другом сеансе, я хочу, чтобы эти изменения были распространены в коллекции в моем сеансе, у меня есть метод refreshList()
и пробовал следующее...
@Override
public List<ItemStatus> refreshList(){
itemList = itemStatusDAO.getCurrentStatus();
}
Со следующим запросом
@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();
}
Повторное выполнение запроса, это просто возвращает те же самые данные, которые у меня уже есть (я предполагаю, что он использует кеш 1-го уровня, а не попадает в базу данных)
@Override
public List<ItemStatus> refreshList(){
itemStatusDAO.refresh(itemList)
}
Вызов entityManager.refresh()
, это должно обновляться из базы данных, однако я получаю javax.ejb.EJBTransactionRolledbackException: Entity not managed
исключение, когда я его использую, обычно я бы использовал entityManager.findById(entity.getId)
перед вызовом.refresh() чтобы он был прикреплен к ПК, но поскольку я обновляю набор объектов, которые я не могу сделать.
Кажется, это довольно простая проблема, я не могу полагать, что нет возможности заставить JPA/hibernate обходить кеш и ударить по базе данных?!
ОБНОВЛЕНИЕ ИСПЫТАНИЯ:
Я использую два разных браузера (1 и 2) для загрузки одной и той же веб-страницы, я делаю модификацию в 1, которая обновляет логический атрибут в одном из объектов ItemStatus, представление обновляется на 1 для отображения обновленного атрибута, я проверяю база данных через PGAdmin и строка обновлена. Затем я нажимаю обновление в браузере 2, и атрибут не обновлен
Я попытался использовать следующий метод для объединения всех объектов перед вызовом.refresh, но сущности все еще не обновлялись из базы данных.
@Override
public void mergeCollectionIntoEntityManager(List<T> entityCollection){
for(T entity: entityCollection){
if(!this.getEntityManager().contains(entity)){
this.getEntityManager().refresh(this.getEntityManager().merge(entity));
}
}
}
Ответы
Ответ 1
Здесь у вас две отдельные проблемы. Пусть сначала возьмите легкое.
javax.ejb.EJBTransactionRolledbackException: объект не управляется
List
объектов, возвращаемых этим запросом, сам по себе не является Entity
, поэтому вы не .refresh
его .refresh
. Фактически, это то, о чем жалуется исключение. Вы просите EntityManager
что-то сделать с объектом, который просто не является известным Entity
.
Если вы хотите .refresh
кучу вещей, итерации через них и .refresh
их индивидуально.
Обновление списка ItemStatus
Вы взаимодействуете с кэшем Hibernate Session
-level таким образом, что, с вашего вопроса, вы не ожидаете. Из документов Hibernate:
Для объектов, прикрепленных к определенному сеансу (т.е. В рамках сеанса)... Идентификация JVM для идентификации базы данных гарантируется Hibernate.
Воздействие этого на Query.getResultList()
заключается в том, что вы не обязательно возвращаете самое современное состояние базы данных.
Query
действительно получает список идентификаторов сущностей, соответствующих этому запросу. Любые идентификаторы, которые уже присутствуют в кеше Session
, сопоставляются с известными объектами, тогда как любые идентификаторы, которые не заполняются, основаны на состоянии базы данных. Ранее известные объекты не обновляются из базы данных.
Это означает, что в случае, когда между двумя исполнениями Query
из одной транзакции некоторые данные были изменены в базе данных для определенного известного объекта, второй запрос не подберет это изменение. Тем не менее, он заберет новый экземпляр ItemStatus
(если вы не используете кеш запросов, который, как я полагаю, вы нет).
Короче говоря: с помощью Hibernate, когда бы вы ни захотели, в рамках одной транзакции загрузите объект и затем получите дополнительные изменения к этому объекту из базы данных, вы должны явно .refresh(entity)
.
Как вы хотите справиться с этим, это немного зависит от вашего прецедента. Два варианта, о которых я могу подумать:
- Должны ли DAO привязываться к сроку действия транзакции и лениво инициализировать
List<ItemStatus>
. Последующие вызовы DAO.refreshList
перебирают через List
и .refresh(status)
. Если вам также нужны новые добавленные объекты, вы должны запустить Query
а также обновить известные объекты ItemStatus. - Запустите новую транзакцию. Звучит как из вашего чата с @Perception, хотя это не вариант.
Некоторые дополнительные примечания
Обсуждались использование подсказок. Вот почему они не работали:
org.hibernate.cacheable = false Это было бы актуально только в том случае, если вы использовали кеш запросов, который рекомендуется только в особых обстоятельствах. Даже если вы использовали его, это не повлияет на вашу ситуацию, потому что кеш запросов содержит идентификаторы объектов, а не данные.
org.hibernate.cacheMode = REFRESH Это директива для второго кэша Hibernate -level. Если второй тайник -level был включен, И вы отправляете два запроса из разных транзакций, то во втором запросе вы должны были получить устаревшие данные, и эта директива разрешила бы проблему. Но если вы находитесь в том же сеансе в двух запросах, кэш второго уровня должен появиться, чтобы избежать загрузки базы данных для объектов, которые являются новыми для этого Session
.
Ответ 2
Вы должны ссылаться на объект, который был возвращен методом entityManager.merge()
, например:
@Override
public void refreshCollection(List<T> entityCollection){
for(T entity: entityCollection){
if(!this.getEntityManager().contains(entity)){
this.getEntityManager().refresh(this.getEntityManager().merge(entity));
}
}
}
Таким образом, вы должны избавиться от javax.ejb.EJBTransactionRolledbackException: Entity not managed
исключение.
ОБНОВИТЬ
Может быть, безопаснее вернуть новую коллекцию:
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;
}
или вы можете быть более эффективными, если вы можете получить доступ к идентификаторам объектов, например:
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;
}
Ответ 3
Попробуйте @Transactional
его @Transactional
@Override
@org.jboss.seam.annotations.Transactional
public void refreshList(){
itemList = em.createQuery("...").getResultList();
}
Ответ 4
Один из вариантов - обход кэша JPA для конкретных результатов запроса:
// force refresh results and not to use cache
query.setHint("javax.persistence.cache.storeMode", "REFRESH");
многие другие советы по настройке и настройке можно найти на этом сайте http://docs.oracle.com/javaee/6/tutorial/doc/gkjjj.html
Ответ 5
После правильной отладки я столкнулся с тем, что один из разработчиков в моей команде ввел его в слой DAO as-
@Repository
public class SomeDAOImpl
@PersistenceContext(type = PersistenceContextType.EXTENDED)
private EntityManager entityManager;
Таким образом, для консолидации он получает кеширование, а запрос используется для возврата тех же устаревших данных, даже если данные были изменены в одном из столбцов таблиц, участвующих в собственном запросе sql.