在本教程中,我想向您展示如何使用Spring Data JDBC实体回调Entity Callback来注册一组钩子,这使我们能够在使用实体对象时在幕后调用某些方法。
Spring Data提供了一些方便的钩子/方法,可以在保存之前或检索后执行以检查和修改实体对象!此挂钩也作为 Spring Data JDBC 的一部分包含在内。它称为 Spring Data JDBC实体回调。
我们有以下钩子。
钩 | 描述: __________ |
---|---|
BeforeConvert | 在将实体对象转换为出站行对象之前对其进行修改。 使用此选项可在保存之前修改实体对象。 |
BeforeSave | 实体对象将转换为出站行。我们仍然可以修改域对象。 使用此选项可在保存之前修改出站行。 |
AfterSave | 此时将保存实体对象。如果它是一个新实体,则 ID 将已更新。 使用此选项可在保存后修改实体对象。 |
AfterConvert | 从 DB 检索实体对象。 使用此选项可在读取 DB 后修改实体对象。 |
这些钩子/回调可以在您的应用程序中用于审计日志记录支持/为事件驱动的应用程序引发事件等情况。
例如:repository.save 方法可能会按此顺序调用这些钩子。
让我们考虑一个简单的应用程序,产品服务,它处理产品特定的信息。我们有一些要求,如
让我们看看如何使用这些钩子来实现这些目标。
让我们创建一个具有这些依赖项的弹簧启动应用程序。
我的表结构如下所示。
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, ProductRepositoryCustom {
Page findAllByNameContaining(String name, Pageable pageable);
}
让我们实现删除产品名称中不允许出现的任何字符的要求。允许的字符只是字母和空格。在这里,我们可以使用“转换前”回调挂钩在保存之前修改实体对象。
package net.codejava;
import org.springframework.data.relational.core.mapping.event.BeforeConvertCallback;
import org.springframework.stereotype.Component;
@Component
public class ProductBeforeConvert implements BeforeConvertCallback {
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;
}
}
让我们实现另一个要求 - 当我们从DB选择记录时,将季节性全球折扣应用于所有产品。在这里,我们不想触及数据库。我们仍然希望保持原价不变。我们只想在从数据库中检索到价格后更新价格。
转换后回调钩子将是一个不错的选择。
package net.codejava;
import org.springframework.data.relational.core.mapping.event.AfterConvertCallback;
import org.springframework.stereotype.Component;
@Component
public class ProductAfterConvert implements AfterConvertCallback {
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, 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;
}
}
有时,实际表可能具有更多列,实体对象可能不包含所有字段。例如:created_by、created_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 {
@Override
public Publisher onBeforeSave(Product product, OutboundRow outboundRow, SqlIdentifier sqlIdentifier) {
outboundRow.put(SqlIdentifier.unquoted("created_by"), Parameter.from("vinsguru"));
return Mono.just(product);
}
}
有了这个钩子,我们可以更新其他字段(如果有的话),或者我们可以将此钩子用于审计日志记录目的。
我们学习了如何使用挂钩 / 实体回调。