public interface OrderMatchBlockListDocumentRepository extends OrderMatchBlockListDocumentRepositoryExtension, MongoRepository<OrderMatchBlockListDocument, Long> { Optional<OrderMatchBlockListDocument> findFirstByPayOrderIdOrderByCreatedAtDesc(Long payOrderId); }
@Component public class OrderMatchBlockListDocumentRepositoryImpl extends AbstractRepoExtension implements OrderMatchBlockListDocumentRepositoryExtension { public OrderMatchBlockListDocumentRepositoryImpl(@Qualifier(MatchPushMongo.MONGO_TEMPLATE) MongoOperations mongoOperations) { super(mongoOperations); }
//参考:com.qibingdaojia.turnright.datasource.mongo.matchpush.repository.OrderMatchBlockListDocumentRepositoryImpl#findPayOrderIdsByLocksmithId
@Override
public List<Long> findPayOrderIdsByLocksmithId(Long locksmithId) {
Criteria criteria = Criteria
.where("blockLocksmithIdList").is(locksmithId)
.and("deletedAt").is(null);
Query query = Query.query(criteria);
query.fields()
.include("payOrderId")
.exclude("_id");
return mongoOperations.find(query, OrderMatchBlockListDocument.class)
.stream()
.map(OrderMatchBlockListDocument::getPayOrderId)
.collect(Collectors.toList());
}
} public class OrderMatchBlockListDocumentRepositoryImpl extends AbstractRepoExtension implements OrderMatchBlockListDocumentRepositoryExtension 这样写有没有问题
public interface OrderMatchBlockListDocumentRepository extends OrderMatchBlockListDocumentRepositoryExtension, MongoRepository<OrderMatchBlockListDocument, Long> { Optional<OrderMatchBlockListDocument> findFirstByPayOrderIdOrderByCreatedAtDesc(Long payOrderId); } public interface OrderMatchBlockListDocumentRepositoryExtension {
List<Long> findPayOrderIdsByLocksmithId(Long locksmithId);
} @Component public class OrderMatchBlockListDocumentRepositoryImpl extends AbstractRepoExtension implements OrderMatchBlockListDocumentRepositoryExtension { public OrderMatchBlockListDocumentRepositoryImpl(@Qualifier(MatchPushMongo.MONGO_TEMPLATE) MongoOperations mongoOperations) { super(mongoOperations); }
//参考:com.qibingdaojia.turnright.datasource.mongo.matchpush.repository.OrderMatchBlockListDocumentRepositoryImpl#findPayOrderIdsByLocksmithId
@Override
public List<Long> findPayOrderIdsByLocksmithId(Long locksmithId) {
Criteria criteria = Criteria
.where("blockLocksmithIdList").is(locksmithId)
.and("deletedAt").is(null);
Query query = Query.query(criteria);
query.fields()
.include("payOrderId")
.exclude("_id");
return mongoOperations.find(query, OrderMatchBlockListDocument.class)
.stream()
.map(OrderMatchBlockListDocument::getPayOrderId)
.collect(Collectors.toList());
}
} OrderMatchBlockListDocumentRepository extends OrderMatchBlockListDocumentRepositoryExtension 这样写有没有问题
这样写完全没有问题。这种结构是 Spring Data 的推荐做法,被称为“自定义 Repository 扩展”。
让我们分解一下这个结构:
OrderMatchBlockListDocumentRepository(主接口)- 继承了
MongoRepository:这为你提供了所有Spring Data内置的标准CRUD和查询方法(如save,findById,findAll等)。 - 继承了
OrderMatchBlockListDocumentRepositoryExtension(你的自定义扩展接口):这声明了你需要但Spring Data无法自动生成的自定义、复杂查询方法。
- 继承了
OrderMatchBlockListDocumentRepositoryExtension(自定义扩展接口)- 定义了一个自定义方法
findPayOrderIdsByLocksmithId。Spring Data无法仅通过方法名自动实现这个相对复杂的查询。
- 定义了一个自定义方法
OrderMatchBlockListDocumentRepositoryImpl(自定义扩展实现类)- 实现了
OrderMatchBlockListDocumentRepositoryExtension接口。 - 使用
MongoOperations手动编写具体的查询逻辑来满足findPayOrderIdsByLocksmithId方法的需求。
- 实现了
Spring Data 的魔法在于:当你的服务层注入 OrderMatchBlockListDocumentRepository 时,Spring Data 会自动将两部分实现合并成一个可用的 Bean。
- 来自
MongoRepository的标准方法由 Spring Data 提供实现。 - 来自
OrderMatchBlockListDocumentRepositoryExtension的自定义方法,其实现由你编写的OrderMatchBlockListDocumentRepositoryImpl提供。
因此,你可以像使用一个普通的 Repository 一样使用它:
@Service
public class MyService {
@Autowired
private OrderMatchBlockListDocumentRepository repository; // 注入合并后的Repository
public void doSomething() {
// 1. 使用标准方法 (来自 MongoRepository)
Optional<OrderMatchBlockListDocument> doc = repository.findFirstByPayOrderIdOrderByCreatedAtDesc(123L);
// 2. 使用自定义方法 (来自 OrderMatchBlockListDocumentRepositoryExtension, 实现由 Impl 类提供)
List<Long> orderIds = repository.findPayOrderIdsByLocksmithId(456L);
}
}
总结: 你提供的代码结构是标准、正确且优雅的。它完美地结合了 Spring Data 的声明式编程的便利性和手动编写复杂查询的灵活性。这是一种常见的、官方推荐的最佳实践。
Spring Data 会自动将两部分实现合并成一个可用的 Bean 什么原理,OrderMatchBlockListDocumentRepositoryExtension还可以用吗?
你的理解完全正确,让我详细解释一下这个机制的原理:
合并原理:Spring Data 的 "组合代理" 模式
Spring Data 在启动时会为每个 Repository 接口创建动态代理,这个代理会组合多个实现来源:
- Spring Data 的标准实现 - 由框架生成
- 你的自定义实现 - 从
*Impl类获取 - 查询方法实现 - 基于方法名解析自动生成
具体工作流程:
// 1. 启动时,Spring Data 发现这个接口
public interface OrderMatchBlockListDocumentRepository
extends OrderMatchBlockListDocumentRepositoryExtension, // ← 扩展接口
MongoRepository<OrderMatchBlockListDocument, Long> { // ← 基础接口
// 声明式查询方法
Optional<OrderMatchBlockListDocument> findFirstByPayOrderIdOrderByCreatedAtDesc(Long payOrderId);
}
// 2. Spring Data 创建代理,组合实现:
代理类 OrderMatchBlockListDocumentRepositoryProxy {
// 包含三个处理器:
- 基础处理器:处理 save(), findById(), delete() 等标准方法
- 查询处理器:处理 findFirstByPayOrderIdOrderByCreatedAtDesc 等声明式查询
- 自定义处理器:处理 findPayOrderIdsByLocksmithId 等自定义方法
}
关于 OrderMatchBlockListDocumentRepositoryExtension 的可用性
可以单独使用,但不推荐,有更好的使用方式:
方式1:直接使用(可行但不优雅)
@Service
public class MyService {
// 可以单独注入扩展接口
@Autowired
private OrderMatchBlockListDocumentRepositoryExtension repositoryExtension;
public void doSomething() {
List<Long> ids = repositoryExtension.findPayOrderIdsByLocksmithId(123L);
// 但这里无法使用标准CRUD方法
}
}
方式2:推荐用法 - 通过主接口使用
@Service
public class MyService {
// 推荐:注入完整的主接口
@Autowired
private OrderMatchBlockListDocumentRepository repository;
public void doSomething(Long locksmithId) {
// 1. 使用自定义方法
List<Long> orderIds = repository.findPayOrderIdsByLocksmithId(locksmithId);
// 2. 使用标准方法
repository.save(newDocument);
// 3. 使用声明式查询
Optional<OrderMatchBlockListDocument> doc =
repository.findFirstByPayOrderIdOrderByCreatedAtDesc(123L);
}
}
方式3:扩展接口的复用(高级用法)
// 定义通用扩展接口
public interface CustomBlockListRepository {
List<Long> findPayOrderIdsByLocksmithId(Long locksmithId);
// 其他通用方法...
}
// 多个Repository可以复用
public interface OrderMatchBlockListDocumentRepository
extends CustomBlockListRepository, MongoRepository<OrderMatchBlockListDocument, Long> {
// ...
}
public interface OtherBlockListRepository
extends CustomBlockListRepository, MongoRepository<OtherDocument, Long> {
// ...
}
技术实现细节
Spring Data 通过以下步骤实现合并:
// Spring Data内部简化流程:
1. 扫描所有继承 Repository 的接口
2. 为每个接口创建 RepositoryFactoryBean
3. 在 RepositoryFactoryBean 中:
a. 查找是否有对应的 *Impl 类
b. 如果有,将其方法添加到代理的方法列表中
c. 将声明式查询方法也添加到代理
d. 将所有方法统一路由到正确的处理器
4. 注册一个合并后的代理Bean到Spring容器
总结建议
- 直接使用主接口:
OrderMatchBlockListDocumentRepository - 不要单独使用扩展接口:除非有特殊的多态需求
- 扩展接口的真正价值:是定义契约,而不是被直接注入
- 实现类的作用:是实现扩展接口的契约,供Spring Data合并使用
你的代码结构是正确的,这种设计模式让你既能享受Spring Data的便利,又能灵活实现复杂查询,是最佳实践。