MongoDB 逻辑删除

发布于:2022-11-28 ⋅ 阅读:(441) ⋅ 点赞:(0)

场景

MongoDB 默认的删除方式是物理删除,但很多时候我们的需求都是逻辑删除,即通过某个字段去判断是否删除,类似于 MyBatisPlus 的 @TableLogic 注解
 

方案

  1. 自定义逻辑删除注解 @SoftDelete
  2. 把注解加到实体类字段 deleteFlag 上
  3. 创建数据时,初始化 deleteFlag 等于 N
  4. 重写删除方法,改成把 deleteFlag 置 Y
  5. 监听查询,每个查询都添加 deleteFlag = N 的查询条件
     

注解

逻辑删除注解 @SoftDelete

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SoftDelete {
}

 

实体类

公共字段实体 BaseDO,所有实体都将继承它

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class BaseDO implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "主键")
    @Mapping("id")
    @Id
    private Long id;

    @SoftDelete
    @ApiModelProperty(value = "逻辑删除标记")
    private String deleteFlag;
}

 

初始化

  • AbstractMongoEventListener:监听器
  • onBeforeConvert:在 object 被MongoConverter转换为Document之前,在MongoTemplate insert,insertList和save操作中调用
  • 参考文章:MongnDB 自动装配
@Configuration
public class MongoCommonFieldsEventListener extends AbstractMongoEventListener {

    @Autowired
    ICurrentUserProvider currentUserProvider;

    @Override
    public void onBeforeConvert(BeforeConvertEvent event) {
        Object source = event.getSource();
        if (source instanceof BaseDO) {
            BaseDO entity = BaseDO.class.cast(source);
            if (entity.getCreationDate() == null) {
				entity.setDeleteFlag("N");
			}
        }
    }
}

 

重写删除方法

默认的删除方法 deleteById,会物理删除数据

重写:更新 deleteFlag 字段为 Y

public void delete(Long id) {
	TestPO po = new TestPO();
	po.setId(id);
	po.setDeleteFlag("Y");
	mongoTemplate.save(testPO);
}

 

查询拦截器

jpa 过滤

 
过滤传参
自定义查询器

  • 效果:每个查询都添加 deleteFlag = N 的查询条件
  • PartTreeMongoQuery:jpa 查询必经之路
  • javaType:通过 entityInformation 获取实体类
  • annotationFieldOne:获取 SoftDelete 注解的字段名
  • query:添加查询条件
public class CusPartTreeMongoQuery extends PartTreeMongoQuery {

    public CusPartTreeMongoQuery(MongoQueryMethod method, MongoOperations mongoOperations, SpelExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
        super(method, mongoOperations, expressionParser, evaluationContextProvider);
    }

    @Override
    protected Query createQuery(ConvertingParameterAccessor accessor) {
        Query query = super.createQuery(accessor);
        query.fields();
        MongoQueryMethod queryMethod = super.getQueryMethod();
        MongoEntityMetadata<?> entityInformation = queryMethod.getEntityInformation();
        Class<?> javaType = entityInformation.getJavaType();
        Optional<Field> annotationFieldOne = ReflectionUtil.getAnnotationFieldOne(javaType, SoftDelete.class);
        annotationFieldOne.ifPresent(field -> {
            try {
                query.addCriteria(Criteria.where(field.getName()).is("N"));
            } catch (Exception ignored) { }
        });
        return query;
    }
}

ReflectionUtil 反射工具类:根据注解获取字段名

public class ReflectionUtil {

    private ReflectionUtil(){}

    public static Collection<Field> getAnnotationField(Object object, Class<? extends Annotation> annotation){
        return getAnnotationField(object.getClass(),annotation);
    }

    public static Collection<Field> getAnnotationField(Class<?> clazz,Class<? extends Annotation> annotation){
        List<Field> result = new ArrayList<>();
        Collection<Field> allDeclaredFields = getDeclaredFields(clazz);
        for (Field declaredField : allDeclaredFields) {
            if (declaredField.isAnnotationPresent(annotation)){
                result.add(declaredField);
            }
        }
        return result;
    }

    public static Optional<Field> getAnnotationFieldOne(Class<?> clazz, Class<? extends Annotation> annotation){
        Collection<Field> result = getAnnotationField(clazz, annotation);
        return result.stream().findFirst();
    }
    public static Optional<Field> getFieldsByFieldName(Class<?> sourceClass,String fieldName){
        Collection<Field> declaredFields = getDeclaredFields(sourceClass);
        return declaredFields.stream().filter(field -> field.getName().equals(fieldName)).findFirst();
    }

    public static Collection<Field> getDeclaredFields(Class<?> sourceClass) {
        ArrayList<Field> result = new ArrayList<>();
        while (sourceClass.getDeclaredFields().length != 0) {
            result.addAll(Arrays.asList(sourceClass.getDeclaredFields().clone()));
            sourceClass = sourceClass.getSuperclass();
        }
        return result;
    }
}

 

