用 Spring JPA 处理软删除

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

id, <fields>..., active

Active 是软删除标志,始终是 10。从长远来看,这种情况可能会消失,取而代之的是历史数据表。

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

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

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

如果需要的话,我使用 Hibernate 4.2作为我的 JPA 实现。

67505 次浏览

在当前版本(直到1.4.1)中,Spring Data JPA 没有对软删除的专门支持。但是,我强烈建议您使用 DATAJPA-307的特性分支,因为这是目前为即将发布的版本所使用的特性。

要使用当前状态,需要将所使用的版本更新为1.5.0.DATAJPA-307-SNAPSHOT,并确保使用所需的特殊 Spring Data Commons 版本。你应该能够按照样本 测试案例我们必须看到如何得到的东西工作。

附注: 一旦我们完成了这个功能,我就会更新这个问题。

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

假设你有一个实体狗:

 @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;
}

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

如果不想导入特定于 hibernate 的注释,我建议您使用数据库视图(或 Oracle 中的等效视图)。在 mySQL 5.5中,如果筛选条件简单到 active = 1,那么这些视图是可更新和可插入的

创建或替换 view active _ Stuff 作为 select * from Stuff where active = 1;

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

取消删除需要一个额外的实体,它直接访问“ Stuff”,然后@Where 也会这样做

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

首先,它只能在 hibernate 实现中工作。

其次,永远不能获取带有弹簧数据的软删除实体。

我的解决方案是由弹簧数据提供的。#{#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);

您可以从 SimpleJpaRepository 进行扩展,并创建自己的自定义存储库,在这里您可以以通用的方式定义 softdelere 功能。

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

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

基于易天明的回答,我创建了 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...
}

我使用@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
}

我定义了一个这样的仓库

@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";
}
}

我将 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);
}
}

正如您可能看到的,我从 delete 方法抛出 UnsupportedOperationException。这样做是为了限制项目中没有经验的程序员调用这些方法。相反,您可以实现自己的 delete 方法。