Spring Boot+JPA實現DDD(三)


構建多對多關系

上一篇我們有了Product這個聚合根。前面已經分析過,一個商品可以包含一個或多個課程明細。課程明細可以單獨編輯,有自己的生命周期,課程明細也是一個聚合根。

  1. 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

  1. 在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這個字段。

為什么不在Product里直接引用CourseItem呢?
聚合根可以直接引用實體,值對象。 不能直接引用其它聚合根,要通過唯一標識來關聯。

就算用唯一標識來關聯,為什么不用物理主鍵而用業務主鍵關聯呢?
哈哈,能問出這個問題,說明你真的在認真看我的文章了。通常我們都使用物理主鍵來做關聯。 但其實db規范里並沒有強制要求我們使用物理主鍵來做關聯。
正如我在上一篇文章里說的,使用業務主鍵有很多好處,用業務主鍵做關聯除了多占了一些空間外,我實在想不通有什么不好?

  1. 啟動項目,hibernate會刪除之前的表,重新生成新的表結構:
    courseitem表:

中間表:

中間表有了一個唯一復合索引,這樣可以在db層面上保證不會重復。

  1. 問題解答

①中間表為什么會有一個new_price字段?

因為同一個課程明細在不同的商品下價格不同。

ProductCourseItem類的equals方法是由@EqualsAndHashCode注解實現的。ProductCourseItem類只有2個字段,那么注解自動生成的equals方法里只會比較這2個字段。 為什么沒有算上productNo?

好問題。 不需要算上productNo,因為ProductCourseItem不會單獨使用,它只會存在於某個Product里,這天然地保證了它們的productNo都是一樣的,所以equals方法也就沒必要算上productNo了。

源碼下載:productcenter3.zip


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM