Spring Data JDBC 实体回调Entity Callback

时间:2022-10-14 08:54:59

概述:

在本教程中,我想向您展示如何使用Spring Data JDBC实体回调Entity Callback来注册一组钩子,这使我们能够在使用实体对象时在幕后调用某些方法。

Spring Data JDBC实体回调:

Spring Data提供了一些方便的钩子/方法,可以在保存之前或检索后执行以检查和修改实体对象!此挂钩也作为 Spring Data JDBC 的一部分包含在内。它称为 Spring Data JDBC实体回调

我们有以下钩子。

  • BeforeConvert
  • AfterConvert
  • BeforeSave
  • AfterSave
描述: __________
BeforeConvert 在将实体对象转换为出站行对象之前对其进行修改。
使用此选项可在保存之前修改实体对象。
BeforeSave 实体对象将转换为出站行。我们仍然可以修改域对象。
使用此选项可在保存之前修改出站行。
AfterSave 此时将保存实体对象。如果它是一个新实体,则 ID 将已更新。
使用此选项可在保存后修改实体对象。
AfterConvert 从 DB 检索实体对象。
使用此选项可在读取 DB 后修改实体对象。

这些钩子/回调可以在您的应用程序中用于审计日志记录支持/为事件驱动的应用程序引发事件等情况。

例如:repository.save 方法可能会按此顺序调用这些钩子。

Spring Data JDBC 实体回调Entity Callback

示例应用程序:

让我们考虑一个简单的应用程序,产品服务,它处理产品特定的信息。我们有一些要求,如

  • 产品名称不应包含任何特殊字符。如果它们存在,则在保存之前将其删除。
  • 当我们从DB检索产品信息时,应应用季节性特别折扣。

让我们看看如何使用这些钩子来实现这些目标。

  • BeforeConvert
  • AfterConvert

项目设置:

让我们创建一个具有这些依赖项的弹簧启动应用程序。

产品表:

我的表结构如下所示。

CREATE TABLE `product` (
	`id` INT(10) NOT NULL AUTO_INCREMENT,
	`name` VARCHAR(45) NOT NULL COLLATE 'utf8mb4_unicode_ci',
	`brand` VARCHAR(45) NOT NULL COLLATE 'utf8mb4_unicode_ci',
	`madein` VARCHAR(45) NOT NULL COLLATE 'utf8mb4_unicode_ci',
	`price` FLOAT NOT NULL,
	PRIMARY KEY (`id`) USING BTREE
);

实体:

package net.codejava;

import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;

import lombok.Data;

@Table("product")
@Data // lomok
public class Product {

    @Id
    private Long id;
    private String name;
    private String brand;
    private String madein;
    private float price;

    protected Product() {
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getMadein() {
        return madein;
    }

    public void setMadein(String madein) {
        this.madein = madein;
    }

    public float getPrice() {
        return price;
    }

    public void setPrice(float price) {
        this.price = price;
    }

}

产品存储库:

package net.codejava;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;

@Repository

public interface ProductRepository extends PagingAndSortingRepository<Product, Long>, ProductRepositoryCustom {

    Page<Product> findAllByNameContaining(String name, Pageable pageable);
}

实体回调 – BeforeConvert:

让我们实现删除产品名称中不允许出现的任何字符的要求。允许的字符只是字母和空格。在这里,我们可以使用“转换前”回调挂钩在保存之前修改实体对象。

package net.codejava;

import org.springframework.data.relational.core.mapping.event.BeforeConvertCallback;
import org.springframework.stereotype.Component;

@Component
public class ProductBeforeConvert implements BeforeConvertCallback<Product> {

    private static final String PATTERN = "[^a-zA-Z ]";

    @Override
    public Product onBeforeConvert(Product product) {
        var updatedDescription = product.getName().replaceAll(PATTERN, "");
        System.out.println("Actual : " + product.getName());
        System.out.println("Updated : " + updatedDescription);
        product.setName(updatedDescription);
        return product;
    }

}

实体回调 – AfterConvert:

让我们实现另一个要求 - 当我们从DB选择记录时,将季节性全球折扣应用于所有产品。在这里,我们不想触及数据库。我们仍然希望保持原价不变。我们只想在从数据库中检索到价格后更新价格。

转换后回调钩子将是一个不错的选择。

package net.codejava;

import org.springframework.data.relational.core.mapping.event.AfterConvertCallback;
import org.springframework.stereotype.Component;

@Component
public class ProductAfterConvert implements AfterConvertCallback<Product> {

    private final double seasonalDiscount = 0.2d;

    @Override
    public Product onAfterConvert(Product product) {
        double price = (product.getPrice() * (1 - seasonalDiscount));
        System.out.println("Actual  : " + product.getPrice());
        System.out.println("Updated : " + price);
        product.setPrice((float) price);
        return product;
    }

}

即使我们在数据库中的价格为10.0,我们也将价格视为8.0。

多个回调:

我们可以为同一个钩子有多个实现。例如:我们可以为转换前回调提供多个实现。在这种情况下,我们的实现应该实现 Ordered,并在此处显示,以返回应执行的顺序。

package net.codejava;

import org.springframework.core.Ordered;
import org.springframework.data.relational.core.mapping.event.BeforeConvertCallback;
import org.springframework.stereotype.Component;

@Component
public class ProductBeforeConvert2 implements BeforeConvertCallback<Product>, Ordered {

    @Override
    public int getOrder() {
        // int HIGHEST_PRECEDENCE = -2147483648;
        // int LOWEST_PRECEDENCE = 2147483647;
        return 2;
    }

    @Override
    public Product onBeforeConvert(Product product) {
        var updatedDescription = product.getName() + " New!";
        System.out.println("Actual : " + product.getName());
        System.out.println("Updated : " + updatedDescription);
        product.setName(updatedDescription);
        return product;
    }

}

实体回调 – BeforeSave:

有时,实际表可能具有更多列,实体对象可能不包含所有字段。例如:created_bycreated_date等字段。但我们可能需要更新这些字段。在这种情况下,BeforeConvertCallback将没有多大帮助!但是我们可以在之前实现BeforeSaveCallback

我在表中添加新列 - created_by。产品实体没有此字段。

create table product (
    id bigint auto_increment,
    description varchar(50),
    price decimal,
    created_by varchar(50),
    primary key (id)
);

我可以使用BeforeSaveCallback 来填充该字段,如下所示。[请注意,此时已创建出站行。修改产品实体不会产生任何影响。因此,我们需要更新出站行(如果有的话)

@Component
public class ProductBeforeSave implements BeforeSaveCallback<Product> {

    @Override
    public Publisher<Product> onBeforeSave(Product product, OutboundRow outboundRow, SqlIdentifier sqlIdentifier) {
        outboundRow.put(SqlIdentifier.unquoted("created_by"), Parameter.from("vinsguru"));
        return Mono.just(product);
    }

}

有了这个钩子,我们可以更新其他字段(如果有的话),或者我们可以将此钩子用于审计日志记录目的。

总结:

我们学习了如何使用挂钩 / 实体回调。