it-swarm.com.ru

Обработка мягкого удаления с помощью Spring JPA

У меня есть таблица Stuff определяется как ...

id, <fields>..., active

Активным является флаг мягкого удаления и всегда 1 или 0. В долгосрочной перспективе это может уйти в пользу исторической таблицы.

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

В коде мы всегда используем активные записи. Есть ли способ заставить Spring всегда добавлять условие active=1 к запросам, сгенерированным для этого репозитория? Или, в идеале, позвольте мне расширить грамматику, используемую для генерации запросов?

Я понимаю, что могу создавать с именем @queues везде, но тогда я теряю удобство сгенерированных запросов. Я также хочу избежать загрязнения интерфейса "активными" методами.

Я использую Hibernate 4.2 в качестве реализации JPA, если это имеет значение.

40
Andrew White

Это старый вопрос, и вы, вероятно, уже нашли ответ. НО, для всех программистов Spring/JPA/Hibernate, которые ищут ответ - 

Скажем, у вас есть сущность Собака:

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

Все запросы, выполняемые хранилищем, будут автоматически отфильтровывать «неактивные» строки.

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

На основе ответа 易天易 я создал реализацию 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...
}
20
vadim_shb

В текущих версиях (до 1.4.1) в Spring Data JPA нет специальной поддержки мягкого удаления. Тем не менее, я настоятельно рекомендую вам поиграть с веткой функций для DATAJPA-307 , поскольку в настоящее время эта функция работает над предстоящим выпуском. 

Чтобы использовать текущее состояние, обновите версию, которую вы используете, до версии 1.5.0.DATAJPA-307-SNAPSHOT и убедитесь, что вы включили специальную версию Spring Data Commons, необходимую для работы. Вы должны быть в состоянии следовать примеру контрольному примеру мы должны увидеть, как заставить все это работать.

П.С .: Я обновлю вопрос, как только мы закончим работу над этой функцией.

9
Oliver Drotbohm

Вы можете расширяться из SimpleJpaRepository и создавать свой собственный репозиторий, в котором вы можете определить функциональность мягкого удаления универсальным способом.

Вам также нужно создать пользовательский JpaRepositoryFactoryBean и включить его в своем основном классе.

Вы можете проверить мой код здесь https://github.com/dzinot/spring-boot-jpa-soft-delete

2
Dzinot

Я предлагаю вам использовать представление базы данных (или эквивалент в Oracle), если вы не хотите импортировать специфичные для спящего аннотации. В MySQL 5.5 эти представления могут быть обновляемыми и вставляемыми, если критерии фильтрации так же просты, как active = 1

создать или заменить представление active_stuff как select * from Stuff, где active = 1;

Является ли это хорошей идеей, вероятно, зависит от вашей базы данных, но она прекрасно работает в моей реализации.

Для удаления потребовалась дополнительная сущность, которая напрямую обращалась к '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