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
'programing' 카테고리의 다른 글
memcpy 구현을 제공하는 방법 (0) | 2023.07.31 |
---|---|
아약스 요청에서 X-Requested-With 헤더를 제거할 수 있습니까? (0) | 2023.07.31 |
제온을 위한 gcc 최적화 플래그? (0) | 2023.07.31 |
PHP 이미지 업로드 보안 확인 목록 (0) | 2023.07.31 |
요소를 전환하는 네이티브 jQuery 기능이 있습니까? (0) | 2023.07.31 |