如果我们遵循DDD原则,一个聚合根应该只有(通过id)引用到另一个聚合根。
示例:
// Product Aggregate root
class Product {
// References to categories Aggregate Roots (to ids)
Set<Long> categoryIds;
}
但是如何用JPA / Hibernate实现呢? 在jpa中,如果我们想要,例如,OneToMany关系,我们将其定义如下:
// Product Aggregate root
class Product {
// Holds category aggregate roots
@OneToMany(mappedBy = "", cascade = CascadeType.ALL)
Set<Category> categories;
}
因此,JPA-s方法本身将保留类别聚合根,这在DDD中是不推荐的。
您如何设计与JPA的关系,但符合DDD原则?
P.S。:我正在考虑使用字符串类型的categories
属性并保留逗号分隔的类别ID列表,但有没有更好的解决方案?
答案 0 :(得分:2)
这是一个很好的问题,但您的示例不适用。逻辑上, 类别 不是 产品 汇总根的一部分。 产品 和 类别 都具有全局ID。删除 产品 时,您不会删除它所属的 类别 ,并且在删除< strong> 类别 ,您不会删除它拥有的所有 产品 。
Google图书免费提供一个概述Alex Evans's DDD book中汇总使用情况的页面。这是有关聚合的内容:
•根ENTITY具有全球性,并最终负责 用于检查不变式。
•根实体具有全球性。 边界内的实体具有本地身份,仅在内部具有唯一性 聚集。
•AGGREGATE边界之外的任何东西都不能容纳 引用除根ENTITY之外的任何内容。根 ENTITY可以将对内部ENTITIES的引用传递给其他对象, 但是这些对象只能暂时使用它们,并且它们可能无法容纳 继续参考。根可以将VALUE OBJECT的副本交给 另一个对象,发生什么都不重要,因为它是 只是一个VALUE,将不再与 骨料。
•作为上一条规则的推论,只有AGGREGATE 根可以直接通过数据库查询获得。所有其他 必须通过遍历关联找到对象。
•内的物体 AGGREGATE可以保留对其他AGGREGATE根目录的引用。
•删除操作必须删除位于AGGREGATE边界内的所有内容 一旦。 (通过垃圾收集,这很容易。因为没有 外部引用除根以外的任何内容,请删除根并 其他所有东西都将被收集。)
•在AGGREGATE边界内提交对任何对象的更改>时,必须满足整个AGGREGATE的所有不变量。
关于JPA的实现,我想说多种方法都可以使用:
@Embeddable
似乎是防弹解决方案,因为三方成员将没有ID。 @OneToMany, @JoinTable
等-在您不通过其他实体的ID引用成分的情况下也可以使用。不过,在实施过程中需要对此进行保险,并且可能会偶然违反。答案 1 :(得分:1)
您可以使用连接表来避免汇总根的类别,如下所示:
@Entity
public class Product {
@Id
@GeneratedValue
private int id;
@OneToMany
@JoinTable
private Set<Category> categories;
// constructor, getters, setters, etc...
}
@Entity
public class Category {
@Id
@GeneratedValue
private int id;
// constructor, getters, setters, etc...
}
作为一个例子,我将一些插在一起:
for (int n = 0; n < 3; ++n) {
categoryRepository.save(new Category());
}
Set<Category> categories = categoryRepository.findAll();
productRepository.save(new Product(categories));
结果如下(你没有指定你的DBMS,所以我只是假设...)MySQL:
MariaDB [so41336455]> show tables;
+----------------------+
| Tables_in_so41336455 |
+----------------------+
| category |
| product |
| product_categories |
+----------------------+
3 rows in set (0.00 sec)
MariaDB [so41336455]> describe category; describe product; describe product_categories;
+-------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
+-------+---------+------+-----+---------+----------------+
1 row in set (0.00 sec)
+-------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
+-------+---------+------+-----+---------+----------------+
1 row in set (0.00 sec)
+---------------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------------+---------+------+-----+---------+-------+
| product_id | int(11) | NO | PRI | NULL | |
| categories_id | int(11) | NO | PRI | NULL | |
+---------------+---------+------+-----+---------+-------+
2 rows in set (0.00 sec)
当然,对他们的内容并不感到惊讶:
MariaDB [so41336455]> select * from category; select * from product; select * from product_categories;
+----+
| id |
+----+
| 1 |
| 2 |
| 3 |
+----+
3 rows in set (0.00 sec)
+----+
| id |
+----+
| 1 |
+----+
1 row in set (0.00 sec)
+------------+---------------+
| product_id | categories_id |
+------------+---------------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
+------------+---------------+
3 rows in set (0.00 sec)
当您使用关系数据库时,我还会避免将关系存储在以逗号分隔的列表中。它会导致不健康的数据库设计,并会在某些时候让您头疼。
答案 2 :(得分:0)
在聚合之间导航时,最好坚持使用标识引用。在调用聚合行为之前,使用服务加载所需的对象。例如:
public class MyProductApplicationService {
...
@Transactional
public void loadDependentDataAndCarryOutAggregateAction(Long productId, Long categoryId) {
Product product = productRepository.findOne(productId);
Category category = categoryRepository.findOne(categoryId);
product.doActionThatNeedsFullCategoryAndMayModifyProduct(category);
}
}
如果这太麻烦,那么至少不要将交易从一个聚合跨越到另一个聚合:
class Product {
@OneToMany(mappedBy = "product")
Set<Category> categories;
}
public class Category {
@ManyToOne
@JoinColumn(name = "productid", insertable = false, updatable = false)
private Product product;
}