71

我有一个表Stuff定义为...

id, <fields>..., active

Active 是软删除标志,始终是1or 0。从长远来看,这可能会消失,取而代之的是历史表格。

public interface StuffRepository extends JpaRepository<StuffEntity, Long> {} 

在代码中,我们总是 使用活动记录。有什么方法可以让 Spring 始终将active=1条件附加到为此存储库生成的查询?或者更理想地允许我扩展用于生成查询的语法?

我知道我可以@queues在任何地方创建命名,但是我失去了生成查询的便利性。我还想避免使用“活动”方法污染接口。

如果这很重要,我将使用 Hibernate 4.2 作为我的 JPA 实现。

4

9 回答 9

123

@Where(clause="is_active=1")不是使用 spring data jpa 处理软删除的最佳方法。

首先,它只适用于休眠工具。

其次,您永远无法使用弹簧数据获取软删除的实体。

我的解决方案是由 spring data 提供的。#{#entityName}可以在通用存储库上使用表达式表示具体的实体类型名称。

代码将是这样的:

//Override CrudRepository or PagingAndSortingRepository's query method:
@Override
@Query("select e from #{#entityName} e where e.deleteFlag=false")
public List<T> findAll();

//Look up deleted entities
@Query("select e from #{#entityName} e where e.deleteFlag=true")
public List<T> recycleBin(); 

//Soft delete.
@Query("update #{#entityName} e set e.deleteFlag=true where e.id=?1")
@Modifying
public void softDelete(String id); 
于 2015-10-16T10:48:58.807 回答
108

这是一个老问题,您可能已经找到了答案。但是,对于所有寻求答案的 Spring/JPA/Hibernate 程序员来说——

假设你有一个实体 Dog:

 @Entity
 public class Dog{

 ......(fields)....        

 @Column(name="is_active")
 private Boolean active;
 }

和一个存储库:

public interface DogRepository extends JpaRepository<Dog, Integer> {
} 

您需要做的就是在实体级别添加 @Where 注释,结果是:

@Entity
@Where(clause="is_active=1")
public class Dog{

......(fields)....        

@Column(name="is_active")
private Boolean active;
}

存储库执行的所有查询将自动过滤掉“非活动”行。

于 2014-03-05T15:48:10.463 回答
45

基于易天明的回答,我创建了 CrudRepository 实现,其中包含用于软删除的重写方法:

@NoRepositoryBean
public interface SoftDeleteCrudRepository<T extends BasicEntity, ID extends Long> extends CrudRepository<T, ID> {
  @Override
  @Transactional(readOnly = true)
  @Query("select e from #{#entityName} e where e.isActive = true")
  List<T> findAll();

  @Override
  @Transactional(readOnly = true)
  @Query("select e from #{#entityName} e where e.id in ?1 and e.isActive = true")
  Iterable<T> findAll(Iterable<ID> ids);

  @Override
  @Transactional(readOnly = true)
  @Query("select e from #{#entityName} e where e.id = ?1 and e.isActive = true")
  T findOne(ID id);

  //Look up deleted entities
  @Query("select e from #{#entityName} e where e.isActive = false")
  @Transactional(readOnly = true)
  List<T> findInactive();

  @Override
  @Transactional(readOnly = true)
  @Query("select count(e) from #{#entityName} e where e.isActive = true")
  long count();

  @Override
  @Transactional(readOnly = true)
  default boolean exists(ID id) {
      return findOne(id) != null;
  }

  @Override
  @Query("update #{#entityName} e set e.isActive=false where e.id = ?1")
  @Transactional
  @Modifying
  void delete(Long id);


  @Override
  @Transactional
  default void delete(T entity) {
      delete(entity.getId());
  }

  @Override
  @Transactional
  default void delete(Iterable<? extends T> entities) {
      entities.forEach(entitiy -> delete(entitiy.getId()));
  }

  @Override
  @Query("update #{#entityName} e set e.isActive=false")
  @Transactional
  @Modifying
  void deleteAll();
}

它可以与 BasicEntity 一起使用:

@MappedSuperclass
public abstract class BasicEntity {
  @Column(name = "is_active")
  private boolean isActive = true;

  public abstract Long getId();

  // isActive getters and setters...
}

最后的实体:

@Entity
@Table(name = "town")
public class Town extends BasicEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "town_id_seq")
    @SequenceGenerator(name = "town_id_seq", sequenceName = "town_id_seq", allocationSize = 1)
    protected Long id;

    private String name;

    // getters and setters...
}
于 2017-06-03T21:49:32.230 回答
12

在当前版本(最高 1.4.1)中,Spring Data JPA 中没有对软删除的专门支持。但是,我强烈建议您使用DATAJPA-307的功能分支,因为这是当前正在为即将发布的版本开发的功能。

要使用当前状态,请将您使用的版本更新为 1.5.0.DATAJPA-307-SNAPSHOT 并确保让它引入它需要工作的特殊 Spring Data Commons 版本。您应该能够按照我们必须了解的示例测试用例来了解如何使这些东西正常工作。

