提问者:小点点

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


假设我们使用软删除策略:存储中没有任何内容被删除;相反,记录/文档/任何内容上的“已删除”属性/列设置为true以使其“已删除”。稍后,查询方法只应返回未删除的条目。

让我们以MongoDB为例(alghough 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的查询拦截器用于软删除,这里他们建议Hibernate的@Anywhere注释仅适用于JPA Hibernate,如果您仍然需要在某些查询中访问已删除的项目,则不清楚如何覆盖它
  2. 使用Spring JPA处理软删除在这里,人们要么建议使用相同的基于@Anywhere的方法,要么解决方案的适用性受到已经定义的标准方法的限制,而不是自定义方法。

共1个答案

匿名用户

事实证明,对于Mongo(至少,对于spring-data-mongo2.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中的大部分代码,这种方法就无法工作。