it-swarm.dev

Obsługa miękkich kasowań za pomocą Spring JPA

Mam tabelę Stuff zdefiniowaną jako ...

id, <fields>..., active

Aktywna jest flagą łagodnego usuwania i zawsze jest kodem 1 lub 0. Długoterminowo może to odejść na korzyść historycznego stołu.

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

W kodzie używamy zawsze aktywnych rekordów. Czy jest jakiś sposób, aby Spring zawsze dołączał warunek active=1 do zapytań generowanych dla tego repozytorium? Lub bardziej idealnie pozwala mi rozszerzyć gramatykę używaną do generowania zapytań?

Rozumiem, że wszędzie mogę utworzyć nazwę @queues, ale wtedy tracę wygodę generowanych zapytań. Chcę także unikać zanieczyszczania interfejsu metodami „aktywnymi”.

Używam Hibernate 4.2 jako mojej implementacji JPA, jeśli ma to znaczenie.

40
Andrew White

To stare pytanie i prawdopodobnie już znalazłeś odpowiedź. ALE, dla wszystkich programistów Spring/JPA/Hibernate szukających odpowiedzi - 

Powiedzmy, że masz jednostkę Dog:

 @Entity
 public class Dog{

 ......(fields)....        

 @Column(name="is_active")
 private Boolean active;
 }

i repozytorium:

public interface DogRepository extends JpaRepository<Dog, Integer> {
} 

Wszystko, co musisz zrobić, to dodać adnotację @Where na poziomie jednostki, co spowoduje:

@Entity
@Where(clause="is_active=1")
public class Dog{

......(fields)....        

@Column(name="is_active")
private Boolean active;
}

Wszystkie zapytania wykonywane przez repozytorium automatycznie odfiltrują „nieaktywne” wiersze.

62
Shay Elkayam

@Where(clause="is_active=1") nie jest najlepszym sposobem na łagodne usuwanie za pomocą danych wiosna jpa.

Po pierwsze, działa tylko z narzędziem hibernacyjnym.

Po drugie, nigdy nie można pobrać usuniętych elementów z danymi sprężynowymi.

Moim rozwiązaniem są dane wiosenne. Wyrażenie #{#entityName} może być użyte w ogólnym repozytorium reprezentującym konkretną nazwę typu jednostki.

I kod będzie taki:

//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); 
54
易天明

Na podstawie odpowiedzi 易天明 stworzyłem implementację CrudRepository z nadpisanymi metodami miękkiego usuwania:

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

Może być używany z BasicEntity:

@MappedSuperclass
public abstract class BasicEntity {
  @Column(name = "is_active")
  private boolean isActive = true;

  public abstract Long getId();

  // isActive getters and setters...
}

I ostateczny podmiot:

@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...
}
20
vadim_shb

W obecnych wersjach (do 1.4.1) nie ma dedykowanej obsługi miękkich kasowań w Spring Data JPA. Gorąco zachęcam jednak do gry w gałęzi funkcji dla DATAJPA-307 ponieważ jest to funkcja, nad którą obecnie pracuje się w nadchodzącym wydaniu. 

Aby użyć aktualnego stanu aktualizacji, użyj wersji 1.5.0.DATAJPA-307-SNAPSHOT i upewnij się, że pozwala ona na ściągnięcie specjalnej wersji Spring Data Commons, która musi działać. Powinieneś być w stanie śledzić przykładowy testowy przypadek musimy zobaczyć, jak to działa.

P.S: Zaktualizuję pytanie po zakończeniu pracy nad tą funkcją.

9
Oliver Drotbohm

Możesz rozszerzyć z SimpleJpaRepository i utworzyć własne niestandardowe repozytorium, w którym możesz zdefiniować miękką funkcję delere w ogólny sposób.

Musisz także utworzyć niestandardowy JpaRepositoryFactoryBean i włączyć go w swojej głównej klasie.

Możesz sprawdzić mój kod tutaj https://github.com/dzinot/spring-boot-jpa-soft-delete

2
Dzinot

Proponuję użyć widoku bazy danych (lub odpowiednika w Oracle), jeśli nie chcesz importować hibernacji określonych adnotacji. W mySQL 5.5 widoki te można aktualizować i wstawiać, jeśli kryteria filtrowania są tak proste, jak aktywne = 1

utwórz lub zamień widok active_stuff jako select * from Stuff, gdzie active = 1;

To, czy jest to dobry pomysł, prawdopodobnie zależy od twojej bazy danych, ale działa świetnie w mojej implementacji.

Cofnięcie usunięcia wymagało dodatkowej jednostki, która uzyskała bezpośredni dostęp do „Stuff”, ale wtedy także @Uhere

1
Chanoch

Zdefiniowałem takie repozytorium

@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";
    }
}
0
mu.xufan

Użyłem rozwiązania z @vadim_shb do rozszerzenia JpaRepository i oto mój kod w Scali. Podnieś jego odpowiedź, nie tę. Chciałem tylko pokazać przykład, który obejmuje stronicowanie i sortowanie. 

Przywoływanie i sortowanie działają świetnie w połączeniu z adnotacjami zapytania. Nie testowałem tego wszystkiego, ale dla tych, którzy pytają o stronicowanie i sortowanie, wydają się być nakładane na adnotację Query. Zaktualizuję to dalej, jeśli rozwiążę wszelkie problemy. 

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
}
0
JMDenver