共计 3556 个字符,预计需要花费 9 分钟才能阅读完成。
之前的博客文章中,我、形容了如何设置和应用 Spring Data JDBC。我还形容了使 Spring Data
原题目:Spring 认证 |Spring Data JDBC、援用和聚合
JDBC 比 JPA 更容易了解的前提。一旦您思考参考资料,这就会变得乏味。作为第一个示例,请思考以下域模型:
class PurchaseOrder {
private @Id Long id;
private String shippingAddress;
private Set items = new HashSet();
void addItem(int quantity, String product) {
items.add(createOrderItem(quantity, product));
}
private OrderItem createOrderItem(int quantity, String product) {
OrderItem item = new OrderItem();
item.product = product;
item.quantity = quantity;
return item;
}
}
class OrderItem {
int quantity;
String product;
}
此外,请思考如下定义的存储库:
interface OrderRepository extends CrudRepository {
@Query(“select count(*) from order_item”)
int countItems();
}
如果您创立一个蕴含商品的订单,您可能心愿所有订单都可能长久化。这正是产生的事件:
@Autowired OrderRepository repository;
@Test
public void createUpdateDeleteOrder() {
PurchaseOrder order = new PurchaseOrder();
order.addItem(4, “Captain Future Comet Lego set”);
order.addItem(2, “Cute blue angler fish plush toy”);
PurchaseOrder saved = 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);
}
然而,如果咱们思考句法雷同但语义不同的关系呢?
class Book {
// …
Set authors = new HashSet();
}
当一本书绝版时,您将其删除。所有的作者都走了。当然不是你想要的,因为一些作者可能也写了其余书。当初,这没有意义。或者是吗?我认为的确如此。
为了了解为什么这的确有意义,咱们须要退后一步,看看哪些存储库理论存在。这与一个重复呈现的问题密切相关:您是否应该在 JPA 中每个表有一个存储库?
而正确且权威的答案是“NO”。存储库长久化并加载聚合。聚合是造成一个单元的一组对象,它应该始终保持统一。此外,它应该始终保持(和加载)在一起。它有一个对象,称为聚合根,它是惟一容许接触或援用聚合外部的对象。聚合根是传递给存储库以长久化聚合的内容。
这带来了一个问题:Spring Data JDBC 如何确定哪些是聚合的一部分,哪些不是?答案很简略:您能够通过遵循非瞬态援用从聚合根拜访的所有内容都是聚合的一部分。
思考到这一点,的行为是 OrderRepository 齐全正当的。OrderItem 实例是聚合的一部分,因而会被删除。Author 相同,实例不是 Book 聚合的一部分,因而不应被删除。所以他们不应该被 Book 类援用。
问题解决了。嗯,… 不是真的。咱们依然须要存储和拜访无关之间的关系的信息 Book 和 Author。答案能够再次在畛域驱动设计 (DDD) 中找到,它倡议应用 ID 而不是间接援用。这实用于各种多对 x 关系。
如果多个聚合援用同一个实体,则该实体不能成为援用它的聚合的一部分,因为它只能是一个聚合的一部分。因而,任何多对一和多对多关系都必须仅通过援用 id 来建模。
如果你利用这一点,你能够实现多项指标:
您分明地示意了聚合的边界。
您还齐全解耦(至多在应用程序的域模型中)波及的两个聚合。
这种拆散能够在数据库中以不同的形式示意:
放弃数据库的失常状态,包含所有外键。这意味着您必须确保以正确的程序创立和保留聚合。
应用提早束缚,仅在事务的提交阶段进行查看。这可能会实现更高的吞吐量。它还编纂了最终一致性的一个版本,其中“最终”与事务的完结相关联。这也容许援用从不存在的聚合,只有它只在事务期间产生。这对于防止大量基础设施代码只是为了满足外键和非空束缚可能很有用。
齐全删除外键,实现真正的最终一致性。
将援用的聚合保留在不同的数据库中,甚至可能是 No SQL 存储。
无论您采取何种拆散形式,即便是 Spring Data JDBC 强制执行的最低限度的拆散,也会激励您的应用程序模块化。此外,如果您尝试过迁徙一个真正有 10 年历史的单体应用程序,您就会理解它的价值。
应用 Spring Data JDBC,您能够对多对多关系进行建模,如下所示:
class Book {
private @Id Long id;
private String title;
private Set authors = new HashSet();
public void addAuthor(Author author) {
authors.add(createAuthorRef(author));
}
private AuthorRef createAuthorRef(Author author) {
Assert.notNull(author, “Author must not be null”);
Assert.notNull(author.id, “Author id, must not be null”);
AuthorRef authorRef = new AuthorRef();
authorRef.author = author.id;
return authorRef;
}
}
@Table(“Book_Author”)
class AuthorRef {
Long author;
}
class Author {
@Id Long id;
String name;
}
留神额定的类 (AuthorRef),它代表 Book 聚合对于作者的常识。它可能蕴含无关作者的其余聚合信息,而后这些信息实际上会在数据库中复制。思考到作者数据库可能与书籍数据库齐全不同,这有很多事件要做。
另请留神,作者集是一个公有字段,实例的 AuthorRef 实例化产生在公有办法中。所以聚合之外的任何货色都不能间接拜访它。Spring Data JDBC 绝不要求这样做,但 DDD 激励这样做。域将像这样应用:
@Test
public void booksAndAuthors() {
Author author = new Author();
author.name = “Greg L. Turnquist”;
author = authors.save(author);
Book book = new Book();
book.title = “Spring Boot”;
book.addAuthor(author);
books.save(book);
books.deleteAll();
assertThat(authors.count()).isEqualTo(1);
}
总结一下:Spring Data JDBC 不反对多对一或多对多关系。为了对这些进行建模,请应用 ID。这激励了畛域模型的洁净模块化。它还打消了人们必须解决的一整套问题,并学习推理这种映射是否可行。
依照相似的思路,防止双向依赖。聚合内的援用从聚合根到元素。聚合之间的援用由一个方向的 ID 示意。此外,如果您须要反向导航,请应用存储库中的查询方法。这使得明确无误地明确哪个聚合负责保护援用。
以下是示例应用的数据库构造。
Purchase_Order (
id
shipping_address
)
Order_Item (
purchase_order
quantity
product
);
Book (
id
title
)
Author (
id
name
)
Book_Author (
book
author
)
残缺的示例代码可在 Spring 中国教育管理中心(Spring 认证)数据示例库拜访!