Java >> Java Tutorial >  >> Java

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.

Wenn Sie Eigenschaften von mehreren Entitäten wiederverwenden möchten, können Sie den @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 im APPROVED Zustand mit einem PostComment untergeordnete Entität im PENDING Zustand und enthält einen Spam message
  • zwei Post Entitäten im PENDING Zustand mit einem Spam title

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 bereitgestellten PostModerate erstellen können Wesen.
  • Zweitens setzen wir den status -Eigenschaft auf SPAM und die updateOn Eigentum bis zum aktuellen Datum.
  • Dann erstellen wir ein Filterprädikat für message -Eigenschaft, die allen PostModerate gemeinsam ist Entitäten, die moderiert werden müssen.
  • Nur für Post Entität prüfen wir auch den title 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.


Java-Tag