Подтвердить что ты не робот

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

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

id, <fields>..., active

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

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

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

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

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

4b9b3361

Ответ 1

Это старый вопрос, и вы, вероятно, уже нашли ответ. НО, для всех программистов 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;
}

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

Ответ 2

@Where(clause="is_active=1") - не лучший способ обработать мягкое удаление с помощью данных весны jpa.

Во-первых, он работает только с hibernate.

Во-вторых, вы никогда не сможете получить мягко удаленные объекты с данными пружины.

Мое решение предоставлено весенними данными. Выражение #{#entityName} можно использовать в общем хранилище, представляющем конкретное имя типа сущности.

И код будет таким:

//Override CrudRepository or PagingAndSortingRepository 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); 

Ответ 3

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

Ответ 4

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

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

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

Ответ 5

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

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

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

Ответ 6

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

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

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

Undeleting требуется дополнительный объект, который напрямую обратился к "Stuff", но затем будет @Where

Ответ 7

Я использовал решение из @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
}

Ответ 8

Я определил репозиторий, как это

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