programing

Spring JPA로 소프트 삭제 처리

lastcode 2023. 7. 31. 21:25
반응형

Spring JPA로 소프트 삭제 처리

자리가 있습니다Stuff...로 정의됨

id, <fields>..., active

활성은 소프트 삭제 플래그이며 항상1또는0장기적으로 이것은 역사적인 표에 유리하게 사라질 수 있습니다.

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

코드에서는 항상 활성 레코드를 사용합니다.스프링이 항상 추가할 수 있는 방법이 있습니까?active=1이 리포지토리에 대해 생성된 쿼리에 대한 조건?아니면 더 이상적으로 쿼리를 생성하는 데 사용되는 문법을 확장할 수 있습니까?

이름을 지정하여 생성할 수 있음을 알고 있습니다.@queues생성된 쿼리의 편의성을 잃게 됩니다.또한 "활성" 방법으로 인터페이스를 오염시키지 않기를 원합니다.

저는 Hibernate 4.2를 JPA 구현체로 사용하고 있습니다.

@Where(clause="is_active=1")스프링 데이터 jpa로 소프트 삭제를 처리하는 가장 좋은 방법은 아닙니다.

첫째, 최대 절전 모드 구현에서만 작동합니다.

둘째, 스프링 데이터로 소프트 삭제 엔티티를 가져올 수 없습니다.

나의 해결책은 스프링 데이터에 의해 제공됩니다.#{#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); 

이것은 오래된 질문이고, 여러분은 아마도 이미 답을 찾았을 것입니다.하지만, 봄/J에는해답을 찾는 PA/하이버네이트 프로그래머들 -

실체가 있다고 치자 개:

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

리포지토리에서 수행하는 모든 쿼리는 "비활성" 행을 자동으로 필터링합니다.

天明의 답변을 바탕으로 소프트 삭제를 위한 재정의된 메서드를 사용하여 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...
}

현재 버전(최대 1.4.1)에서는 Spring Data JPA에서 소프트 삭제를 전용으로 지원하지 않습니다.그러나 DATAJPA-307의 기능 분기는 현재 출시 예정인 기능이므로 이 기능 분기를 사용하는 것이 좋습니다.

현재 상태를 사용하려면 사용하는 버전을 1.5.0으로 업데이트합니다.DATAJPA-307-SNAP샷을 사용하여 작동에 필요한 특수 Spring Data Commons 버전을 가져올 수 있도록 합니다.당신은 샘플 테스트 사례를 따라갈 수 있어야 합니다. 우리는 그것들이 어떻게 작동하는지 봐야 합니다.

추신: 기능에 대한 작업이 끝나면 질문을 업데이트하겠습니다.

저는 vdshb에서 제공하는 솔루션을 새로운 버전의 스프링 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삭제 방법에서.당신의 프로젝트에서 경험이 없는 프로그래머가 이러한 메소드를 호출하는 것을 제한하기 위해 만들어졌습니다.대신 고유한 삭제 방법을 구현할 수 있습니다.

SimpleJpaRepository에서 확장하여 일반적인 방법으로 softdere 기능을 정의할 수 있는 사용자 정의 저장소를 만들 수 있습니다.

또한 사용자 지정 JpaRepositoryFactoryBean을 생성하고 기본 클래스에서 이를 활성화해야 합니다.

당신은 여기에서 제 코드를 확인할 수 있습니다. https://github.com/dzinot/spring-boot-jpa-soft-delete

저는 @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
}

최대 절전 모드의 특정 주석을 가져오지 않으려면 데이터베이스 보기(또는 Oracle에서 동등한 것)를 사용하는 것이 좋습니다.mySQL 5.5에서 필터 기준이 활성=1만큼 간단한 경우 이러한 보기를 업데이트하고 삽입할 수 있습니다.

active=1인 Stuff에서 *를 선택하여 view active_stuff를 만들거나 바꿉니다.

이것이 좋은 생각인지 아닌지는 아마도 당신의 데이터베이스에 달려있지만, 그것은 나의 구현에 매우 효과적입니다.

삭제를 취소하려면 'Stuff'에 직접 액세스하는 추가 엔티티가 필요하지만 @Where도 마찬가지입니다.

저는 이런 저장소를 정의했습니다.

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

언급URL : https://stackoverflow.com/questions/19323557/handling-soft-deletes-with-spring-jpa

반응형