Spring 2.0迁移后MongoRepository集成QuerydslPredicateExecutor<抽象类>报错
我之前升级到Spring Data 2.x时也碰到过一模一样的坑,核心原因是Spring Data 2.x对QuerydslPredicateExecutor的属性解析逻辑变了:当你的Repository泛型参数是抽象类时,默认会基于抽象类的元数据来解析Predicate里的属性,完全不会识别子类特有的属性,所以才会抛出Required property propertyId not found for class org.some.abstract.class.EntityClass这种异常。
下面给你几个实用的解决方案,按推荐优先级排序:
方案一:为每个具体子类创建专属Repository(最推荐)
这是最贴合Spring Data设计思路的方案,简单直接,不需要复杂的自定义实现。你只需要:
- 先定义一个基础的抽象Repository接口,继承
MongoRepository和QuerydslPredicateExecutor:
public interface BaseEntityRepository<T extends AbstractEntity, ID> extends MongoRepository<T, ID>, QuerydslPredicateExecutor<T> { }
- 为每个具体的实体子类创建对应的Repository,继承这个基础接口,泛型参数换成具体子类:
public interface ConcreteEntityRepository extends BaseEntityRepository<ConcreteEntity, String> { }
这样每个具体Repository的QuerydslPredicateExecutor会自动使用对应子类的元数据来解析属性,你再用子类属性构建Predicate查询时就不会报错了。
方案二:用Querydsl PathBuilder指定具体子类类型构建Predicate
如果不想为每个子类创建Repository,你可以在构建Predicate时,明确指定具体子类的类型,让Querydsl知道要解析的是子类的属性:
// 用具体子类创建PathBuilder PathBuilder<ConcreteEntity> pathBuilder = new PathBuilder<>(ConcreteEntity.class, "entity"); // 基于子类属性构建Predicate Predicate predicate = pathBuilder.get("propertyId").eq("your-property-value"); // 调用抽象Repository的findAll方法 baseEntityRepository.findAll(predicate);
这个方案适合临时快速解决问题,但缺点是构建Predicate时需要硬编码子类类型,灵活性稍差。
方案三:自定义QuerydslPredicateExecutor实现(复杂场景)
如果你的项目需要高度复用抽象Repository,又不想硬编码子类类型,可以自定义QuerydslMongoPredicateExecutor的实现,让它能动态识别实际查询的子类元数据:
步骤1:自定义Repository Factory Bean
public class CustomMongoRepositoryFactoryBean<T extends Repository<S, ID>, S, ID> extends MongoRepositoryFactoryBean<T, S, ID> { public CustomMongoRepositoryFactoryBean(Class<? extends T> repositoryInterface) { super(repositoryInterface); } @Override protected RepositoryFactorySupport createRepositoryFactory(MongoOperations operations) { return new CustomMongoRepositoryFactory(operations); } private static class CustomMongoRepositoryFactory extends MongoRepositoryFactory { private final MongoOperations operations; public CustomMongoRepositoryFactory(MongoOperations operations) { super(operations); this.operations = operations; } @Override protected QuerydslPredicateExecutor<?> getQuerydslPredicateExecutor(RepositoryInformation information) { EntityPathResolver resolver = SimpleEntityPathResolver.INSTANCE; MongoEntityInformation<?, Serializable> entityInfo = getEntityInformation(information.getDomainType()); return new CustomQuerydslMongoPredicateExecutor<>(entityInfo, operations, resolver); } } }
步骤2:自定义QuerydslMongoPredicateExecutor子类
public class CustomQuerydslMongoPredicateExecutor<T> extends QuerydslMongoPredicateExecutor<T> { private final MongoOperations mongoOperations; private final EntityPathResolver resolver; private final MongoEntityInformation<T, ?> entityInfo; public CustomQuerydslMongoPredicateExecutor(MongoEntityInformation<T, ?> entityInfo, MongoOperations mongoOperations, EntityPathResolver resolver) { super(entityInfo, mongoOperations, resolver); this.entityInfo = entityInfo; this.mongoOperations = mongoOperations; this.resolver = resolver; } @Override protected MongoQuery createQuery(Predicate predicate) { // 这里可以根据实际场景动态获取子类类型,比如从Predicate中推断 // 示例中直接使用当前实体类(如果是子类的话) Class<?> actualEntityType = entityInfo.getJavaType(); EntityPath<T> path = resolver.createPath((Class<T>) actualEntityType); MongoQuery query = new MongoQuery(path, mongoOperations.getConverter()); return predicate != null ? query.where(predicate) : query; } }
步骤3:配置Spring使用自定义Factory Bean
@Configuration @EnableMongoRepositories(repositoryFactoryBeanClass = CustomMongoRepositoryFactoryBean.class) public class MongoConfiguration { // 你的MongoDB配置代码 }
这个方案适合复杂的复用场景,但代码量较大,维护成本也更高,建议优先用前两个方案。
内容的提问来源于stack exchange,提问作者Szymon Bohdanowicz