PS:一旦我们完成该功能的工作,我会更新问题。

于 2013-10-13T11:47:33.153 回答
2

您可以从 SimpleJpaRepository 扩展并创建自己的自定义存储库,您可以在其中以通用方式定义软删除功能。

您还需要创建一个自定义 JpaRepositoryFactoryBean 并在您的主类中启用它。

你可以在这里查看我的代码https://github.com/dzinot/spring-boot-jpa-soft-delete

于 2016-12-06T13:11:38.590 回答
2

我使用来自@vadim_shb 的解决方案来扩展 JpaRepository,这是我在 Scala 中的代码。支持他的答案,而不是这个。只是想展示一个包含分页和排序的示例。

分页和排序与查询注释结合使用效果很好。我还没有全部测试过,但是对于那些询问分页和排序的人来说,它们似乎是分层在 Query 注释之上的。如果我解决任何问题,我会进一步更新。

import java.util
import java.util.List

import scala.collection.JavaConverters._
import com.xactly.alignstar.data.model.BaseEntity
import org.springframework.data.domain.{Page, Pageable, Sort}
import org.springframework.data.jpa.repository.{JpaRepository, Modifying, Query}
import org.springframework.data.repository.NoRepositoryBean
import org.springframework.transaction.annotation.Transactional

@NoRepositoryBean
trait BaseRepository[T <: BaseEntity, ID <: java.lang.Long] extends JpaRepository[T, ID] {

  /* additions */
  @Query("select e from #{#entityName} e where e.isDeleted = true")
  @Transactional(readOnly = true)
  def findInactive: Nothing

  @Transactional
  def delete(entity: T): Unit = delete(entity.getId.asInstanceOf[ID])

  /* overrides */
  @Query("select e from #{#entityName} e where e.isDeleted = false")
  override def findAll(sort: Sort):  java.util.List[T]

  @Query("select e from #{#entityName} e where e.isDeleted = false")
  override def findAll(pageable: Pageable): Page[T]

  @Transactional(readOnly = true)
  @Query("select e from #{#entityName} e where e.isDeleted = false")
  override def findAll: util.List[T]

  @Transactional(readOnly = true)
  @Query("select e from #{#entityName} e where e.id in :ids and e.isDeleted = false")
  override def findAll(ids: java.lang.Iterable[ID]): java.util.List[T]

  @Transactional(readOnly = true)
  @Query("select e from #{#entityName} e where e.id = :id and e.isDeleted = false")
  override def findOne(id: ID): T

  @Transactional(readOnly = true)
  @Query("select count(e) from #{#entityName} e where e.isDeleted = false")
  override def count: Long

  @Transactional(readOnly = true)
  override def exists(id: ID): Boolean = findOne(id) != null

  @Query("update #{#entityName} e set e.isDeleted=true where e.id = :id")
  @Transactional
  @Modifying
  override def delete(id: ID): Unit

  @Transactional
  override def delete(entities: java.lang.Iterable[_ <: T]): Unit = {
    entities.asScala.map((entity) => delete(entity))
  }

  @Transactional
  @Modifying
  override def deleteInBatch(entities: java.lang.Iterable[T]): Unit = delete(entities)

  override def deleteAllInBatch(): Unit = throw new NotImplementedError("This is not implemented in BaseRepository")

  @Query("update #{#entityName} e set e.isDeleted=true")
  @Transactional
  @Modifying
  def deleteAll(): Unit
}
于 2018-11-15T04:58:37.097 回答
2

我将vdshb提供的解决方案改编为 Spring JPA 存储库的较新版本。还添加了一些可能出现在您的企业应用程序中的常见字段。

基本实体:

@Data
@MappedSuperclass
public abstract class BasicEntity {

  @Id
  @GeneratedValue
  protected Integer id;

  protected boolean active = true;

  @CreationTimestamp
  @Column(updatable = false, nullable = false)
  protected OffsetDateTime createdDate;

  @UpdateTimestamp
  @Column(nullable = false)
  protected OffsetDateTime modifiedDate;

  protected String createdBy = Constants.SYSTEM_USER;

  protected String modifiedBy = Constants.SYSTEM_USER;
}

基本存储库:

@NoRepositoryBean
public interface BasicRepository<T extends BasicEntity, ID extends Integer> extends JpaRepository<T, ID> {
    @Override
    @Transactional(readOnly = true)
    @Query("select e from #{#entityName} e where e.active = true")
    List<T> findAll();

    @Override
    @Transactional(readOnly = true)
    @Query("select e from #{#entityName} e where e.active = true and e.id = ?1")
    Optional<T> findById(ID id);

    @Override
    @Transactional(readOnly = true)
    @Query("select e from #{#entityName} e where e.id in ?1 and e.active = true")
    List<T> findAllById(Iterable<ID> ids);

