简体   繁体   English

Spring mongo软删除

[英]Spring mongo soft delete

I need to implement soft delete functionality(Maintain a boolean field in table and filter all query based on this).我需要实现软删除功能(在表中维护一个 boolean 字段并根据此过滤所有查询)。 Below link has solution for hibernate only.下面的链接只有 hibernate 的解决方案。 Handling soft-deletes with Spring JPA 使用 Spring JPA 处理软删除

Since my application is very old, I don't want to change each existing query.由于我的应用程序很旧,我不想更改每个现有查询。 I am looking for solution like one place change in spring data classes.我正在寻找解决方案,例如 spring 数据类中的一处更改。 Spring mongo data version: 1.5.0.RELEASE Spring mongo数据版本:1.5.0.RELEASE

Add Boolean Field active to every class which is mapped with Collection
set the same true for all valid Documents  and false for non valid documnets 
private Boolean active = Boolean.TRUE;

and can chnage your Query to 
Long countByActiveTrueAndAccountStatusNot(AccountStatus status);

First Step.第一步。 Override default methods like as findAll(), findById(), exists()... .覆盖默认方法,如findAll(), findById(), exists()... For this you should override mongoTemplate, it simple).为此,您应该覆盖 mongoTemplate,这很简单)。

  1. Add to your entities field "deletedAt":添加到您的实体字段“deletedAt”:
@Document("costAreas")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder
public class User{
    @Id
    String id;
    String name;
    LocalDateTime deletedAt;
}

