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)
Ответы
Ответ 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