我将表Stuff
定义为......
id, <fields>..., active
有效是软删除标记,始终为1
或0
。从长远来看,这可能会有利于历史表。
public interface StuffRepository extends JpaRepository<StuffEntity, Long> {}
在代码中,我们始终使用活动记录。有没有办法让Spring始终为为此存储库生成的查询附加active=1
条件?或者更理想的是允许我扩展用于生成查询的语法?
我知道我可以在任何地方创建名为@queues
,但后来我失去了生成的查询的便利性。我还想避免使用“主动”方法污染接口。
如果重要的话,我正在使用Hibernate 4.2作为我的JPA实现。
答案 0 :(得分:64)
这是一个老问题,你可能已经找到了答案。但是,所有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;
}
存储库执行的所有查询都将自动过滤掉“非活动”行。
答案 1 :(得分:61)
@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);
答案 2 :(得分:22)
根据易天明的回答,我用软覆盖的覆盖方法创建了CrudRepository实现:
if (Objects.equals(myEnum, ProductCategory.FOOD)) System.out.println("It's a food");
它可以与BasicEntity一起使用:
@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();
}
最终实体:
@MappedSuperclass
public abstract class BasicEntity {
@Column(name = "is_active")
private boolean isActive = true;
public abstract Long getId();
// isActive getters and setters...
}
答案 3 :(得分:9)
在当前版本(最高1.4.1)中,Spring Data JPA中没有专门的软删除支持。但是,我强烈建议您使用DATAJPA-307的功能分支,因为这是即将发布的功能。
要使用当前状态更新您使用的版本1.5.0.DATAJPA-307-SNAPSHOT并确保让它引入它需要工作的特殊Spring Data Commons版本。你应该能够关注我们必须看到的样本test case。
P.S。:一旦我们完成了该功能,我将更新问题。
答案 4 :(得分:2)
您可以从SimpleJpaRepository扩展并创建自己的自定义存储库,您可以在其中以通用方式定义软删除功能。
您还需要创建一个自定义JpaRepositoryFactoryBean并在主类中启用它。
您可以在此处查看我的代码https://github.com/dzinot/spring-boot-jpa-soft-delete
答案 5 :(得分:1)
如果您不想导入特定于hibernate的注释,我建议您使用数据库视图(或Oracle中的等效视图)。在mySQL 5.5中,如果过滤条件与active = 1
一样简单,则这些视图可以更新和插入创建或替换视图active_stuff作为select * from Stuff where active = 1;
这是否是一个好主意可能取决于您的数据库,但它在我的实现中很有用。
取消删除需要一个访问&#39; Stuff&#39;直接,但那么@Where
答案 6 :(得分:1)
我将 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
。它旨在限制您的项目中没有经验的程序员调用这些方法。相反,您可以实现自己的删除方法。
答案 7 :(得分:0)
我使用来自@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
}
答案 8 :(得分: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";
}
}