Обработка мягких удалений с помощью 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, если это имеет значение.
Ответы
Ответ 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";
}
}