Подтвердить что ты не робот

Spring Загрузочные данные JPA - Изменение запроса обновления - Обновить контекст сохранения

Я работаю с Spring Boot 1.3.0.M4 и базой данных MySQL.

У меня проблема при использовании модифицирующих запросов, EntityManager содержит устаревшие объекты после выполнения запроса.

Исходный репозиторий JPA:

public interface EmailRepository extends JpaRepository<Email, Long> {

    @Transactional
    @Modifying
    @Query("update Email e set e.active = false where e.active = true and e.expire <= NOW()")
    Integer deactivateByExpired();

}

Предположим, у нас есть электронная почта [id = 1, active = true, expire = 2015/01/01] в БД.

После выполнения:

emailRepository.save(email);
emailRepository.deactivateByExpired();
System.out.println(emailRepository.findOne(1L).isActive()); // prints true!! it should print false

Первый подход к решению проблемы: добавьте clearAutomatics = true

public interface EmailRepository extends JpaRepository<Email, Long> {

    @Transactional
    @Modifying(clearAutomatically = true)
    @Query("update Email e set e.active = false where e.active = true and e.expire <= NOW()")
    Integer deactivateByExpired();

}

Этот подход очищает контекст постоянства, чтобы не иметь устаревших значений, но он отбрасывает все не очищенные изменения, все еще ожидающие в EntityManager. Поскольку я использую только методы save(), а не saveAndFlush(), некоторые изменения теряются для других объектов :(


Второй подход к решению проблемы: пользовательская реализация для хранилища

public interface EmailRepository extends JpaRepository<Email, Long>, EmailRepositoryCustom {

}

public interface EmailRepositoryCustom {

    Integer deactivateByExpired();

}

public class EmailRepositoryImpl implements EmailRepositoryCustom {

    @PersistenceContext
    private EntityManager entityManager;

    @Transactional
    @Override
    public Integer deactivateByExpired() {
        String hsql = "update Email e set e.active = false where e.active = true and e.expire <= NOW()";
        Query query = entityManager.createQuery(hsql);
        entityManager.flush();
        Integer result = query.executeUpdate();
        entityManager.clear();
        return result;
    }

}

Этот подход работает аналогично @Modifying(clearAutomatically = true), но сначала он заставляет EntityManager сбросить все изменения в БД перед выполнением обновления, а затем очищает контекст постоянства. Таким образом, не будет устаревших объектов, и все изменения будут сохранены в БД.


Я хотел бы знать, есть ли лучший способ выполнить операторы обновления в JPA без проблем с устаревшими объектами и без ручного сброса в БД. Возможно, отключение кэша 2-го уровня? Как я могу сделать это в Spring Boot?


Обновление 2018

Spring Data JPA одобрил мой PR, теперь в @Modifying() есть опция @Modifying().

@Modifying(flushAutomatically = true, clearAutomatically = true)
4b9b3361

Ответ 1

Я знаю, что это не прямой ответ на ваш вопрос, так как вы уже создали исправление и начали пул-запрос на Github. Спасибо вам за это!

Но я хотел бы объяснить, как JPA вы можете пойти. Таким образом, вы хотели бы изменить все объекты, которые соответствуют определенным критериям, и обновить значение каждого из них. Обычный подход - просто загрузить все необходимые объекты:

@Query("SELECT * FROM Email e where e.active = true and e.expire <= NOW()")
List<Email> findExpired();

Затем выполните итерации по ним и обновите значения:

for (Email email : findExpired()) {
  email.setActive(false);
}

Теперь hibernate знает все изменения и запишет их в базу данных, если транзакция выполнена или вы вызываете EntityManager.flush() вручную. Я знаю, что это не будет работать хорошо, если у вас есть большое количество записей данных, так как вы загружаете все объекты в память. Но это наилучший способ синхронизации кеша спящего объекта, кеша 2-го уровня и базы данных.

Говорит ли этот ответ "аннотация @Modifying´ бесполезна"? Нет! Если вы убедитесь, что измененные объекты не находятся в вашем локальном кэше, например приложение только для записи, этот подход - только путь.

И просто для справки: вам не нужен @Transactional в ваших методах репозитория.

Просто для записи v2: столбец active выглядит так, как будто имеет прямую зависимость от expire. Так почему бы не полностью удалить active и посмотреть только на expire в каждом запросе?

Ответ 2

Как сказал klaus-groenbaek, вы можете ввести EntityManager и использовать его метод обновления:

@Inject
EntityManager entityManager;

...

emailRepository.save(email);
emailRepository.deactivateByExpired();
Email email2 = emailRepository.findOne(1L);
entityManager.refresh(email2);
System.out.println(email2.isActive()); // prints false