web-dev-qa-db-ja.com

Spring JPAでのソフト削除の処理

次のように定義されたテーブルStuffがあります...

id, <fields>..., active

アクティブはソフト削除フラグであり、常に1または0です。長期的には、これは履歴表に有利になる可能性があります。

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

コードでは、常にアクティブなレコードを使用します。 Springにこのリポジトリ用に生成されたクエリに常にactive=1条件を追加する方法はありますか?または、理想的には、クエリの生成に使用される文法を拡張できるようにしますか?

名前付き@queuesをどこでも作成できることは理解していますが、生成されたクエリの利便性が失われます。また、「アクティブな」メソッドでインターフェイスを汚染することも避けたいです。

それが重要であれば、JPA実装としてHibernate 4.2を使用しています。

43
Andrew White

これは古い質問であり、おそらくあなたはすでに答えを見つけました。しかし、答えを求めているすべてのSpring/JPA/Hibernateプログラマーのために-

エンティティーDogがあるとします:

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

リポジトリによって実行されるすべてのクエリは、「非アクティブ」行を自動的に除外します。

69
Shay Elkayam

@Where(clause="is_active=1")は、スプリングデータjpaでソフト削除を処理する最良の方法ではありません。

まず、Hibernate実装でのみ機能します。

第二に、スプリングデータでソフト削除されたエンティティをフェッチすることはできません。

私のソリューションは、春のデータによって提供されます。 #{#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); 
72
易天明

易天明の回答に基づいて、ソフト削除のオーバーライドされたメソッドを使用して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...
}
25
vadim_shb

現在のバージョン(1.4.1まで)では、Spring Data JPAのソフト削除の専用サポートはありません。ただし、 DATAJPA-307 の機能ブランチを使用することを強くお勧めします。これは、今後のリリースで現在機能している機能です。

現在の状態を使用するには、使用するバージョンを1.5.0.DATAJPA-307-SNAPSHOTに更新し、動作に必要な特別なSpring Data Commonsバージョンを確実に取り込むようにします。サンプルに従うことができるはずです テストケース これらを機能させる方法を確認する必要があります。

追伸:この機能の作業が終了したら、質問を更新します。

10
Oliver Drotbohm

SimpleJpaRepositoryから拡張し、独自のカスタムリポジトリを作成して、一般的な方法でソフト削除機能を定義できます。

また、カスタムJpaRepositoryFactoryBeanを作成し、メインクラスで有効にする必要があります。

ここで私のコードを確認できます https://github.com/dzinot/spring-boot-jpa-soft-delete

2
Dzinot

Hibernate固有の注釈をインポートしたくない場合は、データベースビュー(またはOracleの同等物)を使用することをお勧めします。 mySQL 5.5では、フィルター基準がactive = 1のように単純な場合、これらのビューは更新可能および挿入可能です。

active = 1のStuffからselect *としてビューactive_stuffを作成または置換します。

これが良いアイデアであるかどうかはおそらくデータベースに依存しますが、私の実装ではうまく機能します。

削除を解除するには、「スタッフ」に直接アクセスする追加のエンティティが必要でしたが、@ Where

1
Chanoch

このようなリポジトリを定義しました

@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

@vadim_shbのソリューションを使用してJpaRepositoryを拡張しました。これがScalaのコードです。これではなく、彼の答えに賛成票を投じてください。ページングとソートを含む例を示したかっただけです。

ページングと並べ替えは、クエリの注釈と連携して優れた機能を発揮します。すべてをテストしたわけではありませんが、ページングとソートについて尋ねる人にとっては、Queryアノテーションの上に階層化されているようです。問題を解決したら、これをさらに更新します。

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