Java 用springjpa处理软删除
我有一个表的东西定义为Java 用springjpa处理软删除,java,spring,jpa,spring-data,spring-data-jpa,Java,Spring,Jpa,Spring Data,Spring Data Jpa,我有一个表的东西定义为 id, <fields>..., active 在代码中,我们总是使用活动记录。有没有办法让Spring总是向为此存储库生成的查询附加active=1条件?或者更理想地允许我扩展用于生成查询的语法 我知道我可以在任何地方创建命名的@队列,但这样我就失去了生成查询的便利性。我还希望避免使用主动方法污染接口 如果有必要的话,我将使用Hibernate 4.2作为我的JPA实现。在1.4.1之前的当前版本中,Spring Data JPA中没有专门支持软删除。但是
id, <fields>..., active
在代码中,我们总是使用活动记录。有没有办法让Spring总是向为此存储库生成的查询附加active=1条件?或者更理想地允许我扩展用于生成查询的语法
我知道我可以在任何地方创建命名的@队列,但这样我就失去了生成查询的便利性。我还希望避免使用主动方法污染接口
如果有必要的话,我将使用Hibernate 4.2作为我的JPA实现。在1.4.1之前的当前版本中,Spring Data JPA中没有专门支持软删除。但是,我强烈建议您使用功能分支,因为这是目前正在为即将发布的版本开发的功能 要使用当前状态,请将您使用的版本更新为1.5.0.DATAJPA-307-SNAPSHOT,并确保让它使用它需要的特殊Spring数据共享版本。你应该能够按照样品,我们必须看看如何让这些东西工作
附言:一旦我们完成了这个功能,我会更新这个问题。这是一个老问题,你可能已经找到了答案。但是,对于所有正在寻找答案的Spring/JPA/Hibernate程序员来说— 假设你有一只实体狗:
@Entity
public class Dog{
......(fields)....
@Column(name="is_active")
private Boolean active;
}
和存储库:
public interface DogRepository extends JpaRepository<Dog, Integer> {
}
存储库执行的所有查询将自动过滤掉非活动行。如果不想导入特定于hibernate的注释,我建议您在Oracle中使用数据库视图或等效视图。在mySQL 5.5中,如果过滤条件像active=1一样简单,那么这些视图是可更新和可插入的 创建或替换view active_stuff作为select*from stuff,其中active=1 这是否是一个好主意可能取决于您的数据库,但它在我的实现中非常有效 取消删除需要一个直接访问“Stuff”的附加实体,但@Where@Whereclause=is_active=1并不是使用spring数据jpa处理软删除的最佳方式 首先,它只适用于hibernate实现 其次,您永远无法使用spring数据获取软删除的实体 我的解决方案由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);
您可以从SimpleParepository进行扩展,并创建自己的自定义存储库,在其中可以以通用方式定义soft delere功能 您还需要创建一个自定义JpaRepositoryFactoryBean,并在主类中启用它
您可以根据以下内容在此处检查我的代码易天明 答:我已经创建了CRUDepository实现,其中包含用于软删除的重写方法:
@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();
}
最终实体:
@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中的代码。投票支持他的答案,而不是这个。只是想展示一个包括分页和排序的示例 分页和排序与查询注释结合使用效果很好。我并没有测试所有这些,但对于那些询问分页和排序的人来说,它们似乎是在查询注释之上分层的。如果我解决了任何问题,我将进一步更新此内容
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提供的解决方案改编为较新版本的SpringJPA存储库。还添加了一些可能出现在企业应用程序中的公共字段 基本实体: 基本存储库:
public interface DogRepository extends JpaRepository<Dog, Integer> {
}
正如您可能看到的,我从delete方法抛出UnsupportedOperationException。这是为了限制项目中没有经验的程序员调用这些方法。相反,您可以实现自己的删除方法。期待它。奥利弗,你在那儿干得真棒!这使它成为发行版了吗?@VineetBhatia,2020年4月仍然没有关于何时发行的计划:基于8年过去的Jira ticket8和功能没有更新-仍处于设计阶段FacePalm我相信这是一个以Hibernate为中心的答案。如果您有一些文档显示@Where是JPA或Spring的特性,请共享它们。是的,这是一个Hibernate解决方案。我在答案的第一段提到了这一点,但显然我不是100%清楚。所以-此解决方案使用Hibernate的@Where注释。对不起,谢谢你的更正。顺便说一下,问这个问题的人使用Hibernate4.2,这是我给出符合他需要的答案的主要原因易天明 回答它更完整在这种情况下你怎么做?从逻辑上讲,所有JPA删除都应该是更新查询。相反,使用此方法,它们将成为从表中删除,其中is_active=1My测试表明,在复杂的父子表中,此软删除方法必须通过实际编码将其is_active设置为0,将软删除传播到子表或子表。否则,查询子/子表
很容易出错,例如,关于父表中的总计。因此,对于复杂的表,代码很难维护。听起来不错吧?谢谢你的意见!向上投票你的答案,不确定为什么它不在顶部,因为它以最JPA/Spring友好的方式回答了这个问题。谢谢。如果e.id不是id,而是userId或accountId等,那么这仍然有效吗?或者我需要将此方法添加到我的所有存储库中吗?spring数据中的SpEL现在不支持表示id的变量。因此,如果您的实体id不是命名id,请重写这些方法。我认为,您的大多数实体都将被称为id。如果开发人员选择使用Spring data@Query注释编写jpql怎么办?如果我有一个自定义方法,即findByLastNameString lastName或findByStatusboolean status或FindByAge,会发生什么?在创建定制的BaseJParePostRoy之后,我扩展了JParePostory,使用下面提到的示例,我可以删除实体,这意味着在findAll或findByIdint id中只能找到那些删除标志为false的实体。但它不适用于其他定制的FindByOtherProeProperty,这将返回所有其他实体的delete标志也为true的实体。我是否遗漏了一些可以让它们工作的东西?是否可以将其与PagingAndSortingRepository集成?例如,如何覆盖PageFindAllPageable pageable?使用与findAll相同的查询。分页和排序似乎是在查询注释之上分层的。请参阅下面我的答案以获取示例。您将如何以这种方式处理自定义hibernate查询?前任。findByIdAndName@MilanMiljus,您可以手动向其添加AndIsActive,或者尝试实现BeanDefinitionPostProcessor或类似的功能来自动添加它,并创建代理接口来隐藏它。我个人更喜欢更简单、更高效/可扩展的解决方案,所以我根本不会选择JPA/Hibernate/SpringData;
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";
}
}
@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);
}
}