JPA-Massenaktualisierung und -Löschung mit Blaze Persistence
Einführung
In diesem Artikel zeige ich Ihnen, wie Sie mit dem erstaunlichen Blaze Persistence-Framework JPA-Bulk-Update- und Delete-Abfragen schreiben.
Blaze Persistence ist ein JPA-Framework, mit dem Sie Criteria-Abfragen erstellen können, die viel leistungsfähiger sind als die Standard-JPA-Criteria-API. Darüber hinaus bietet es auch Unterstützung für die Keyset-Paginierung, was sehr nützlich ist, wenn Sie durch große Ergebnismengen navigieren müssen.
Domänenmodell
In diesem Artikel werde ich dieselben Entitätsklassen verwenden, die ich für die Massenaktualisierungs- und Löschabfragen der JPA Criteria API verwendet habe:
Der PostStatus
ist ein Java Enum
die steuert, ob ein gegebener Post
oder PostComment
sollte in unserer Anwendung sichtbar sein.
Weil Post
und PostComment
Datensätze müssen moderiert werden, der initiale Status ist PENDING
. Wenn die Systemadministratoren entscheiden, dass eine bestimmte Buchung gültig ist, wird der Status zu APPROVED
, und die Post
und PostComment
Einträge werden sichtbar. Andernfalls werden sie als SPAM
gekennzeichnet .
Beide Post
und PostComment
erweitern Sie den PostModerate
Basisklasse, und da die Basisklasse persistente Eigenschaften enthält, müssen wir sie mit @MappedSuperclass
annotieren JPA-Anmerkung.
@MappedSuperClass
verwenden Anmerkung.
Die PostModerate
Klasse sieht wie folgt aus:
@MappedSuperclass public abstract class PostModerate { @Enumerated(EnumType.ORDINAL) @Column(columnDefinition = "tinyint") private PostStatus status = PostStatus.PENDING; @Column(name = "updated_on") private Date updatedOn = new Date(); //Getters and setters omitted for brevity }
Wenn Sie beibehalten möchten Enum
Eigenschaften, dann ist der kompakteste Spaltentyp der kürzeste verfügbare ganzzahlige Spaltentyp.
Weitere Einzelheiten zu den Vor- und Nachteilen verschiedener Enum
-persistierende Strategien, lesen Sie diesen Artikel.
Die Post
übergeordnete Entität sieht so aus:
@Entity(name = "Post") @Table(name = "post") public class Post extends PostModerate { @Id @GeneratedValue private Long id; private String title; private String message; //Getters and setters omitted for brevity }
Und die PostComment
untergeordnete Entität sieht wie folgt aus:
@Entity(name = "PostComment") @Table(name = "post_comment") public class PostComment extends PostModerate { @Id @GeneratedValue private Long id; @ManyToOne(fetch = FetchType.LAZY) private Post post; private String message; //Getters and setters omitted for brevity }
Standardmäßig @ManyToOne
und @OneToOne
Assoziationen verwenden den FetchType.EAGER
Abrufstrategie, was sich sehr nachteilig auf die Leistung auswirkt und zu N+1-Abfrageproblemen führen kann.
Weitere Einzelheiten finden Sie in diesem Artikel.
Testdaten
In Anbetracht dessen, dass wir die folgenden Entitäten zu unserem System hinzugefügt haben:
entityManager.persist( new Post() .setId(1L) .setTitle("High-Performance Java Persistence") .setStatus(PostStatus.APPROVED) ); entityManager.persist( new Post() .setId(2L) .setTitle("Spam title") ); entityManager.persist( new Post() .setId(3L) .setMessage("Spam message") ); entityManager.persist( new PostComment() .setId(1L) .setPost( entityManager.getReference(Post.class, 1L) ) .setMessage("Spam comment") );
Wir haben also:
- ein
Post
Entität imAPPROVED
Zustand mit einemPostComment
untergeordnete Entität imPENDING
Zustand und enthält einen Spammessage
- zwei
Post
Entitäten imPENDING
Zustand mit einem Spamtitle
Blaze-Persistenz-Massenaktualisierung
Um eine Bulk-UPDATE-Anweisung dynamisch zu erstellen, bietet Blaze Persistence den UpdateCriteriaBuilder
Dienstprogramm.
Um zu sehen, wie UpdateCriteriaBuilder
funktioniert, sehen Sie sich das folgende Beispiel an:
public <T extends PostModerate> int flagSpam( EntityManager entityManager, Class<T> postModerateClass) { UpdateCriteriaBuilder<T> builder = cbf .update(entityManager, postModerateClass) .set(PostModerate_.STATUS, PostStatus.SPAM) .set(PostModerate_.UPDATED_ON, new Date()); String spamToken = "%spam%"; if(Post.class.isAssignableFrom(postModerateClass)) { builder .whereOr() .where(lower(Post_.MESSAGE)) .like().value(spamToken).noEscape() .where(lower(Post_.TITLE)) .like().value(spamToken).noEscape() .endOr(); } else if(PostComment.class.isAssignableFrom(postModerateClass)) { builder .where(lower(PostComment_.MESSAGE)) .like().value(spamToken).noEscape(); } return builder.executeUpdate(); }
Die flagSpam
Methode funktioniert wie folgt:
- Zuerst benötigen wir einen
UpdateCriteriaBuilder
Verweis, damit wir unsere dynamische Massenaktualisierungsanweisung für den bereitgestelltenPostModerate
erstellen können Wesen. - Zweitens setzen wir den
status
-Eigenschaft aufSPAM
und dieupdateOn
Eigentum bis zum aktuellen Datum. - Dann erstellen wir ein Filterprädikat für
message
-Eigenschaft, die allenPostModerate
gemeinsam ist Entitäten, die moderiert werden müssen. - Nur für
Post
Entität prüfen wir auch dentitle
Eigentum.
Beachten Sie, dass wir den Post_
verwendet haben , PostComment_
und PostModerate_
JPA-Metadatenklassen zum Verweisen auf die Entitätseigenschaften.
Weitere Einzelheiten zum JPA-Metamodell finden Sie in diesem Artikel.
Dieses Beispiel zeigt die wahre Leistungsfähigkeit eines dynamischen Abfragegenerators, da die SQL-Anweisungssyntax je nach den bereitgestellten Argumenten variiert.
Ohne Blaze Persistence oder Criteria API würden Sie wahrscheinlich auf String
zurückgreifen Verkettung und Risiko von SQL-Injection-Angriffen.
Beim Ausführen von flagSpam
Methode gegen Post
Entitätsklasse:
assertEquals(2, flagSpam(entityManager, Post.class));
Hibernate führt die folgende SQL-Anweisung aus:
UPDATE post SET status = 2, updated_on = '2018-01-09 10:50:42.861' WHERE lower(message) LIKE '%spam%' OR lower(title) LIKE '%spam%'
Und wenn es gegen PostComment
ausgeführt wird Aufzeichnungen:
assertEquals(1, flagSpam(entityManager, PostComment.class));
Wir bekommen die folgende SQL-Anweisung ausgeführt:
UPDATE post_comment SET status = 2, updated_on = '2018-01-09 10:50:43.07' WHERE lower(message) LIKE '%spam%'
Blaze-Persistenz-Massenlöschung
Sie können Blaze Persistence verwenden, um Massenlöschabfragen dynamisch zu erstellen.
Das folgende Beispiel zeigt beispielsweise, wie Sie den alten Post
löschen können und PostComment
Entitäten mit dem status
von SPAM
:
public <T extends PostModerate> int deleteSpam( EntityManager entityManager, Class<T> postModerateClass) { return cbf .delete(entityManager, postModerateClass) .where(PostModerate_.STATUS).eq(PostStatus.SPAM) .where(PostModerate_.UPDATED_ON).le( Timestamp.valueOf( LocalDateTime.now().minusDays( (Post.class.isAssignableFrom(postModerateClass)) ? 7 : 3 ) ) ) .executeUpdate(); }
Dieses Mal variieren wir nur den an das Filterprädikat übergebenen Parameter. Sie können jedoch die gesamte WHERE-Klausel variieren, genau wie wir es für die Bulk-Update-Anweisung getan haben.
Um zu testen, wie es funktioniert, stellen wir sicher, dass unsere Spam-Postings alt genug sind, um gelöscht zu werden:
entityManager.createQuery(""" update Post set updatedOn = :timestamp where status = :status """) .setParameter( "timestamp", Timestamp.valueOf(LocalDateTime.now().minusDays(7)) ) .setParameter("status", PostStatus.SPAM) .executeUpdate(); entityManager.createQuery(""" update PostComment set updatedOn = :timestamp where status = :status """) .setParameter( "timestamp", Timestamp.valueOf(LocalDateTime.now().minusDays(3)) ) .setParameter("status", PostStatus.SPAM) .executeUpdate();
Gut, jetzt können wir deleteSpam
ausführen Methode:
assertEquals(2, deleteSpam(entityManager, Post.class)); assertEquals(1, deleteSpam(entityManager, PostComment.class));
und Hibernate wird die folgenden DELETE-Anweisungen ausführen:
DELETE FROM post WHERE status = 2 AND updated_on <= '2021-09-07 17:19:11.709' DELETE FROM post_comment WHERE status = 2 AND updated_on <= '2021-09-11 17:19:11.720'
Großartig, oder?
Schlussfolgerung
Blaze Persistence ist eine sehr gute Alternative zur standardmäßigen JPA Criteria API.
Wenn Sie neugierig sind, wie Sie dieselben Abfragen mit der Criteria API schreiben würden, sehen Sie sich diesen vorherigen Artikel an, den ich geschrieben habe, und Ihnen wird Blaze Persistence sicherlich besser gefallen.