wake-up-neo.com

Spring Boot Data JPA - Aktualisierungsabfrage ändern - Persistenzkontext aktualisieren

Ich arbeite mit Spring Boot 1.3.0.M4 und einer MySQL-Datenbank.

Ich habe ein Problem beim Ändern von Abfragen. Der EntityManager enthält veraltete Entitäten, nachdem die Abfrage ausgeführt wurde.

Ursprüngliches JPA-Repository:

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();

}

Angenommen, wir haben E-Mail [id = 1, active = true, expire = 2015/01/01] in der Datenbank.

Nach der Ausführung:

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

Erster Lösungsansatz: add 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();

}

Dieser Ansatz löscht den Persistenzkontext, um keine veralteten Werte zu haben, löscht jedoch alle nicht gelöschten Änderungen, die im EntityManager noch ausstehen. Da ich nur save() Methoden verwende und nicht saveAndFlush() einige Änderungen gehen für andere Entitäten verloren :(


Zweiter Lösungsansatz: Benutzerdefinierte Implementierung für das Repository

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;
    }

}

Dieser Ansatz funktioniert ähnlich wie @Modifying(clearAutomatically = true), er zwingt den EntityManager jedoch zuerst, alle Änderungen in die Datenbank zu schreiben, bevor die Aktualisierung ausgeführt wird, und löscht dann den Persistenzkontext. Auf diese Weise gibt es keine veralteten Entitäten und alle Änderungen werden in der Datenbank gespeichert.


Ich würde gerne wissen, ob es eine bessere Möglichkeit gibt, Aktualisierungsanweisungen in JPA auszuführen, ohne dass das Problem der veralteten Entitäten auftritt und ohne dass das manuelle Flush in die DB erfolgt. Deaktivieren Sie möglicherweise den Cache der 2. Ebene? Wie mache ich das im Spring Boot?


pdate 2018

Spring Data JPA hat meine PR genehmigt, es gibt jetzt eine flushAutomatically -Option in @Modifying().

@Modifying(flushAutomatically = true, clearAutomatically = true)
47
ciri-cuervo

Ich weiß, dass dies keine direkte Antwort auf Ihre Frage ist, da Sie bereits einen Fix erstellt und eine Pull-Anfrage für Github gestartet haben. Danke für das!

Aber ich möchte Ihnen den JPA-Weg erläutern, den Sie gehen können. Sie möchten also alle Entitäten ändern, die einem bestimmten Kriterium entsprechen, und jeweils einen Wert aktualisieren. Der normale Ansatz besteht nur darin, alle benötigten Entitäten zu laden:

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

Dann iteriere darüber und aktualisiere die Werte:

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

Jetzt kennt der Ruhezustand alle Änderungen und schreibt sie in die Datenbank, wenn die Transaktion abgeschlossen ist oder Sie EntityManager.flush() manuell aufrufen. Ich weiß, dass dies nicht gut funktioniert, wenn Sie eine große Menge an Dateneinträgen haben, da Sie alle Entitäten in den Speicher laden. Dies ist jedoch die beste Methode, um den Cache für den Ruhezustand von Entitäten, Caches der zweiten Ebene und die Datenbank synchron zu halten.

Sagt diese Antwort "Die Annotation" @ Modifying "ist nutzlos"? Nein! Wenn Sie sicherstellen, dass sich die geänderten Entitäten nicht in Ihrem lokalen Cache befinden, z. schreibgeschützte Anwendung, dieser Ansatz ist genau das Richtige.

Und nur zur Veranschaulichung: Sie benötigen kein @Transactional Für Ihre Repository-Methoden.

Nur für den Datensatz v2: Die Spalte active sieht so aus, als ob sie direkt von expire abhängt. Warum also nicht active komplett löschen und in jeder Abfrage nur expire betrachten?

8
Johannes Leimer

Wie klaus-groenbaek sagte, können Sie EntityManager injizieren und seine Aktualisierungsmethode verwenden:

@Inject
EntityManager entityManager;

...

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