Принудительное обновление коллекции 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).

Как вы хотите справиться с этим, это немного зависит от вашего прецедента. Два варианта, о которых я могу подумать:

  1. Должны ли DAO привязываться к сроку действия транзакции и лениво инициализировать List<ItemStatus>. Последующие вызовы DAO.refreshList перебирают через List и .refresh(status). Если вам также нужны новые добавленные объекты, вы должны запустить Query а также обновить известные объекты ItemStatus.
  2. Запустите новую транзакцию. Звучит как из вашего чата с @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.