2016-12-26 152 views
4

如果我们遵循DDD原则,则一个聚合根应该仅具有对另一个聚合根的引用(通过id)。DDD:如何正确实现与JPA/Hibernate实体的关系?

例子:

// Product Aggregate root 
class Product { 

    // References to categories Aggregate Roots (to ids) 
    Set<Long> categoryIds; 
} 

但如何才能将其与JPA实现/休眠? 在JPA中,如果我们希望有,例如,一对多的关系,我们把它定义为如下:

// 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

'级联= CascadeType.ALL'正在朝着错误的方向前进,因为将两个总计关联到同一个事务 –

回答

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) 

和cour瑟毫不奇怪相对于它们的内容:

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) 

另外我想避免当你使用关系数据库存储在一个逗号分隔的列表关系。它导致不健康的数据库设计,并会导致您在某个时候头痛。

+0

但是'Product'保存了一组'Category'。根据DDD,这是我们应该首先避免的。 –

+0

是的,在这种情况下,“类别”应该只是CategoryId – Teimuraz

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; 
} 
+0

谢谢,但在第一种方法中,您将在哪里存储产品的类别参考? – Teimuraz

+0

@moreo只需将'@Column private Long productId'添加到'Category' –