Spring Data:使用软删除策略时,基于方法的自动查询的默认“未删除”逻辑

时间:2019-05-09 14:17:31

标签: java spring-data-jpa spring-data spring-data-mongodb

比方说,我们使用软删除策略:什么都不会从存储中删除;而是在记录/文档/任何要使其“删除”的属性上将“已删除”属性/列设置为true。稍后,查询方法只应返回未删除的条目。

让我们以MongoDB为例(尽管JPA也很有趣)。

对于MongoRepository定义的标准方法,我们可以扩展默认实现(SimpleMongoRepository),覆盖感兴趣的方法,并使它们忽略“已删除”的文档。

但是,当然,我们也想使用自定义查询方法,例如

List<Person> findByFirstName(String firstName)

在软删除环境中,我们被迫做类似的事情

List<person> findByFirstNameAndDeletedIsFalse(String firstName)

或使用@Query手动编写查询(始终添加关于“未删除”的相同样板条件)。

这是一个问题:是否可以将此“未删除”条件自动添加到任何生成的查询中?我没有在文档中找到任何内容。

我正在查看Spring Data(Mongo和JPA)2.1.6。

类似的问题

  1. Query interceptor for spring-data-mongodb for soft deletions在这里,他们建议使用Hibernate的@Where批注,该批注仅适用于JPA + Hibernate,如果您仍需要访问某些查询中的已删除项,则不清楚如何覆盖它。
  2. Handling soft-deletes with Spring JPA在这里,人们要么建议使用基于@Where的相同方法,要么解决方案的适用性受到已经定义的标准方法的限制,而不是自定义方法。

1 个答案:

答案 0 :(得分:0)

事实证明,对于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注释中引用工厂bean,如下所示:

@EnableMongoRepositories(repositoryFactoryBeanClass = SoftDeleteMongoRepositoryFactoryBean.class)

如果需要动态确定特定存储库是需要“软删除”还是常规的“硬删除”存储库,则可以对存储库接口(或域类)进行内部检查,并决定是否需要更改QueryLookupStrategy

对于JPA,如果不重写(可能重复)PartTreeJpaQuery中的大部分代码,这种方法将无法工作。