MongnDB 增强点


思考

  1. MongnDB 的插入和更新操作是否可以做增强呢?
  2. MongnDB 的 mapper 继承 MongoRepository,MongoRepository 拥有大量常用接口,但这些接口好用吗,不符合需求的话,我们是否可以重写呢?
  3. MongnDB 因为联表查询较少,日常查询可用 JPA 实现,那么可以对 JPA 查询进行增强吗?

对应场景

  1. 统一插入 id、添加创建人、创建时间等
  2. 物理删除改为逻辑删除、添加默认查询条件等
  3. 添加默认查询条件

 

插入和更新

AbstractMappingEventListener 中存在以下回调方法:

  • onBeforeConvert:在 object 被 MongoConverte r转换为 Document 之前,在 MongoTemplate insert,insertLis t和 save 操作中调用。
  • onBeforeSave: 在将 Document 插入或保存在数据库中之前,请在 MongoTemplate insert,insertList 和 save 操作中调用。
  • onAfterSave: 在将 Document 插入或保存在数据库中之后,在 MongoTemplate insert,insertList 和 save 操作中调用。
  • onAfterLoad: 从数据库中检索 Document 后,在 MongoTemplate find,findAndRemove,findOne,findAll 和 getCollection 方法中调用。
  • onAfterConvert: 从数据库中检索到 Document 后,在 MongoTemplate find,findAndRemove,findOne,findAll 和 getCollection方法中调用被转换为 POJO

可以看出,这个监听器可以对插入和更新的传参进行过滤,也可以对查询结果进行过滤

下面举例说明如何自定义 id 生成策略

@Configuration
public class CommonFieldsEventListener extends AbstractMongoEventListener {

    @Override
    public void onBeforeConvert(BeforeConvertEvent event) {
        Object source = event.getSource();
        if (source instanceof BasePO) {
            LocalDateTime now = LocalDateTime.now();
            BasePO entity = BasePO.class.cast(source);
            
            /* 使用随机数做 id */
            if(entity.getId() == null){
                entity.setId(RandomUtils.nextLong());
            }           
        }
    }
}

BasePO 为公共实体,每个数据库表实体都必须继承它

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

    private static final long serialVersionUID = 1L;
 
    @ApiModelProperty(value = "主键")
    @Id
    private Long id;

	...
}

 

MongoRepository

继承 MongoRepository

@NoRepositoryBean:使用了该注解的接口不会被单独创建实例,只会作为其他接口的父接口而被使用。

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

实现类

public class CusMongoRepositoryImpl<T, ID> implements CusMongoRepository<T, ID> {
	...
}

操作 Mapper 继承自定义的 CusMongoRepository

@Repository
public interface TestMapper extends CusMongoRepository<TestPO, Long> {

}

然而启动后会报错

Error creating bean with name 'testMapper' defined in com.njc.mongodb.mapper.TestMapper defined in @EnableMongoRepositories declared on MongoRepositoriesRegistrar.EnableMongoRepositoriesConfiguration: Invocation of init method failed; 
nested exception is org.springframework.data.repository.query.QueryCreationException: Could not create query for public abstract com.mongodb.client.result.UpdateResult com.njc.mongodb.config.CusMongoRepository.updateMulti

原因:自定义类 CusMongoRepositoryImpl 并没有实例化,
方案:从报错可以看出,通过 @EnableMongoRepositories 配置 使用解释

repositoryBaseClass:Configure the repository base class to be used to create repository proxies for this particular configuration.
意思是:配置用于为此特定配置创建存储库代理的存储库基类

启动类装配

@SpringBootApplication
@EnableMongoRepositories(repositoryBaseClass = CusMongoRepositoryImpl.class)
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application .class, args);
    }
}

 

JPA

假设有个需求,需要根据名称查询数据,使用 JPA 可以快速实现 findByName,下面以这个需求为例子做探讨

@Repository
public interface TestMapper extends CusMongoRepository<TestPO, Long> {

    TestPO findByName(String name);

}

 

寻找增强点

  1. MongoRepositoryFactoryBean 创建 MongoRepositoryFactory
public class MongoRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extends Serializable>
		extends RepositoryFactoryBeanSupport<T, S, ID> {

	...
	
	protected RepositoryFactorySupport getFactoryInstance(MongoOperations operations) {
		return new MongoRepositoryFactory(operations);
	}

}
  1. MongoRepositoryFactory 创建 PartTreeMongoQuery
public class MongoRepositoryFactory extends RepositoryFactorySupport {
		@Override
		public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
				NamedQueries namedQueries) {

			MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, factory, mappingContext);
			queryMethod.verify();

			String namedQueryName = queryMethod.getNamedQueryName();

			if (namedQueries.hasQuery(namedQueryName)) {
				String namedQuery = namedQueries.getQuery(namedQueryName);
				return new StringBasedMongoQuery(namedQuery, queryMethod, operations, expressionParser,
						evaluationContextProvider);
			} else if (queryMethod.hasAnnotatedAggregation()) {
				return new StringBasedAggregation(queryMethod, operations, expressionParser, evaluationContextProvider);
			} else if (queryMethod.hasAnnotatedQuery()) {
				return new StringBasedMongoQuery(queryMethod, operations, expressionParser, evaluationContextProvider);
			} else {
				return new PartTreeMongoQuery(queryMethod, operations, expressionParser, evaluationContextProvider);
			}
		}
	}
}
  1. PartTreeMongoQuery 的 createQuery 方法,为 JPA 创建查询条件的方法
public class PartTreeMongoQuery extends AbstractMongoQuery {

	@Override
	protected Query createQuery(ConvertingParameterAccessor accessor) {

		MongoQueryCreator creator = new MongoQueryCreator(tree, accessor, context, isGeoNearQuery);
		Query query = creator.createQuery();
	}
}
  1. PartTree 是 PartTreeMongoQuery 的一个入参,从此可以看出 findByName 和 queryByName 可以打到同样效果
public class PartTree implements Streamable<OrPart> {

	private static final String KEYWORD_TEMPLATE = "(%s)(?=(\\p{Lu}|\\P{InBASIC_LATIN}))";
	private static final String QUERY_PATTERN = "find|read|get|query|search|stream";
	private static final String COUNT_PATTERN = "count";
	private static final String EXISTS_PATTERN = "exists";
	private static final String DELETE_PATTERN = "delete|remove";
	private static final Pattern PREFIX_TEMPLATE = Pattern.compile( //
			"^(" + QUERY_PATTERN + "|" + COUNT_PATTERN + "|" + EXISTS_PATTERN + "|" + DELETE_PATTERN + ")((\\p{Lu}.*?))??By");
			
	...
}

 

配置

自定义 MongoQuery 继承 PartTreeMongoQuery,举例场景:添加条件,删除标识等于 N

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.addCriteria(Criteria.where("deleteFlag").is("N"));
        return query;
    }
}

自定义 CusMongoRepositoryFactory 继承 MongoRepositoryFactory

  • getQueryLookupStrategy:创建自定义的工厂 CusMongoRepositoryFactory
  • resolveQuery:返回自定义的查询策略 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;
 
    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));
    }

    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;
        }

        @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);
            }
        }
    }
}

创建自定义 MongoRepositoryFactoryBean,用来创建自定义 CusMongoRepositoryFactory

public class CusMongoRepositoryFactoryBean <T extends Repository<S, ID>, S, ID extends Serializable> extends MongoRepositoryFactoryBean<T, S, ID> {
    /**
     * Creates a new {@link MongoRepositoryFactoryBean} for the given repository interface.
     *
     * @param repositoryInterface must not be {@literal null}.
     */
    public CusMongoRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
        super(repositoryInterface);
    }

    /**
     * Creates and initializes a {@link RepositoryFactorySupport} instance.
     *
     * @param operations
     * @return
     */
    @Override
    protected RepositoryFactorySupport getFactoryInstance(MongoOperations operations) {
        return new CusMongoRepositoryFactory(operations);
    }
}

此时 FactoryBean、Factory、Query 都已实现了自定义,然后重启后断点发现,并没有走到我们自定义 Query 里面去

原因是 FactoryBean 并没有被 spring 实例化
方案:通过 @EnableMongoRepositories 配置 使用解释

repositoryFactoryBeanClass:Returns the FactoryBean class to be used for each repository instance.
意思是:返回要用于每个存储库实例的 FactoryBean 类

启动类装配

@SpringBootApplication
@EnableMongoRepositories(repositoryFactoryBeanClass= CusMongoRepositoryFactoryBean.class)
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application .class, args);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值