PS: Filed "deletedAt" contains the date of deletion (if this field is null then document wasn't deleted). PS:归档的“deletedAt”包含删除日期(如果此字段为 null,则文档未被删除)。

  1. Create CustomMongoTemplate:创建 CustomMongoTemplate:
import com.mongodb.client.MongoClient;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Objects;

public class CustomMongoTemplate extends MongoTemplate {
    public CustomMongoTemplate(MongoTemplate mongoTemplate) {
        super(mongoTemplate.getMongoDatabaseFactory());
    }

    public CustomMongoTemplate(MongoClient mongoClient, String databaseName) {
        super(mongoClient, databaseName);
    }

    public CustomMongoTemplate(MongoDatabaseFactory mongoDbFactory) {
        super(mongoDbFactory);
    }

    public CustomMongoTemplate(MongoDatabaseFactory mongoDbFactory, MongoConverter mongoConverter) {
        super(mongoDbFactory, mongoConverter);
    }

    @Override
    public <T> List<T> find(Query query, Class<T> entityClass, String collectionName) {
        Assert.notNull(query, "Query must not be null!");
        Assert.notNull(collectionName, "CollectionName must not be null!");
        Assert.notNull(entityClass, "EntityClass must not be null!");
        query.addCriteria(Criteria.where("deletedAt").exists(Boolean.FALSE));

        return super.find(query, entityClass, collectionName);
    }

    @Nullable
    @Override
    public <T> T findById(Object id, Class<T> entityClass, String collectionName) {
        T t = super.findById(id, entityClass, collectionName);

        try {
            Field field = entityClass.getDeclaredField("deletedAt");
            field.setAccessible(Boolean.TRUE);
            if (Objects.nonNull(field.get(t))) {
                return null;
            }
        } catch (NoSuchFieldException | IllegalAccessException ignored) {
        }

        return t;
    }

    @Nullable
    @Override
    public <T> T findOne(Query query, Class<T> entityClass, String collectionName) {
        Assert.notNull(query, "Query must not be null!");
        Assert.notNull(entityClass, "EntityClass must not be null!");
        Assert.notNull(collectionName, "CollectionName must not be null!");
        query.addCriteria(Criteria.where("deletedAt").exists(Boolean.FALSE));

        return super.findOne(query, entityClass, collectionName);
    }

    @Override
    @SuppressWarnings("ConstantConditions")
    public boolean exists(Query query, @Nullable Class<?> entityClass, String collectionName) {
        if (query == null) {
            throw new InvalidDataAccessApiUsageException("Query passed in to exist can't be null");
        }

        query.addCriteria(Criteria.where("deletedAt").exists(Boolean.FALSE));

        return super.exists(query, entityClass, collectionName);
    }

// You can also override ```delete()``` method, but I decided to not to do this

//  Maybe here should add other methods: count, findAndModify and ect. It depends which methods you going to use.
}
  1. Then create Bean in configuration class:然后在配置class创建Bean:
@Configuration
public class MyConfiguration {
//...
    @Bean(name = "mongoTemplate")
    CustomMongoTemplate customMongoTemplate(MongoDatabaseFactory databaseFactory, MappingMongoConverter converter) {
        return new CustomMongoTemplate(databaseFactory, converter);
    }
//...
}
  1. And allow Spring to override default MongoTemplate bean.并允许 Spring 覆盖默认的 MongoTemplate bean。 Add next thing to your application.properties file:将下一个内容添加到您的 application.properties 文件中:
spring.main.allow-bean-definition-overriding=true
  1. Replace delete() with set deletedAt:将 delete() 替换为 set deletedAt:
// Deletion method

// Before
User = userRepository.findById(id);
userRepository.delete(user);

// Now
User = userRepository.findById(id);
user.setDeletedAt(LocalDateTime.now()); 
userRepository.save(user);



Second Step.第二步。 Implement soft delete for method in Repositories (generated by JPA) like as findAllByEmail(String email), existsByNameAndUsername(String name, String username)... .为存储库(由 JPA 生成)中的方法实现软删除,例如findAllByEmail(String email), existsByNameAndUsername(String name, String username)...

Resource: https://blog.rpuch.com/2019/10/27/spring-data-mongo-soft-delete-repositories.html资源: https://blog.rpuch.com/2019/10/27/spring-data-mongo-soft-delete-repositories.html

  1. SoftDeleteMongoQueryLookupStrategy SoftDeleteMongoQueryLookupStrategy
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.query.ConvertingParameterAccessor;
import org.springframework.data.mongodb.repository.query.PartTreeMongoQuery;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.expression.spel.standard.SpelExpressionParser;

import java.lang.reflect.Method;

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

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

    @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().andOperator(
                Criteria.where("deletedAt").exists(false)
        );
    }

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

        @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());
        }
    }
}
  1. SeesSoftlyDeletedRecords (if you marked method annotation then method will ignore soft-deletion) SeesSoftlyDeletedRecords(如果您标记了方法注释,则方法将忽略软删除)
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SeesSoftlyDeletedRecords {
}
  1. SoftDeleteMongoRepositoryFactory软删除MongoRepositoryFactory
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.repository.support.MongoRepositoryFactory;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;

import java.util.Optional;

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 Optional.of(createSoftDeleteQueryLookupStrategy(optStrategy.get(), evaluationContextProvider));
    }

    private SoftDeleteMongoQueryLookupStrategy createSoftDeleteQueryLookupStrategy(QueryLookupStrategy strategy,
                                                                                   QueryMethodEvaluationContextProvider evaluationContextProvider) {
        return new SoftDeleteMongoQueryLookupStrategy(strategy, mongoOperations, evaluationContextProvider);
    }
}
  1. SoftDeleteMongoRepositoryFactoryBean SoftDeleteMongoRepositoryFactoryBean
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.repository.support.MongoRepositoryFactoryBean;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;

import java.io.Serializable;

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);
    }
}
  1. Add it to configuration将其添加到配置
@Configuration
@EnableMongoRepositories(basePackages = {"path to package with your repositories"}, repositoryFactoryBeanClass = SoftDeleteMongoRepositoryFactoryBean.class)
public class MyConfiguration {
//...
}

Hope it will help someone)希望它会帮助别人)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM