中,我描述了如何设置和使用SpringDataJDBC。我还描述了使JDBC比JPA更容易理解的前提。一旦您考虑引用,这就会变得有趣。作为第一个例子,考虑以下领域模型:,product));}privateOrderItemcreateOrderItem(intquantity,Stringproduct){OrderItemitem=newOrderItem();item.product=product;item.quantity=quantity;returnitem;}}classOrderItem{intquantity;Stringproduct;}此外,考虑这样定义的存储库:interfaceOrderRepositoryextendsCrudRepository{@Query("selectcount(*)fromorder_item")intcountItems();}坚持。这正是发生的事情:@AutowiredOrderRepository存储库;@TestpublicvoidcreateUpdateDeleteOrder(){PurchaseOrderorder=newPurchaseOrder();order.addItem(4,"未来彗星船长乐高套装");order.addItem(2,"可爱的蓝色垂钓鱼毛绒玩具");PurchaseOrdersaved=repository.save(order);assertThat(repository.count()).isEqualTo(1);assertThat(repository.countItems()).isEqualTo(2);...此外,如果您删除PurchaseOrder,则其所有项目也应被删除。同样,事情就是这样。...repository.delete(saved);assertThat(repository.count()).isEqualTo(0);assertThat(repository.countItems()).isEqualTo(0);}但如果我们考虑具有相同语法的关系呢但不同的语义?classBook{//...Setauthors=newHashSet();}当一本书绝版时,您将其删除。所有的作者都走了。当然不是你想要的,因为一些作者可能也写过其他书。现在,这没有意义。是吗?我认为是的。要理解为什么这实际上有意义,我们需要退后一步,看看实际存在哪些存储库。这与一个反复出现的问题密切相关:您应该在JPA中为每个表创建一个存储库吗?而正确权威的答案是“NO”。存储库持久存在并加载聚合。聚合是一组对象组成一个单元,应该始终保持一致。此外,它应该始终保持(和加载)在一起。它有一个对象,称为聚合根,这是唯一允许接触或引用聚合内部的对象。聚合根是传递给存储库以持久保存聚合的内容。这就提出了一个问题:SpringDataJDBC是如何判断哪些是聚合的一部分,哪些不是?答案很简单:您可以通过遵循非瞬态引用从聚合根访问的所有内容都是聚合的一部分。考虑到这一点,OrderRepository的行为是完全合理的。OrderItem实例是聚合的一部分,因此被删除。相反,作者实例不是Book聚合的一部分,因此不应删除。所以它们不应该被Book类引用。问题已经解决了。好吧,...不是真的。我们仍然需要存储和访问有关Book和Author之间关系的信息。答案可以再次在领域驱动设计(DDD)中找到,它建议使用ID而不是直接引用。这适用于各种多对x关系。如果多个聚合引用同一实体,则该实体不能是引用它的聚合的一部分,因为它只能是一个聚合的一部分。因此,任何多对一和多对多关系都必须通过仅引用id来建模。如果你应用它,你会实现几件事:你清楚地表示聚合的边界。您还可以完全解耦(至少在应用程序的域模型中)所涉及的两个聚合。这种分离在数据库中可以用不同的方式来表达:保持数据库的正常状态,包括所有的外键。这意味着您必须确保以正确的顺序创建和保存聚合。使用延迟约束,检查仅在事务的提交阶段完成。这可以实现更高的吞吐量。它还编纂了最终一致性的一个版本,其中“最终”与事务的结束相关联。这也允许引用从未存在过的聚合,只要它只发生在事务期间。这对于避免仅仅为了满足外键和非空约束而编写大量基础结构代码很有用。完全删除外键以实现真正的最终一致性。将引用的聚合保存在不同的数据库中,甚至可能是NoSQL存储。无论您采取何种分离,即使是SpringDataJDBC强制执行的最小分离也会鼓励您的应用程序模块化。此外,如果您曾经尝试过迁移一个真正的10年前的单一应用程序,您就会知道它的价值。使用SpringDataJDBC,您可以为多对多关系建模,如下所示:(createAuthorRef(author));}privateAuthorRefcreateAuthorRef(Authorauthor){Assert.notNull(author,"Authormustnotbenull");Assert.notNull(author.id,"Authorid,mustnotbenull"");AuthorRefauthorRef=newAuthorRef();authorRef.author=author.id;returnauthorRef;}}@Table("Book_Author")classAuthorRef{Longauthor;}classAuthor{@IdLongid;Stringname;}注意额外的类(AuthorRef),它代表书籍聚合关于作者的知识。它可能包含关于作者的其他聚合信息,然后实际复制到数据库中。考虑到作者数据库可能与书籍数据库有很大不同,这有很多事情要做。另请注意,作者集是私有字段,实例的AuthorRef的实例化发生在私有方法中。所以聚合之外的任何东西都不能直接访问它。SpringDataJDBC从不要求这样做,但DDD鼓励这样做。域将像这样使用:@TestpublicvoidbooksAndAuthors(){Authorauthor=newAuthor();author.name="GregL.Turnquist";author=authors.save(author);书book=newBook();book.title="SpringBoot";book.addAuthor(author);books.save(book);books.deleteAll();assertThat(authors.count()).isEqualTo(1);}总结一下:SpringDataJDBC不支持多对一或多对多关系。要对这些进行建模,请使用ID。这鼓励了域模型的干净模块化。它还消除了人们必须解决并学习推理这种映射是否可行的一整套问题。沿着类似的思路,避免双向依赖。聚合中的引用从聚合根到元素。聚合之间的引用由一个方向的ID表示。此外,如果您需要反向导航,请使用存储库中的查询方法。这使得哪个聚合负责维护引用变得明确。以下是示例使用的数据库结构。Purchase_Order(idshipping_address)Order_Item(purchase_orderquantityproduct);Book(idtitle)Author(idname)Book_Author(bookauthor)完整示例代码可在Spring中国教育管理中心(Spring认证)数据示例库中获取!
