假设我们使用软删除策略:存储中没有任何内容被删除;相反,记录/文档/任何内容上的“已删除”属性/列设置为true以使其“已删除”。稍后,查询方法只应返回未删除的条目。
让我们以MongoDB为例(alghough JPA也很有趣)。
对于MongoRepository
定义的标准方法,我们可以扩展默认实现(SimpleMongoRepository
),覆盖感兴趣的方法并使它们忽略“已删除”的文档。
但是,当然,我们也想使用自定义查询方法,例如
List<Person> findByFirstName(String firstName)
在软删除环境中,我们被迫做一些类似
List<person> findByFirstNameAndDeletedIsFalse(String firstName)
或者使用@Query
手动编写查询(始终添加关于“未删除”的相同样板条件)。
问题来了:是否可以自动将此“未删除”条件添加到任何生成的查询中?我在留档中没有找到任何内容。
我正在查看Spring Data(Mongo和JPA)2.1.6。
@Anywhere
注释仅适用于JPA Hibernate,如果您仍然需要在某些查询中访问已删除的项目,则不清楚如何覆盖它@Anywhere
的方法,要么解决方案的适用性受到已经定义的标准方法的限制,而不是自定义方法。
事实证明,对于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
中的大部分代码,这种方法就无法工作。