Spring Boot+JPA實現DDD(三)
上一篇我們有了Product這個聚合根。前面已經分析過,一個商品可以包含一個或多個課程明細。課程明細可以單獨編輯,有自己的生命週期,課程明細也是一個聚合根。
- 在
domain.model
包下建立courseitem.CourseItem
類,內容如下:
@Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) public class CourseItem implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "item_no", length = 32, nullable = false, unique = true) private String itemNo; @Column(name = "name", length = 64, nullable = false) private String name; @Column(name = "category_id", nullable = false) private Integer categoryId; @Column(name = "price", precision = 10, scale = 2) private BigDecimal price; @Column(name = "remark", length = 256) private String remark; @Column(name = "study_type", nullable = false) private Integer studyType; @Column(name = "period") private Integer period; @Temporal(TemporalType.TIMESTAMP) @Column(name = "deadline") private Date deadline; public static CourseItem of(String itemNo, String name, Integer categoryId, BigDecimal price, String remark, Integer studyType, Integer period, Date deadline) { return new CourseItem(null, itemNo, name, categoryId, price, remark, studyType, period, deadline); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; CourseItem that = (CourseItem) o; return Objects.equal(itemNo, that.itemNo); } @Override public int hashCode() { return Objects.hashCode(itemNo); } }
跟產品類似,課程明細也有名稱,價格,唯一的明細編碼,課程明細有2種有效學習期,按截止日期或者按下單後xx月。
產品跟課程明細是多對多的關係,這個關係怎麼處理?是不是要配置 @ManyToMany
啊?
不要,因為模型裡的程式碼應該是框架無關的。 @ManyToMany
是hibernate的註解,我們應該避免使用JPA具體實現的註解,而應該多用JPA通用的註解。
也許你會反駁我說,既然這樣,Entity類就應該保持純潔性,為什麼我還在Entity類裡使用JPA相關的註解?JPA雖然不是框架,但是在實體類裡寫@Column
這種DB相關的東西真的好嗎?
這是個好問題。用JPA的原因是不給自己找麻煩。既然使用了Spring這個框架,框架提供了Spring Data JPA這麼成熟好用的工具我們為什麼不用呢。
沒必要自己再寫一套東西,把非常純潔的實體物件轉成持久化物件後再持久化它。 有種重複造輪子的感覺不說,還容易出錯。
個人覺得實體里加一些JPA的註解是可以忍受的,不是什麼很嚴重的問題。油管上看到的視訊,有人問過大神這個問題,大神就是這麼回答的。
我們知道要描述多對多的關係需要維護一張中間表。@Entity註解的類可以直接生成表,那麼商品-明細這個中間表怎麼生成呢?
需要使用JPA的2個註解。@Embeddable
和@ElementCollection
。
- 在product包下新建
ProductCourseItem
類,內容如下:
@Embeddable @Getter @EqualsAndHashCode @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) public class ProductCourseItem implements Serializable { @Column(name = "course_item_no", length = 32, nullable = false) private String courseItemNo; @Column(name = "new_price", precision = 10, scale = 2) private BigDecimal newPrice; public static ProductCourseItem of(String courseItemNo, BigDecimal retakePrice) { return new ProductCourseItem(courseItemNo, retakePrice); } }
注意,ProductCourseItem是一個值物件,值物件是不能被修改的。所以這個類只提供了getter,並沒有提供setter。
Product
類新增如下:
@ElementCollection(targetClass = ProductCourseItem.class)
@CollectionTable(
name = "product_course_item",
uniqueConstraints = @UniqueConstraint(columnNames = {"product_no", "course_item_no"}),
joinColumns = {@JoinColumn(name = "product_no", referencedColumnName = "product_no")}
)
private Set<ProductCourseItem> productCourseItems = new HashSet<>();
並且修改of
工廠方法(這裡也可以看到使用Lombok的好處之一,不用頻繁地重新生成有參建構函式和getter了):
public static Product of(String productNo, String name, BigDecimal price, Integer categoryId, Integer productStatus, String remark, Boolean allowAcrossCategory, Set<ProductCourseItem> productCourseItems) {
return new Product(null, productNo, name, price, categoryId, productStatus, remark, allowAcrossCategory, productCourseItems);
}
商品的課程明細不能重複,所以我們使用Set集合。
中間表的名稱是product_course_item
,並且給中間表加一個唯一複合索引——商品的product_no
和明細的course_item_no
組成一個唯一索引。
到這裡也許你會奇怪,中間表product_course_item
裡並沒有宣告product_no
這個欄位啊。 別擔心,因為Product類裡有一個@ElementCollection
。這個註解會幫我們在中間表裡生成product_no
這個欄位。
- 啟動專案,hibernate會刪除之前的表,重新生成新的表結構:
courseitem表:
中間表:
中間表有了一個唯一複合索引,這樣可以在db層面上保證不會重複。
中間表為什麼會有一個new_price
欄位?
因為不同的課程明細在不同的商品下價格不同。