JPA-Modifying update query-Refresh persistence context
je travaille avec la botte à ressort 1.3.0.M4 et une base de données MySQL.
j'ai un problème lorsque j'utilise des requêtes de modification, L'EntityManager contient des entités périmées après l'exécution de la requête.
Origine JPA Référentiel:
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();
}
Supposons que nous avons Email [id=1, active= true, expire = 2015/01/01] in DB.
Après l'exécution:
emailRepository.save(email);
emailRepository.deactivateByExpired();
System.out.println(emailRepository.findOne(1L).isActive()); // prints true!! it should print false
Première approche pour résoudre le problème: ajouter clearAutomatically = 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();
}
cette approche élimine le contexte de persistance pour ne pas avoir de valeurs périmées, mais elle supprime tous les changements non-flushés encore en attente dans L'EntityManager. Comme je l'ai utiliser seulement save()
méthodes et non saveAndFlush()
certains changements sont perdus pour d'autres entités : (
deuxième approche pour résoudre le problème: mise en œuvre sur mesure pour référentiel
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;
}
}
Cette approche fonctionne de manière similaire à @Modifying(clearAutomatically = true)
mais il force tout d'abord le EntityManager à vider toutes les modifications de DB avant d'exécuter la mise à jour, puis il efface le contexte de persistance. De cette façon, il n'y aura pas d'entités périmées et tous les changements seront enregistrés dans la base de données.
je voudrais savoir s'il y a une meilleure façon d'exécuter des instructions de mise à jour dans JPA sans avoir la question des entités périmées et sans la commande manuelle flush to DB. Peut-être désactiver le cache du 2e niveau? Comment puis-je le faire dans une botte de printemps?
mise à Jour 2018
données de printemps JPA approuvé mon PR, Il ya un flushAutomatically
option dans @Modifying()
maintenant.
@Modifying(flushAutomatically = true, clearAutomatically = true)
2 réponses
je sais que ce n'est pas une réponse directe à votre question, puisque vous avez déjà construit un correctif et lancé une demande de pull sur Github. Je vous en remercie!
mais j'aimerais vous expliquer la façon dont vous pouvez vous y prendre. Vous souhaitez donc changer toutes les entités qui correspondent à un critère spécifique et mettre à jour une valeur sur chacun. L'approche normale, c'est juste pour charger tous besoin entités:
@Query("SELECT * FROM Email e where e.active = true and e.expire <= NOW()")
List<Email> findExpired();
parcourir et mettre à jour les valeurs:
for (Email email : findExpired()) {
email.setActive(false);
}
maintenant hibernez connaît tous les changements et les écrira dans la base de données si la transaction est faite ou vous appelez EntityManager.flush()
manuellement. Je sais que ça ne marchera pas si vous avez un grand nombre d'entrées de données, puisque vous chargez toutes les entités dans la mémoire. Mais c'est la meilleure façon de synchroniser le cache des entités hibernées, les caches de deuxième niveau et la base de données.
est-ce que cette réponse dit "l'annotation `@Modifying est inutile"? Pas de! Si vous vous assurez que les entités modifiées ne sont pas dans votre cache local, par exemple en écriture seulement l'application, cette approche est juste le chemin à parcourir.
Et juste pour le record: vous n'avez pas besoin @Transactional
sur vos méthodes de dépôt.
Juste pour le record v2:active
colonne paraît qu'il a une dépendance directe expire
. Alors pourquoi ne pas supprimer active
complètement et regarder juste sur expire
dans chaque requête?
comme klaus-groenbaek l'a dit, Vous pouvez injecter EntityManager et utiliser sa méthode de rafraîchissement:
@Inject
EntityManager entityManager;
...
emailRepository.save(email);
emailRepository.deactivateByExpired();
Email email2 = emailRepository.findOne(1L);
entityManager.refresh(email2);
System.out.println(email2.isActive()); // prints false