一、简介
从表中物理删除数据是与数据库交互时的常见要求。但有时业务要求不从数据库中永久删除数据。这些要求,例如,需要数据历史跟踪或审计,也与参考完整性相关。
我们可以隐藏该数据,以便无法从应用程序前端访问它,而不是物理删除数据。
在本教程中,我们将了解软删除以及如何使用Spring JPA实现此技术。
2. 什么是软删除?
软删除执行更新过程以将某些数据标记为已删除,而不是从数据库的表中物理删除它。 实现软删除的常见方法是添加一个字段来指示数据是否已被删除。
现在让我们看一下从表中物理删除记录时将运行的 SQL 命令:
delete from table_product where id=1
此 SQL 命令将从数据库的表中永久删除 id=1 的产品。
请注意,我们添加了一个名为 “已删除”的新字段。 该字段将包含值 0 或 1 。
值为 1 表示数据已被删除,值为 0 表示数据尚未删除。我们应该将 0 设置为默认值,并且对于每个数据删除过程,我们不运行SQL删除命令,而是运行以下SQL更新命令:
update from table_product set deleted=1 where id=1
使用此 SQL 命令,我们实际上并没有删除该行,而只是将其标记为已删除。因此,当我们要执行读取查询时,并且只想要那些尚未删除的行,我们应该只在 SQL 查询中添加一个过滤器:
select * from table_product where deleted=0
3. Spring JPA中如何实现软删除
使用Spring JPA,软删除的实现变得更加容易。为此,我们只需要一些 JPA 注释。
众所周知,我们通常只在 JPA 中使用很少的 SQL 命令。它将在幕后创建并执行大部分 SQL 查询。
现在让我们使用与上面相同的表示例在 Spring JPA 中实现软删除。
3.1.实体类
最重要的部分是创建实体类。
让我们创建一个 Product 实体类:
@Entity
@Table(name = "table_product")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
private boolean deleted = Boolean.FALSE;
// setter getter methods
}
正如我们所看到的,我们添加了一个 已删除的 属性,其默认值设置为 FALSE 。
下一步将覆盖 JPA 存储库中的 删除 命令。
默认情况下,JPA 存储库中的删除命令将运行 SQL 删除查询,因此我们首先向实体类添加一些注释:
@Entity
@Table(name = "table_product")
@SQLDelete(sql = "UPDATE table_product SET deleted = true WHERE id=?")
@Where(clause = "deleted=false")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
private boolean deleted = Boolean.FALSE;
// setter getter method
}
我们使用 @SQLDelete 注释来覆盖删除命令。 每次执行删除命令时,我们实际上已经 将其变成了SQL更新命令,将删除的字段值更改为true, 而不是永久删除数据。
另一方面, @Where 注解会在我们读取产品数据时添加过滤器。 因此,根据上面的代码示例,值为 deleted = true的 产品数据将不会包含在结果中。
3.2.存储库
存储库类没有特殊的变化,我们可以像Spring Boot应用程序中的普通存储库类一样编写它:
public interface ProductRepository extends CrudRepository<Product, Long>{
}
3.3.服务
对于服务级别来说,还没有什么特别的。我们可以从存储库中调用我们想要的函数。
在这个例子中,我们调用三个存储库函数来创建一条记录,然后执行软删除:
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
public Product create(Product product) {
return productRepository.save(product);
}
public void remove(Long id){
productRepository.deleteById(id);
}
public Iterable<Product> findAll(){
return productRepository.findAll();
}
}
4. 如何获取已删除的数据?
通过使用 @Where 注释,如果我们仍然希望可以访问已删除的数据,则无法获取已删除的产品数据。例如,具有管理员级别的用户具有完全访问权限,可以查看已“删除”的数据。
为了实现这一点,我们不应该使用 @Where 注释 ,而应该使用两个不同的注释: @FilterDef 和 @Filter 。通过这些注释,我们可以根据需要动态添加条件:
@Entity
@Table(name = "tbl_products")
@SQLDelete(sql = "UPDATE tbl_products SET deleted = true WHERE id=?")
@FilterDef(name = "deletedProductFilter", parameters = @ParamDef(name = "isDeleted", type = "boolean"))
@Filter(name = "deletedProductFilter", condition = "deleted = :isDeleted")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
private boolean deleted = Boolean.FALSE;
}
这里 @FilterDef 注解定义了 @Filter 注解将使用的基本要求。此外,我们还需要更改 ProductService 服务类中的 findAll() 函数来处理动态参数或过滤器:
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
@Autowired
private EntityManager entityManager;
public Product create(Product product) {
return productRepository.save(product);
}
public void remove(Long id){
productRepository.deleteById(id);
}
public Iterable<Product> findAll(boolean isDeleted){
Session session = entityManager.unwrap(Session.class);
Filter filter = session.enableFilter("deletedProductFilter");
filter.setParameter("isDeleted", isDeleted);
Iterable<Product> products = productRepository.findAll();
session.disableFilter("deletedProductFilter");
return products;
}
}
这里我们添加 isDeleted 参数,我们将添加到影响读取 Product 实体的过程的对象 Filter 中。
5. 结论
使用 Spring JPA 可以轻松实现软删除技术。我们需要做的是定义一个字段来存储行是否已被删除。然后我们必须使用该特定实体类上的 @SQLDelete 注释来覆盖删除命令。
如果我们想要更多的控制,我们可以使用 @FilterDef 和 @Filter 注释,这样我们就可以确定查询结果是否应该包含已删除的数据。
本文中的所有代码都可以 在 GitHub 上获取。