软删除,即通过在标识数据表中的某个字段来达到看似删除的目的,由于软删除对数据库的侵入比较小,所以在删除时可以完美的规避数据约束的问题。
我曾在4年前写过一篇spring boot实现软删除,在该文章中给出了实现软删除的一种方式。本文将使用一种更优雅的方式来实现它。
项目源码
本文项目源码https://github.com/codedemo-club/spring-data-jpa-soft-delete
接口
首先我们需要建立一个接口,来标识哪些实体在查询时是需要软删除的:
public interface SoftDelete {
Boolean getDeleted();
}
然后实体实现这一接口:
@Entity
public class Foo implements SoftDelete {
private Boolean deleted = false;
@Override
public Boolean getDeleted() {
return deleted;
}
// 设置为私有
private void setDeleted(Boolean deleted) {
this.deleted = deleted;
}
注意:在此省略了其它的属性,其它的属性请参考源代码
@Entity
public class Bar implements SoftDelete {
private Boolean deleted = false;
@Override
public Boolean getDeleted() {
return deleted;
}
// 设置为私有
private void setDeleted(Boolean deleted) {
this.deleted = deleted;
}
设置标识
接下来,使用@SQLDelete
注解来设置删除语句,虽然此操作我们也可以通过相应的方法统一设置,但使用@SQLDelete
会使软删除更加的灵活,特别是当我们解决一些数据唯一性的时候。
@Entity
@SQLDelete(sql = "update `bar` set deleted = 1 where id = ?")
public class Bar implements SoftDelete {
@Entity
@SQLDelete(sql = "update `foo` set deleted = 1 where id = ?")
public class Foo implements SoftDelete {
自定义软删除实现类
通过自定义的软删除实现类来达到在查询时加入软删除的目的。
package club.codedemo.springdatajpasoftdelete;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaEntityInformationSupport;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.lang.Nullable;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* 软删除实现类
* https://www.codedemo.club/spring-data-jpa-soft-delete/
* https://developer.aliyun.com/article/465404
* https://stackoverflow.com/questions/36721601/spring-boot-how-to-declare-a-custom-repository-factory-bean
*/
@Transactional(
readOnly = true
)
public class SoftDeleteRepositoryImpl<T, ID> extends SimpleJpaRepository<T, ID> implements PagingAndSortingRepository<T, ID>,
CrudRepository<T, ID>,
JpaSpecificationExecutor<T> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
public SoftDeleteRepositoryImpl(Class<T> domainClass, EntityManager em) {
this(JpaEntityInformationSupport.getEntityInformation(domainClass, em), em);
}
public SoftDeleteRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
}
@Override
public boolean existsById(ID id) {
return this.findById(id).isPresent();
}
@Override
public List<T> findAll() {
return this.findAll(this.andDeleteFalseSpecification(null));
}
@Override
public Page<T> findAll(Pageable pageable) {
return this.findAll(this.andDeleteFalseSpecification(null), pageable);
}
@Override
public List<T> findAll(@Nullable Specification<T> specification) {
return super.findAll(this.andDeleteFalseSpecification(specification));
}
@Override
public Page<T> findAll(@Nullable Specification<T> specification, Pageable pageable) {
return super.findAll(this.andDeleteFalseSpecification(specification), pageable);
}
@Override
public List<T> findAll(@Nullable Specification<T> specification, Sort sort) {
return super.findAll(this.andDeleteFalseSpecification(specification), sort);
}
@Override
public Optional<T> findById(ID id) {
Optional<T> entityOptional = super.findById(id);
if (entityOptional.isPresent()) {
T entity = entityOptional.get();
if (entity instanceof SoftDelete) {
if (!((SoftDelete) entity).getDeleted())
return entityOptional;
} else {
this.logger.warn("未实现SoftDeleteEntity的实体" + entity.getClass() + "使用了软删除功能。请检查JpaRepositoryFactoryBean配置");
}
}
return Optional.empty();
}
@Override
public List<T> findAllById(Iterable<ID> ids) {
return super.findAllById(ids).stream().filter(entity -> {
if (entity instanceof SoftDelete) {
return !((SoftDelete) entity).getDeleted();
} else {
this.logger.warn("未实现SoftDeleteEntity的实体" + entity.getClass() + "使用了软删除功能。请检查JpaRepositoryFactoryBean配置");
}
return false;
}).collect(Collectors.toList());
}
@Override
public long count() {
return this.count(this.andDeleteFalseSpecification(null));
}
@Override
public long count(@Nullable Specification<T> specification) {
return super.count(this.andDeleteFalseSpecification(specification));
}
/**
* 添加软查询条件
*
* @param specification 综合查询条件
*/
private Specification<T> andDeleteFalseSpecification(Specification<T> specification) {
Specification<T> deleteIsFalse = (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.equal(root.get("deleted").as(Boolean.class), false);
if (specification == null) {
specification = deleteIsFalse;
} else {
specification = specification.and(deleteIsFalse);
}
return specification;
}
}
自定义JpaRepositoryFactoryBean
通过自定义JpaRepositoryFactoryBean
达到:实现了SoftDelete
的实体在查询时使用SoftDeleteRepositoryImpl
,而未实现SoftDelete
的实体在查询时使用原SimpleJpaRepository
/**
* 自定义软件删除工厂
* @param <R> 仓库
* @param <T> 实体
*/
public class SoftDeleteRepositoryFactoryBean <R extends JpaRepository<T, Serializable>, T> extends JpaRepositoryFactoryBean<R, T, Serializable> {
public SoftDeleteRepositoryFactoryBean(Class<? extends R> repositoryInterface) {
super(repositoryInterface);
}
@Override
protected RepositoryFactorySupport createRepositoryFactory(final EntityManager entityManager) {
return new JpaRepositoryFactory(entityManager) {
protected SimpleJpaRepository<T, Serializable> getTargetRepository(
RepositoryInformation information, EntityManager entityManager) {
Class<T> domainClass = (Class<T>) information.getDomainType();
if(SoftDelete.class.isAssignableFrom(domainClass)) {
return new SoftDeleteRepositoryImpl(domainClass, entityManager);
} else {
return new SimpleJpaRepository(domainClass, entityManager);
}
}
@Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return metadata.getDomainType().isAssignableFrom(SoftDelete.class) ? SoftDeleteRepositoryImpl.class : SimpleJpaRepository.class;
}
};
}
}
注册Bean
最后我们注册刚刚创建的工厂Bean,以使其生效:
@EnableJpaRepositories(value = "club.codedemo.springdatajpasoftdelete",
repositoryFactoryBeanClass = SoftDeleteRepositoryFactoryBean.class)
public class SpringDataJpaSoftDeleteApplication {
此时,将我们在进行查询操作时,如果实体实现了SoftDelete,则会使用我们自定义的SoftDeleteRepositoryImpl
,而如果没有实现SoftDelete,则仍然使用原查询。
在SoftDeleteRepositoryImpl
方法中,我们重写了所有的查询方法,并在查询中加入了deleted
为fasle
的查询条件。
测试
此时,重新启动系统,点击删除操作后。数据仍然存在于数据中,而且关联查询也不会发生任何错误。如果我们希望在@OneToMany
注解中也使用软删除生效(即不返回已删除的关联实体),则仅仅需要在相应的字段加入@Where(clause = "deleted = false")
注解即可。