工厂类
CusMongoRepositoryFactory

自定义工厂类,因为默认工厂类不会加载自定义的查询器

重点是最后一行,创建自定义查询器:return new CusPartTreeMongoQuery

public class CusMongoRepositoryFactory extends MongoRepositoryFactory {
    private static final SpelExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
    private final MongoOperations operations;
    private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
    /**
     * Creates a new {@link MongoRepositoryFactory} with the given {@link MongoOperations}.
     *
     * @param mongoOperations must not be {@literal null}.
     */
    public CusMongoRepositoryFactory(MongoOperations mongoOperations) {
        super(mongoOperations);
        Assert.notNull(mongoOperations, "MongoOperations must not be null!");

        this.operations = mongoOperations;
        this.mappingContext = mongoOperations.getConverter().getMappingContext();

    }

    @Override
    protected Optional<QueryLookupStrategy> getQueryLookupStrategy(QueryLookupStrategy.Key key, QueryMethodEvaluationContextProvider evaluationContextProvider) {
        return Optional.of(new CusMongoRepositoryFactory.MongoQueryLookupStrategy(operations, evaluationContextProvider, mappingContext));
    }

    /**
     * {@link QueryLookupStrategy} to create instances.
     *
     * @author Oliver Gierke
     * @author Thomas Darimont
     */
    private static class MongoQueryLookupStrategy implements QueryLookupStrategy {

        private final MongoOperations operations;
        private final QueryMethodEvaluationContextProvider evaluationContextProvider;
        MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;

        public MongoQueryLookupStrategy(MongoOperations operations,
                                        QueryMethodEvaluationContextProvider evaluationContextProvider,
                                        MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {

            this.operations = operations;
            this.evaluationContextProvider = evaluationContextProvider;
            this.mappingContext = mappingContext;
        }

        /*
         * (non-Javadoc)
         * @see org.springframework.data.repository.query.QueryLookupStrategy#resolveQuery(java.lang.reflect.Method, org.springframework.data.repository.core.RepositoryMetadata, org.springframework.data.projection.ProjectionFactory, org.springframework.data.repository.core.NamedQueries)
         */
        @Override
        public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
                                            NamedQueries namedQueries) {

            MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, factory, mappingContext);
            String namedQueryName = queryMethod.getNamedQueryName();

            if (namedQueries.hasQuery(namedQueryName)) {
                String namedQuery = namedQueries.getQuery(namedQueryName);
                return new StringBasedMongoQuery(namedQuery, queryMethod, operations, EXPRESSION_PARSER,
                        evaluationContextProvider);
            } else if (queryMethod.hasAnnotatedAggregation()) {
                return new StringBasedAggregation(queryMethod, operations, EXPRESSION_PARSER, evaluationContextProvider);
            } else if (queryMethod.hasAnnotatedQuery()) {
                return new StringBasedMongoQuery(queryMethod, operations, EXPRESSION_PARSER, evaluationContextProvider);
            } else {
                return new CusPartTreeMongoQuery(queryMethod, operations, EXPRESSION_PARSER, evaluationContextProvider);
            }
        }
    }
}

 

CusMongoRepositoryFactoryBean

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

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

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

在启动类加载装配

@SpringBootApplication
@EnableMongoRepositories(repositoryFactoryBeanClass = CusMongoRepositoryFactoryBean.class)
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application .class, args);
    }

}

 

MongoRepository 过滤

接口继承 PagingAndSortingRepository 和 QueryByExampleExecutor

@NoRepositoryBean
public interface CusMongoRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
	...
}

实现类 CusMongoRepositoryImpl

查询方法添加查询条件,以 findAll 做例子

public class CusMongoRepositoryImpl<T, ID> implements CusMongoRepository<T, ID> {
    private static final String SOFT_DELETION_KEY = "deleteFlag";
    private static final Object DELETE_TRUE = "Y";
    private static final Object DELETE_FALSE = "N";
    private final MongoOperations mongoOperations;
    private final MongoEntityInformation<T, ID> entityInformation;

    public CusMongoRepositoryImpl(
            MongoEntityInformation<T, ID> metadata, MongoOperations mongoOperations) {
        Assert.notNull(metadata, "MongoEntityInformation must not be null!");
        Assert.notNull(mongoOperations, "MongoOperations must not be null!");
        this.entityInformation = metadata;
        this.mongoOperations = mongoOperations;
    }

    @Override
    public List<T> findAll(@Nullable Query query) {
        if (query == null) {
            return Collections.emptyList();
        }
        query.addCriteria(where(SOFT_DELETION_KEY).is(DELETE_FALSE));
        return mongoOperations.find(
                query, entityInformation.getJavaType(), entityInformation.getCollectionName());
    }
}

在启动类加载装配

@SpringBootApplication
@EnableMongoRepositories(repositoryFactoryBeanClass = CusMongoRepositoryFactoryBean.class,repositoryBaseClass = CusMongoRepositoryImpl.class)
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application .class, args);
    }
}
本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到