掃二維碼與項(xiàng)目經(jīng)理溝通
我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營(yíng)咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
在 DDD 中,Repository 是一個(gè)非常重要的概念,它是領(lǐng)域?qū)拥囊粋€(gè)組件,用來管理聚合根的生命周期和持久化。
創(chuàng)新互聯(lián)2013年開創(chuàng)至今,先為猇亭等服務(wù)建站,猇亭等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為猇亭企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問題。
DDD 是由領(lǐng)域?qū)ο蟪休d業(yè)務(wù)邏輯,所有的業(yè)務(wù)操作均在模型對(duì)象上完成,同一聚合上不同的業(yè)務(wù)操作構(gòu)成了聚合的生命周期。
我們以訂單為例,如下圖所示:
整個(gè)流程很好的體現(xiàn)了 Order 聚合根的生命周期。
假設(shè),有一臺(tái)非常牛逼的計(jì)算機(jī),計(jì)算資源無限、內(nèi)存大小無限、永不掉電、永不宕機(jī),那最簡(jiǎn)單高效的方式便是將模型對(duì)象全部放在內(nèi)存中。
但,現(xiàn)實(shí)不存在這樣的機(jī)器,我們不得不將內(nèi)存對(duì)象寫入磁盤,下次使用時(shí),在將其從磁盤讀入到內(nèi)存。
整體結(jié)構(gòu)如下圖所示:
和上圖相比,具有如下特點(diǎn):
相對(duì)全內(nèi)存版本確實(shí)增加了不小的復(fù)雜性,為了更好的對(duì)這些復(fù)雜性進(jìn)行管理,引入 Repository 模式。
在領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(DDD)中,Repository 是一種設(shè)計(jì)模式,它是用來存儲(chǔ)領(lǐng)域?qū)ο蟮娜萜?。它提供了一種統(tǒng)一的方式來查詢和存儲(chǔ)領(lǐng)域?qū)ο?。Repository提供了對(duì)底層數(shù)據(jù)存儲(chǔ)的抽象,允許應(yīng)用程序在沒有直接與數(shù)據(jù)存儲(chǔ)技術(shù)交互的情況下訪問數(shù)據(jù),同時(shí)該抽象允許在不修改應(yīng)用程序代碼的情況下更改數(shù)據(jù)存儲(chǔ)技術(shù)。
【注】在 DDD 中,Repository 并不是一個(gè) DAO,它的職責(zé)比 DAO 要多得多,它管理的是整個(gè)聚合根,而不是單個(gè)實(shí)體對(duì)象。同時(shí),Repository 還需要提供一些查詢接口,用來查詢聚合根的狀態(tài)。
Repository 主要用于完成對(duì)聚合根生命周期的管理,所以必須提供三組操作:
有人會(huì)說,這和 DAO 沒啥區(qū)別吧?。?!
DAO 是單表單實(shí)體操作,Repository 操作的是整個(gè)聚合甚至包括繼承關(guān)系,這就是最大的區(qū)別。也就是Repository 必須能夠:
既支持組合又支持繼承,DAO 就沒辦法更好的承載了。
那就完了嗎?并沒有?。?!
聚合根是一個(gè)對(duì)象組,包含各種關(guān)系,并不是每個(gè)業(yè)務(wù)操作都需要聚合根內(nèi)的所有實(shí)體。
舉個(gè)例子,在電商訂單聚合根內(nèi),包括:
在改價(jià)流程里,需要修改 Order、OrderItem、Pay 三組實(shí)體。
在更新地址流程里,僅需要修改 Address 和 Order 兩組實(shí)體。
為了滿足不同的業(yè)務(wù)場(chǎng)景,Repository 需要具備兩個(gè)高級(jí)特性:
總體來說,能夠具備以下特性的 Repository 才是好的 Repository:
綜合調(diào)研各類 ORM 框架,只有 JPA 具備上述特性,而且和 DDD 是絕配。
組合是一種面向?qū)ο缶幊痰闹匾拍睿敢粋€(gè)類的對(duì)象可以將其他類的對(duì)象作為自己的組成部分。組合在DDD中使用場(chǎng)景最為廣泛,這也是聚合的主要工作方式。也就是將一組對(duì)象保存到存儲(chǔ)引擎,然后在從存儲(chǔ)引擎中獲取完整的對(duì)象組。
從數(shù)據(jù)視角,組合關(guān)系存在兩個(gè)維度:
兩者組合,情況更加復(fù)雜,會(huì)產(chǎn)生:
聚合根是一組對(duì)象訪問的入口,聚合內(nèi)的所有操作都必須通過聚合根進(jìn)行,所以,聚合根于其他實(shí)體的關(guān)系只能是 一對(duì)多 和 一對(duì)一;同時(shí),所有的業(yè)務(wù)操作都是從聚合根發(fā)起,通過聚合根能關(guān)聯(lián)到內(nèi)部實(shí)體即可,因此也不存在雙向。綜上所述,DDD 對(duì)組合進(jìn)行了大量簡(jiǎn)化,實(shí)際工作中主要涉及:
通過外鍵的方式實(shí)現(xiàn)單向一對(duì)一關(guān)系,需要在主表中添加一個(gè)指向另一個(gè)表的外鍵,通過外鍵信息獲取關(guān)聯(lián)數(shù)據(jù)。
實(shí)體如下:
// 聚合根實(shí)現(xiàn)
@Entity
@Table(name = "order_info")
public class Order{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 增加 @OneToOne 注解
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Pay pay;
// 增加 @OneToOne 注解
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Address address;
// 忽略其他屬性
}
// Pay 實(shí)體實(shí)現(xiàn)
@Entity
@Table(name = "pay_info")
public class Pay {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 忽略其他屬性
}
// Address 實(shí)現(xiàn)
@Entity
@Table(name = "address")
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 忽略其他屬性
}插入記錄后,order_Infor 表數(shù)據(jù)如下
其中:
可見,執(zhí)行時(shí)先插入 address 和 pay 獲取主鍵后,在插入到 order_info 表,從而維護(hù)外鍵的有效性。
實(shí)體定義如下:
// 聚合根實(shí)體
@Entity
@Table(name = "order_info")
public class Order{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 添加 @OneToMany 注解
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
// 指定多端的關(guān)聯(lián)列(如果不指定,會(huì)使用第三張表來保存關(guān)系
@JoinColumn(name = "order_id")
private List orderItems = new ArrayList<>();
// 忽略其他屬性
}
// OrderItem 實(shí)現(xiàn)
@Entity
@Table(name = "order_item")
public class OrderItem {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 忽略其他屬性
} 插入記錄后,表數(shù)據(jù)如下:
order 表數(shù)據(jù):
order+item表數(shù)據(jù):
其中 order_item 表中的 order_id 指向 order_info 表的主鍵。
繼承是面向?qū)ο缶幊痰暮诵奶匦?,但這一特性確與數(shù)據(jù)庫的關(guān)系模型產(chǎn)生巨大阻抗。
JPA 中提供了三種繼承模型,包括:
為了更好的對(duì)比各種策略,我們以一個(gè)業(yè)務(wù)場(chǎng)景為案例進(jìn)行分析。
在優(yōu)惠計(jì)算過程中,需要根據(jù)不同的配置策略對(duì)當(dāng)前用戶進(jìn)行驗(yàn)證,以判斷用戶是否能夠享受優(yōu)惠,常見的驗(yàn)證策略有:
為了保障系統(tǒng)有良好的擴(kuò)展性,引入策略模式,整體設(shè)計(jì)如下:
那接下來便是將這些實(shí)現(xiàn)類存儲(chǔ)到數(shù)據(jù)庫,然后在方便的查詢出來。
單表繼承非常簡(jiǎn)單,也最為實(shí)用,數(shù)據(jù)庫表只有一張,通過一列辨別字段來區(qū)別不同類別的實(shí)體。
它的使用涉及幾個(gè)關(guān)鍵注解:
相關(guān)實(shí)體代碼如下:
// 父類
@Entity
// 單表表名
@Table(name = "activity_matcher")
// 當(dāng)前策略為單表策略
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
// activity_type 列用于存儲(chǔ)對(duì)應(yīng)的類型
@DiscriminatorColumn(name = "activity_type")
@Data
public abstract class BaseActivityMatcher implements ActivityMatcher {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private ActivityMatcherStatus status = ActivityMatcherStatus.ENABLE;
}
// SpecifyUserMatcher 實(shí)現(xiàn)類
@Entity
// 使用 SpecifyUser 作為標(biāo)記
@DiscriminatorValue("SpecifyUser")
public class SpecifyUserMatcher
extends BaseActivityMatcher
implements ActivityMatcher {
// 省略屬性和方法
}
// SexMatcher 實(shí)現(xiàn)類
@Entity
// 使用 Sex 作為標(biāo)記
@DiscriminatorValue("Sex")
public class SexMatcher
extends BaseActivityMatcher
implements ActivityMatcher {
// 省略屬性和方法
}
@Entity
// 使用 VipLevel 作為標(biāo)記
@DiscriminatorValue("VipLevel")
public class VipLevelMatcher
extends BaseActivityMatcher
implements ActivityMatcher {
// 省略屬性和方法
}每種策略保存一條數(shù)據(jù)后,數(shù)據(jù)庫表activity_matcher數(shù)據(jù)如下圖所示:
其中:
單表繼承策略,最大的優(yōu)點(diǎn)便是簡(jiǎn)單,但由于父類實(shí)體和子類實(shí)體共用一張表,因此表中會(huì)有很多空字段,造成浪費(fèi)。
Joined策略,父類實(shí)體和子類實(shí)體分別對(duì)應(yīng)數(shù)據(jù)庫中不同的表,子類實(shí)體的表中只存在其擴(kuò)展的特殊屬性,父類的公共屬性保存在父類實(shí)體映射表中。
它的使用涉及幾個(gè)關(guān)鍵注解:
相關(guān)實(shí)體代碼如下:
// 父類
@Entity
@Table(name = "activity_joined_matcher")
// 當(dāng)前策略為Joined策略
@Inheritance(strategy = InheritanceType.JOINED)
@Data
public abstract class BaseActivityMatcher implements ActivityMatcher {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private ActivityMatcherStatus status = ActivityMatcherStatus.ENABLE;
}
// SpecifyUserMatcher 實(shí)現(xiàn)類
@Entity
@Table(name = "user_joined_matcher")
@PrimaryKeyJoinColumn(name = "matcher_id")
public class SpecifyUserMatcher
extends BaseActivityMatcher
implements ActivityMatcher {
// 省略屬性和方法
}
// SexMatcher 實(shí)現(xiàn)類
@Entity(name = "JoinedSexMatcher")
@Table(name = "sex_joined_matcher")
@PrimaryKeyJoinColumn(name = "matcher_id")
public class SexMatcher
extends BaseActivityMatcher
implements ActivityMatcher {
// 省略屬性和方法
}
// VipLevelMatcher 實(shí)現(xiàn)類
@Entity(name = "JoinedVipLevelMatcher")
@Table(name = "vip_joined_matcher")
@PrimaryKeyJoinColumn(name = "matcher_id")
public class VipLevelMatcher
extends BaseActivityMatcher
implements ActivityMatcher {
// 省略屬性和方法
}每種策略保存一條數(shù)據(jù)后,各個(gè)表數(shù)據(jù)如下:
activity_joined_matcher 如下:
user_joined_matcher 如下:
sex_joined_matcher 如下:
vip_joined_matcher 如下:
具有以下特點(diǎn):
從表數(shù)據(jù)上可以看出,Joined策略可以減少冗余的空字段,但是查詢時(shí)需要多表連接,效率較低。
TABLE_PER_CLASS 策略,父類實(shí)體和子類實(shí)體每個(gè)類分別對(duì)應(yīng)一張數(shù)據(jù)庫中的表,子類表中保存所有屬性,包括從父類實(shí)體中繼承的屬性。
它的使用主要涉及以下幾個(gè)點(diǎn):
相關(guān)實(shí)體代碼如下:
// 父類
@Entity
// 當(dāng)前策略為 TABLE_PER_CLASS 策略
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@Data
public abstract class BaseActivityMatcher implements ActivityMatcher {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
// 省略屬性和方法
}
// SpecifyUserMatcher 實(shí)現(xiàn)類
@Entity
@Table(name = "user_per_class_matcher")
public class SpecifyUserMatcher
extends BaseActivityMatcher
implements ActivityMatcher {
// 省略屬性和方法
}
// SexMatcher 實(shí)現(xiàn)類
@Entity
@Table(name = "sex_per_class_matcher")
public class SexMatcher
extends BaseActivityMatcher
implements ActivityMatcher {
// 省略屬性和方法
}
// VipLevelMatcher 實(shí)現(xiàn)類
@Entity
@Table(name = "vip_per_class_matcher")
public class VipLevelMatcher
extends BaseActivityMatcher
implements ActivityMatcher {
// 省略屬性和方法
}每種策略保存一條數(shù)據(jù)后,各個(gè)表數(shù)據(jù)如下:
user_per_class_matcher 如下:
sex_per_class_matcher 如下:
vip_per_class_matcher 如下:
具有以下特點(diǎn):
從表數(shù)據(jù)上可以看出,子類中有相同的屬性,則每個(gè)子類都需要?jiǎng)?chuàng)建一遍,會(huì)導(dǎo)致表結(jié)構(gòu)冗余,影響查詢效率。
三種策略各具特色,都有最佳應(yīng)用場(chǎng)景,簡(jiǎn)單如下:
子類的數(shù)據(jù)量不大,且與父類的屬性差別不大;
?可以使用單表繼承策略來減少表的數(shù)量;
當(dāng)子類過多或數(shù)據(jù)量過大時(shí),Joined 和 table per class 在查詢場(chǎng)景存在明顯的性能問題,這個(gè)需要格外注意。
JPA提供了兩種加載策略:立即加載和延遲加載。
如果默認(rèn)策略不符合要求,可以通過手工設(shè)置注解上 fetch 配置,對(duì)默認(rèn)策略進(jìn)行重寫。
立即加載會(huì)在查詢主實(shí)體類的同時(shí)查詢它所有關(guān)聯(lián)實(shí)體類,并綁定到實(shí)體屬性上。
立即加載的好處是能夠提高查詢效率,因?yàn)椴恍枰~外的查詢操作。但是,使用立即加載會(huì)增加數(shù)據(jù)庫的查詢負(fù)擔(dān),查詢出所有關(guān)聯(lián)實(shí)體類,會(huì)導(dǎo)致查詢結(jié)果的數(shù)據(jù)量比較大。
實(shí)體配置如下:
@Entity
@Table(name = "order_info")
public class Order{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinColumn(name = "order_id")
private List orderItems = new ArrayList<>();
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Pay pay;
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Address address;
// 忽略其他屬性和方法
} 測(cè)試腳本如下:
Order order = this.orderRepository.findById(this.order.getId()).get();
Assertions.assertNotNull(order);
System.out.println("訪問 item");
Assertions.assertEquals(3, order.getOrderItems().size());
System.out.println("訪問 address");
Assertions.assertNotNull(order.getAddress().getDetail());
System.out.println("訪問 pay");
Assertions.assertNotNull(order.getPay().getPrice());日志輸出如下:
Hibernate: select order0_.id as id1_3_0_, order0_.address_id as address_6_3_0_, order0_.pay_id as pay_id7_3_0_, order0_.status as status2_3_0_, order0_.total_price as total_pr3_3_0_, order0_.total_selling_price as total_se4_3_0_, order0_.user_id as user_id5_3_0_, address1_.id as id1_2_1_, address1_.detail as detail2_2_1_, orderitems2_.order_id as order_id6_4_2_, orderitems2_.id as id1_4_2_, orderitems2_.id as id1_4_3_, orderitems2_.price as price2_4_3_, orderitems2_.product_id as product_3_4_3_, orderitems2_.quantity as quantity4_4_3_, orderitems2_.selling_price as selling_5_4_3_, pay3_.id as id1_5_4_, pay3_.price as price2_5_4_ from order_info order0_ left outer join address address1_ on order0_.address_id=address1_.id left outer join order_item orderitems2_ on order0_.id=orderitems2_.order_id left outer join pay_info pay3_ on order0_.pay_id=pay3_.id where order0_.id=?
訪問 item
訪問 address
訪問 pay從日志輸出可見:
延遲加載是指在進(jìn)行數(shù)據(jù)庫查詢時(shí),并不會(huì)立即查詢關(guān)聯(lián)表數(shù)據(jù),而是要等到使用時(shí)才會(huì)去查,這樣可以避免不必要的數(shù)據(jù)庫查詢,提高查詢效率。
延遲加載又分為兩種情況:
在此,重點(diǎn)介紹表間關(guān)聯(lián)的延遲加載:
實(shí)體代碼如下所示:
@Entity
@Table(name = "order_info")
public class Order{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "order_id")
private List orderItems = new ArrayList<>();
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Pay pay;
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Address address;
// 忽略其他字段和方法
} 查詢代碼如下:
Order order = this.orderRepository.findById(this.order.getId()).get();
Assertions.assertNotNull(order);
System.out.println("訪問 item");
Assertions.assertEquals(3, order.getOrderItems().size());
System.out.println("訪問 address");
Assertions.assertNotNull(order.getAddress().getDetail());
System.out.println("訪問 pay");
Assertions.assertNotNull(order.getPay().getPrice());控制臺(tái)輸出如下:
Hibernate: select order0_.id as id1_3_0_, order0_.address_id as address_6_3_0_, order0_.pay_id as pay_id7_3_0_, order0_.status as status2_3_0_, order0_.total_price as total_pr3_3_0_, order0_.total_selling_price as total_se4_3_0_, order0_.user_id as user_id5_3_0_ from order_info order0_ where order0_.id=?
訪問 item
Hibernate: select orderitems0_.order_id as order_id6_4_0_, orderitems0_.id as id1_4_0_, orderitems0_.id as id1_4_1_, orderitems0_.price as price2_4_1_, orderitems0_.product_id as product_3_4_1_, orderitems0_.quantity as quantity4_4_1_, orderitems0_.selling_price as selling_5_4_1_ from order_item orderitems0_ where orderitems0_.order_id=?
訪問 address
Hibernate: select address0_.id as id1_2_0_, address0_.detail as detail2_2_0_ from address address0_ where address0_.id=?
訪問 pay
Hibernate: select pay0_.id as id1_5_0_, pay0_.price as price2_5_0_ from pay_info pay0_ where pay0_.id=?從日志輸出可知,關(guān)聯(lián)實(shí)體只有在屬性被訪問時(shí)才會(huì)觸發(fā)自動(dòng)加載。
延遲加載在聚合更新時(shí)極為重要,面對(duì)一個(gè)大聚合,每次修改只會(huì)涉及少量相關(guān)聯(lián)的實(shí)體,由于延遲加載機(jī)制的保障,對(duì)于那些沒有必要訪問的實(shí)體并不會(huì)執(zhí)行實(shí)際的加載操作,從而大幅提升性能。
簡(jiǎn)單理解按需更新,就是只有在有必要時(shí)才會(huì)對(duì)數(shù)據(jù)進(jìn)行更新。
按需更新可以分為兩個(gè)場(chǎng)景:
在數(shù)據(jù)保存時(shí),JPA 會(huì)自動(dòng)識(shí)別發(fā)生變更的實(shí)體,僅對(duì)變更實(shí)體執(zhí)行 update 語句。
測(cè)試代碼如下:
Order order = this.orderRepository.findById(this.order.getId()).get();
order.getOrderItems().size(); // 獲取未更新
order.getPay().getPrice(); // 獲取未更新
order.getAddress().setDetail("新地址"); // 獲取并更新
System.out.println("更新數(shù)據(jù)");
this.orderRepository.save(order);控制臺(tái)輸出如下:
Hibernate: select order0_.id as id1_3_0_, order0_.address_id as address_6_3_0_, order0_.pay_id as pay_id7_3_0_, order0_.status as status2_3_0_, order0_.total_price as total_pr3_3_0_, order0_.total_selling_price as total_se4_3_0_, order0_.user_id as user_id5_3_0_ from order_info order0_ where order0_.id=?
Hibernate: select orderitems0_.order_id as order_id6_4_0_, orderitems0_.id as id1_4_0_, orderitems0_.id as id1_4_1_, orderitems0_.price as price2_4_1_, orderitems0_.product_id as product_3_4_1_, orderitems0_.quantity as quantity4_4_1_, orderitems0_.selling_price as selling_5_4_1_ from order_item orderitems0_ where orderitems0_.order_id=?
Hibernate: select pay0_.id as id1_5_0_, pay0_.price as price2_5_0_ from pay_info pay0_ where pay0_.id=?
Hibernate: select address0_.id as id1_2_0_, address0_.detail as detail2_2_0_ from address address0_ where address0_.id=?
更新數(shù)據(jù)
Hibernate: update address set detail=? where id=?從日志輸出可見:
只更新變更字段,是指只更新實(shí)體類中有變化的字段,而不是全部字段。為了實(shí)現(xiàn)按需更新,需要在實(shí)體類中使用@DynamicUpdate注解,表示只更新有變化的字段。
實(shí)體代碼見:
@Entity
@Table(name = "order_info")
@DynamicUpdate
public class Order{
// 其他忽略
}測(cè)試代碼如下:
Order order = this.orderRepository.findById(this.order.getId()).get();
order.setUserId(RandomUtils.nextLong()); // 僅更新 user id
System.out.println("更新數(shù)據(jù)");
this.orderRepository.save(order);控制臺(tái)輸出如下:
Hibernate: select order0_.id as id1_3_0_, order0_.address_id as address_6_3_0_, order0_.pay_id as pay_id7_3_0_, order0_.status as status2_3_0_, order0_.total_price as total_pr3_3_0_, order0_.total_selling_price as total_se4_3_0_, order0_.user_id as user_id5_3_0_ from order_info order0_ where order0_.id=?
更新數(shù)據(jù)
Hibernate: update order_info set user_id=? where id=?如果移除 @DynamicUpdate 注解,控制臺(tái)輸出如下:
Hibernate: select order0_.id as id1_3_0_, order0_.address_id as address_6_3_0_, order0_.pay_id as pay_id7_3_0_, order0_.status as status2_3_0_, order0_.total_price as total_pr3_3_0_, order0_.total_selling_price as total_se4_3_0_, order0_.user_id as user_id5_3_0_ from order_info order0_ where order0_.id=?
更新數(shù)據(jù)
Hibernate: update order_info set address_id=?, pay_id=?, status=?, total_price=?, total_selling_price=?, user_id=? where id=?對(duì)比輸出可知:使用@DynamicUpdate注解后,當(dāng)修改實(shí)體類中的某個(gè)字段時(shí),JPA會(huì)自動(dòng)將該字段標(biāo)記為“臟數(shù)據(jù)”,并只更新標(biāo)記為“臟數(shù)據(jù)”的字段,這樣可以減少數(shù)據(jù)庫的IO操作,提高更新效率。
本章從 DDD 聚合生命周期講起,當(dāng)我們面對(duì)一組高內(nèi)聚對(duì)象時(shí),如何更好的對(duì)這一對(duì)象組進(jìn)行維護(hù)。
從高內(nèi)聚對(duì)象組視角需要支持:
從系統(tǒng)性能角度需要支持:
JPA 與 DDD 的==聚合寫== 是絕配,但在 “讀” 場(chǎng)景 往往會(huì)引發(fā)各種性能問題。這也是很多公司棄用 JPA 而選擇 MyBatis 的主要原因,就其本質(zhì)并不是框架的錯(cuò),而是將框架用在了錯(cuò)誤的場(chǎng)景。
對(duì)于 Command 和 Query 分離架構(gòu),最佳組合是:

我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營(yíng)咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流