В текущем проекте у меня есть AuditableEntity
абстрактный класс, от которого наследуется большинство других сущностей.AuditableEntity
указанные столбцы createdAt
, updatedAt
(со значениями, автоматически заполняемыми аннотациями @CreatedDate
& @LastModifiedDate
Spring), а также столбец deletedAt
, который реализован вручную из-за того, что Spring не поддерживает (длянасколько мне известно) софт-удаление из коробки.AuditableEntity
выглядит следующим образом:
@Getter
@Setter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@JsonIgnoreProperties(
value = {"createdAt", "updatedAt", "deletedAt"},
allowGetters = true
)
public abstract class AuditableEntity<T> extends IdentifiableEntity<T> implements Serializable {
@CreatedDate
@Column(name = "dba_ins_ymd", nullable = false, updatable = false)
private Instant createdAt;
@LastModifiedDate
@Column(name = "dba_upd_ymd", nullable = false)
private Instant updatedAt;
@Column(name = "dba_del_ymd")
private Instant deletedAt;
}
Затем, благодаря другому ответу StackOverflow, у меня есть интерфейс SoftDeleteJpaRepository
(унаследованный от JpaRepository
), который автоматически игнорирует удаленные записи, например так:
@NoRepositoryBean
public interface SoftDeleteJpaRepository<T extends AuditableEntity<ID>, ID> extends JpaRepository<T, ID> {
@Override
@Transactional(readOnly = true)
@Query("select e from #{#entityName} e where e.deletedAt is null")
List<T> findAll();
@Override
@Transactional(readOnly = true)
@Query("select e from #{#entityName} e where e.deletedAt is null order by ?1")
List<T> findAll(Sort sort);
@Override
@Transactional(readOnly = true)
@Query("select e from #{#entityName} e where e.id in ?1 and e.deletedAt is null")
List<T> findAllById(Iterable<ID> ids);
@Override
default void deleteInBatch(Iterable<T> entities) {
throw new NotImplementedException("Not implemented yet");
}
@Override
default void deleteAllInBatch() {
throw new NotImplementedException("Not implemented yet");
}
@Override
@Transactional(readOnly = true)
@Query("select e from #{#entityName} e where e.id = ?1 and e.deletedAt is null")
T getOne(ID id);
@Override
@Transactional(readOnly = true)
@Query("select e from #(#entityName) e where e.deletedAt is null")
default Page<T> findAll(Pageable pageable) {
throw new NotImplementedException("Not implemented yet");
}
@Override
@Transactional(readOnly = true)
@Query("select e from #{#entityName} e where e.id = ?1 and e.deletedAt is null")
Optional<T> findById(ID id);
@Override
default boolean existsById(ID id) {
return getOne(id) != null;
}
@Override
@Transactional(readOnly = true)
@Query("select count(e) from #{#entityName} e where e.deletedAt is null")
long count();
@Override
@Transactional
@Query("update #{#entityName} e set e.deletedAt = current_timestamp where e.id = ?1")
@Modifying
void deleteById(ID id);
@Override
@Transactional
default void delete(T entity) {
deleteById(entity.getId());
}
@Override
@Transactional
default void deleteAll(Iterable<? extends T> entities) {
entities.forEach(entity -> deleteById(entity.getId()));
}
@Override
@Transactional
@Query("update #{#entityName} e set e.deletedAt = current_timestamp")
@Modifying
void deleteAll();
@Query("select e from #{#entityName} e where e.deletedAt is not null")
@Transactional(readOnly = true)
List<T> findSoftDeleted();
}
По сути, смесь измененных аннотаций @Query
, а также некоторых реализаций default
- пока что все хорошо.
Наконец, вот вопрос: для некоторых из моихВ некоторых случаях мне также нужно использовать запрос за примером, поэтому мне нужно аналогичным образом настроить методы <S extends T> List<S> findAll(Example<S> example)
и <S extends T> Page<S> findAll(Example<S> example, Pageable pageable)
, чтобы они автоматически опускали записи, содержащие любое значение в столбце deletedAt
.Но, честно говоря, я не знаю, с чего начать и как это сделать.
Я попытался посмотреть, как Spring реализует эти методы, но все, что я могу найти, - это имя метода в интерфейсе QueryByExampleExecutor
.IDEA показывает, что единственная реализация этого метода - SimpleJpaRepository
, но является ли это правильным местом для вдохновения?Как будет выглядеть общая версия этих двух методов (которая будет автоматически игнорировать записи, удаленные без ошибок)?