    @Override
    @Transactional(readOnly = true)
    @Query("select e from #{#entityName} e where e.id = ?1 and e.active = true")
    T getOne(ID id);

    //Look up deleted entities
    @Query("select e from #{#entityName} e where e.active = false")
    @Transactional(readOnly = true)
    List<T> findAllInactive();

    @Override
    @Transactional(readOnly = true)
    @Query("select count(e) from #{#entityName} e where e.active = true")
    long count();

    @Override
    @Transactional(readOnly = true)
    default boolean existsById(ID id) {
        return getOne(id) != null;
    }

    @Override
    default void deleteById(ID id) {
        throw new UnsupportedOperationException();
    }

    @Override
    default void delete(T entity) {
        throw new UnsupportedOperationException();
    }

    @Override
    default void deleteAll(Iterable<? extends T> entities) {
        throw new UnsupportedOperationException();
    }

    @Override
    default void deleteAll() {
        throw new UnsupportedOperationException();
    }

    /**
     * Soft deletes entity in the database.
     * It will not appear in the result set of default queries.
     *
     * @param id of the entity for deactivation
     * @param modifiedBy who modified this entity
     * @return deactivated entity with fetched fields
     * @throws IncorrectConditionException when the entity is already deactivated.
     * @throws NotFoundException when the entity is not found in the database.
     */
    @Transactional
    @Modifying
    default T deactivate(ID id, String modifiedBy) throws IncorrectConditionException {
        final T entity = findById(id)
                .orElseThrow(() -> new NotFoundException(
                        String.format("Entity with ID [%s] wasn't found in the database. " +
                                "Nothing to deactivate.", id)));
        if (!entity.isActive()) {
            throw new IncorrectConditionException(String.format("Entity with ID [%s] is already deactivated.", id));
        }
        entity.setActive(false);
        entity.setModifiedBy(modifiedBy);
        return save(entity);
    }

    /**
     * Activates soft deleted entity in the database.
     *
     * @param id of the entity for reactivation
     * @param modifiedBy who modified this entity
     * @return updated entity with fetched fields
     * @throws IncorrectConditionException when the entity is already activated.
     * @throws NotFoundException when the entity is not found in the database.
     */
    @Transactional
    @Modifying
    default T reactivate(ID id, String modifiedBy) throws IncorrectConditionException {
        final T entity = findById(id)
                .orElseThrow(() -> new NotFoundException(
                        String.format("Entity with ID [%s] wasn't found in the database. " +
                                "Nothing to reactivate.", id)));
        if (entity.isActive()) {
            throw new IncorrectConditionException(String.format("Entity with ID [%s] is already active.", id));
        }
        entity.setActive(true);
        entity.setModifiedBy(modifiedBy);
        return save(entity);
    }
}

如您所见,我UnsupportedOperationException从删除方法中抛出。它是为了限制您的项目中没有经验的程序员调用这些方法。相反,您可以实现自己的删除方法。

于 2021-04-05T10:03:44.750 回答
1

如果您不想导入特定于休眠的注释,我建议您使用数据库视图(或 Oracle 中的等效视图)。在 mySQL 5.5 中,如果过滤条件像 active=1 这样简单,这些视图可以是可更新和可插入的

创建或替换视图 act​​ive_stuff 作为 select * from Stuff where active=1;

这是否是一个好主意可能取决于您的数据库,但它在我的实现中效果很好。

取消删除需要一个直接访问“Stuff”的附加实体,但@Where 也是如此

于 2015-01-06T00:50:02.293 回答
0

我定义了一个这样的存储库

@NoRepositoryBean
public interface SoftDeleteRepository<T, ID extends Serializable> extends JpaRepository<T, ID>,
    JpaSpecificationExecutor<T> {

    enum StateTag {
        ENABLED(0), DISABLED(1), DELETED(2);

        private final int tag;

        StateTag(int tag) {
            this.tag = tag;
        }

        public int getTag() {
            return tag;
        }
    }

    T changeState(ID id, StateTag state);

    List<T> changeState(Iterable<ID> ids, StateTag state);

    <S extends T> List<S> changeState(Example<S> example, StateTag state);

    List<T> findByState(@Nullable Iterable<StateTag> states);

    List<T> findByState(Sort sort, @Nullable Iterable<StateTag> states);

    Page<T> findByState(Pageable pageable, @Nullable Iterable<StateTag> states);

    <S extends T> List<S> findByState(Example<S> example, @Nullable Iterable<StateTag> states);

    <S extends T> List<S> findByState(Sort sort, Example<S> example, @Nullable Iterable<StateTag> states);

    <S extends T> Page<S> findByState(Pageable pageable, Example<S> example,
                                  @Nullable Iterable<StateTag> states);

    long countByState(@Nullable Iterable<StateTag> states);

    default String getSoftDeleteColumn() {
        return "disabled";
    }
}
于 2019-01-10T09:09:43.147 回答