场景
MongoDB 默认的删除方式是物理删除,但很多时候我们的需求都是逻辑删除,即通过某个字段去判断是否删除,类似于 MyBatisPlus 的 @TableLogic 注解
方案
- 自定义逻辑删除注解 @SoftDelete
- 把注解加到实体类字段 deleteFlag 上
- 创建数据时,初始化 deleteFlag 等于 N
- 重写删除方法,改成把 deleteFlag 置 Y
- 监听查询,每个查询都添加 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 后查看