wake-up-neo.com

Umgang mit Soft-Deletes mit Spring JPA

Ich habe eine Tabelle Stuff definiert als ...

id, <fields>..., active

Aktiv ist das Soft-Delete-Flag und ist immer 1 oder 0. Langfristig kann dies zugunsten einer historischen Tabelle verschwinden.

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

Im Code verwenden wir immer aktive Datensätze. Gibt es eine Möglichkeit, Spring dazu zu bringen, immer eine active=1-Bedingung an Abfragen anzuhängen, die für dieses Repository generiert werden? Oder lassen Sie mich im Idealfall die Grammatik erweitern, mit der die Abfragen generiert werden?

Ich verstehe, dass ich überall den Namen @queues erstellen kann, aber dann verliere ich die Bequemlichkeit der generierten Abfragen. Ich möchte auch vermeiden, die Schnittstelle mit "aktiven" Methoden zu verschmutzen.

Ich verwende Hibernate 4.2 als meine PPV-Implementierung, wenn dies wichtig ist.

40
Andrew White

Dies ist eine alte Frage und Sie haben wahrscheinlich bereits die Antwort gefunden. ABER, für alle Spring/JPA/Hibernate-Programmierer auf der Suche nach Antworten - 

Angenommen, Sie haben eine Entität Hund:

 @Entity
 public class Dog{

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

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

und ein Repository:

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

Alles, was Sie tun müssen, ist die @Where-Annotation auf Entitätsebene hinzuzufügen.

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

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

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

Alle vom Repository ausgeführten Abfragen filtern automatisch die "nicht aktiven" Zeilen.

62
Shay Elkayam

@Where(clause="is_active=1") ist nicht der beste Weg, um Soft-Delete mit Federdaten jpa zu behandeln.

Erstens funktioniert es nur mit dem Hibernate-Gerät.

Zweitens können Sie niemals weich gelöschte Objekte mit Federdaten abrufen.

Meine Lösung wird durch Federdaten bereitgestellt. #{#entityName} expression kann in einem generischen Repository verwendet werden, das den konkreten Namen des Entitätstyps darstellt.

Und der Code wird so sein:

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

Basierend auf 易天明 Antwort habe ich eine CrudRepository-Implementierung mit Überschreibungsmethoden für Soft-Delete erstellt:

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

Es könnte mit BasicEntity verwendet werden:

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

  public abstract Long getId();

  // isActive getters and setters...
}

Und letzte Entität:

@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

In aktuellen Versionen (bis zu 1.4.1) gibt es keine dedizierte Unterstützung für Soft-Löschvorgänge in Spring Data JPA. Ich empfehle Ihnen jedoch dringend, mit dem Feature-Zweig für DATAJPA-307 zu spielen, da dies ein Feature ist, an dem aktuell die neue Version gearbeitet wird. 

Um den aktuellen Status zu verwenden, aktualisieren Sie die Version, die Sie verwenden, auf 1.5.0.DATAJPA-307-SNAPSHOT, und stellen Sie sicher, dass Sie die Spring Data Commons-Version verwenden, die sie benötigt. Sie sollten in der Lage sein, dem Beispiel test case zu folgen. Wir müssen sehen, wie das Zeug zum Laufen gebracht wird.

P .: Ich werde die Frage aktualisieren, sobald wir mit der Funktion fertig sind.

9
Oliver Drotbohm

Sie können das SimpleJpaRepository erweitern und ein eigenes benutzerdefiniertes Repository erstellen, in dem Sie die Soft-Delere-Funktionalität auf allgemeine Weise definieren können.

Sie müssen außerdem eine benutzerdefinierte JpaRepositoryFactoryBean erstellen und diese in Ihrer Hauptklasse aktivieren.

Sie können meinen Code hier überprüfen https://github.com/dzinot/spring-boot-jpa-soft-delete

2
Dzinot

Ich empfehle Ihnen, eine Datenbankansicht (oder eine entsprechende Entsprechung in Oracle) zu verwenden, wenn Sie bestimmte Anmerkungen nicht in den Ruhezustand importieren möchten. In mySQL 5.5 können diese Ansichten aktualisierbar und einfügbar sein, wenn die Filterkriterien so einfach sind wie aktiv = 1

ansicht active_stuff als select * from Stuff erstellen oder ersetzen, wenn active = 1;

Ob dies eine gute Idee ist, hängt wahrscheinlich von Ihrer Datenbank ab, funktioniert aber bei meiner Implementierung hervorragend.

Für das Wiederherstellen war eine zusätzliche Entität erforderlich, die direkt auf "Stuff" zugegriffen hat, dann aber @Where

1
Chanoch

Ich habe so ein Repository definiert

@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

Ich habe die Lösung von @vadim_shb verwendet, um JpaRepository zu erweitern. Hier ist mein Code in Scala. Befürworte seine Antwort, nicht diese. Ich wollte nur ein Beispiel zeigen, das Paging und Sortierung beinhaltet. 

Paging und Sortierung funktionieren hervorragend in Verbindung mit den Abfragekommentaren. Ich habe nicht alles getestet, aber für diejenigen, die nach Paging und Sortierung fragen, scheinen sie auf der Query-Anmerkung zu liegen. Ich werde dies weiter aktualisieren, wenn Probleme behoben werden. 

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