Spring Data: логика по умолчанию «не удалено» для автоматических запросов на основе методов при использовании политики мягкого удаления - PullRequest
1 голос
/ 09 мая 2019

Допустим, мы используем политику мягкого удаления: из хранилища ничего не удаляется; вместо этого для удаленного атрибута / столбца устанавливается значение true в записи / документе / чем угодно, чтобы сделать его «удаленным». Позже, только не удаленные записи должны быть возвращены методами запроса.

Давайте возьмем MongoDB в качестве примера (хотя JPA также интересен).

Для стандартных методов, определенных MongoRepository, мы можем расширить реализацию по умолчанию (SimpleMongoRepository), переопределить интересующие методы и заставить их игнорировать «удаленные» документы.

Но, конечно, мы также хотели бы использовать пользовательские методы запросов, такие как

List<Person> findByFirstName(String firstName)

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

List<person> findByFirstNameAndDeletedIsFalse(String firstName)

или писать запросы вручную с помощью @Query (добавляя одно и то же шаблонное условие о том, что «не удаляются» все время).

Здесь возникает вопрос : возможно ли автоматически добавить это «не удаленное» условие в любой сгенерированный запрос? Я не нашел ничего в документации.

Я смотрю на Spring Data (Mongo и JPA) 2.1.6.

Похожие вопросы

  1. Запрос перехватчика для spring-data-mongodb для мягких удалений здесь они предлагают аннотацию @Where Hibernate, которая работает только для JPA + Hibernate, и неясно, как ее переопределить, если вам все еще нужно доступ к удаленным элементам в некоторых запросах
  2. Обработка мягкого удаления с помощью Spring JPA здесь люди либо предлагают тот же подход на основе @Where, либо применимость решения ограничена уже определенными стандартными методами, а не пользовательскими.

1 Ответ

0 голосов
/ 18 мая 2019

Оказывается, что для Mongo (по крайней мере, для spring-data-mongo 2.1.6) мы можем взломать стандартную реализацию QueryLookupStrategy, чтобы добавить нужные «мягко удаленные документы, не видимые поведением искателей»:

public class SoftDeleteMongoQueryLookupStrategy implements QueryLookupStrategy {
    private final QueryLookupStrategy strategy;
    private final MongoOperations mongoOperations;

    public SoftDeleteMongoQueryLookupStrategy(QueryLookupStrategy strategy,
            MongoOperations mongoOperations) {
        this.strategy = strategy;
        this.mongoOperations = mongoOperations;
    }

    @Override
    public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
            NamedQueries namedQueries) {
        RepositoryQuery repositoryQuery = strategy.resolveQuery(method, metadata, factory, namedQueries);

        // revert to the standard behavior if requested
        if (method.getAnnotation(SeesSoftlyDeletedRecords.class) != null) {
            return repositoryQuery;
        }

        if (!(repositoryQuery instanceof PartTreeMongoQuery)) {
            return repositoryQuery;
        }
        PartTreeMongoQuery partTreeQuery = (PartTreeMongoQuery) repositoryQuery;

        return new SoftDeletePartTreeMongoQuery(partTreeQuery);
    }

    private Criteria notDeleted() {
        return new Criteria().orOperator(
                where("deleted").exists(false),
                where("deleted").is(false)
        );
    }

    private class SoftDeletePartTreeMongoQuery extends PartTreeMongoQuery {
        SoftDeletePartTreeMongoQuery(PartTreeMongoQuery partTreeQuery) {
            super(partTreeQuery.getQueryMethod(), mongoOperations);
        }

        @Override
        protected Query createQuery(ConvertingParameterAccessor accessor) {
            Query query = super.createQuery(accessor);
            return withNotDeleted(query);
        }

        @Override
        protected Query createCountQuery(ConvertingParameterAccessor accessor) {
            Query query = super.createCountQuery(accessor);
            return withNotDeleted(query);
        }

        private Query withNotDeleted(Query query) {
            return query.addCriteria(notDeleted());
        }
    }
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SeesSoftlyDeletedRecords {
}

Мы просто добавляем условие «а не удалено» во все запросы, если только @SeesSoftlyDeletedRecords не запрашивает, как этого избежать.

Затем нам нужна следующая инфраструктура для подключения нашей QueryLiikupStrategy реализации:

public class SoftDeleteMongoRepositoryFactory extends MongoRepositoryFactory {
    private final MongoOperations mongoOperations;

    public SoftDeleteMongoRepositoryFactory(MongoOperations mongoOperations) {
        super(mongoOperations);
        this.mongoOperations = mongoOperations;
    }

    @Override
    protected Optional<QueryLookupStrategy> getQueryLookupStrategy(QueryLookupStrategy.Key key,
            QueryMethodEvaluationContextProvider evaluationContextProvider) {
        Optional<QueryLookupStrategy> optStrategy = super.getQueryLookupStrategy(key,
                evaluationContextProvider);
        return optStrategy.map(this::createSoftDeleteQueryLookupStrategy);
    }

    private SoftDeleteMongoQueryLookupStrategy createSoftDeleteQueryLookupStrategy(QueryLookupStrategy strategy) {
        return new SoftDeleteMongoQueryLookupStrategy(strategy, mongoOperations);
    }
}

public class SoftDeleteMongoRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extends Serializable>
        extends MongoRepositoryFactoryBean<T, S, ID> {

    public SoftDeleteMongoRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
        super(repositoryInterface);
    }

    @Override
    protected RepositoryFactorySupport getFactoryInstance(MongoOperations operations) {
        return new SoftDeleteMongoRepositoryFactory(operations);
    }
}

Тогда нам просто нужно сослаться на фабричный компонент в аннотации @EnableMongoRepositories, подобной этой:

@EnableMongoRepositories(repositoryFactoryBeanClass = SoftDeleteMongoRepositoryFactoryBean.class)

Если требуется динамически определить, должен ли конкретный репозиторий быть «мягким-удаленным» или обычным «жестко удаляемым», мы можем проанализировать интерфейс репозитория (или класс домена) и решить, нужно ли нам изменить QueryLookupStrategy или нет.

Что касается JPA, этот подход не работает без переписывания (возможно, дублирования) существенной части кода в PartTreeJpaQuery.

...