JPA的CascadeType的解釋

2020-05-12更新:因為我們的業(yè)務(wù)并不允許使用jpa的級聯(lián)操作以及mysql的外鍵隆嗅,所以只是學(xué)習(xí)了這篇文章但是并沒有試過其中的代碼。今天想起來把代碼測試了一遍儡率,并修正了文中的一些說法挂据。


這是我看過的解釋的最清楚的一篇文章。本來只想放個鏈接儿普,但是怕鏈接原文被刪了崎逃,所以把原文也復(fù)制過來并重新排版。感謝作者眉孩。
原文鏈接:http://westerly-lzh.github.io/cn/2014/12/JPA-CascadeType-Explaining/


1. 背景

??網(wǎng)上關(guān)于JPA的CascadeType講解很多个绍,但幾乎都說的很模糊。本文試圖使用一個具體的例子來說明CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.REMOVE, CascadeType.ALL 具體區(qū)別浪汪。
??我們使用一個訂單和訂單項的例子巴柿。該例子在網(wǎng)絡(luò)上那些介紹JPA CascadeType用法的文章中廣為流傳。

2. 使用的代碼

/**
 * 訂單
 */
@Entity
@Table(name="t_order")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    @Column
    private String name;
    @OneToMany(mappedBy="order",targetEntity=Item.class,fetch=FetchType.LAZY)
    private List<Item> items;
}

/**
 *訂單物品
 */
@Entity
@Table(name="t_item")
public class Item {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    @Column
    private String name;
    @ManyToOne(fetch=FetchType.LAZY,targetEntity=Order.class)
    @JoinColumn(name="order_id")
    private Order order;
}

/** Order Repository */
public interface OrderRepository extends JpaRepository<Order, Integer>,
    JpaSpecificationExecutor<Order> {

}

/** Item Repository */
public interface ItemRepository extends JpaRepository<Item, Integer>,
    JpaSpecificationExecutor<Item> {

}

通過jpa自動建表死遭,這時候item表中有一個字段order_id广恢,是order表的id,這是一個外鍵約束呀潭,記住這一點钉迷,很重要。

3. 新增(保存)數(shù)據(jù)(CascadeType.PERSIST)

??客戶每次下完訂單后蜗侈,需要保存Order篷牌,但是訂單里含有Item,因此踏幻,在保存Order時候枷颊,Order相關(guān)聯(lián)的Item也需要保存。采用上面的模型该面,使用如下的測試代碼:

@Test
public void addTest(){
    Order order = new Order();
    order.setName("order1");

    Item item1 = new Item();
    item1.setName("item1_order1");
    item1.setOrder(order);

    Item item2 = new Item();
    item2.setName("item2_order1");
    item2.setOrder(order);

    List<Item> items = new ArrayList<Item>();
    items.add(item1);
    items.add(item2);
    order.setItems(items);

  //代碼段1
    orderRepository.save(order);
    Assert.assertEquals(1,orderRepository.count());
    Assert.assertEquals(2,itemRepository.count());

    //代碼段2
    itemRepository.save(items);
    Assert.assertEquals(1,orderRepository.count());
    Assert.assertEquals(2,itemRepository.count());
}

3.1 在該場景中夭苗,我們分別測試了如下情況:

CascadeType的用法 代碼段1結(jié)果 代碼段2結(jié)果
不使用CascadeType order可以保存到數(shù)據(jù)庫,item不能 報錯隔缀,因為外鍵使用的order_id不存在
單獨在Order類的items屬性上加入CascadeType.PERSIST order和items都可以保存到數(shù)據(jù)庫 報錯题造,同上
單獨在Item類的order屬性上加入CascadeType.PERSIST order可以保存到數(shù)據(jù)庫,item不能 order和items都可以保存到數(shù)據(jù)庫
在Order和Item類中都使用CascadeType.PERSIST order和items都可以保存到數(shù)據(jù)庫 order和items都可以保存到數(shù)據(jù)庫

3.5 結(jié)論

??在某個類的某個級聯(lián)屬性上使用CascadeType.PERSIST猾瘸,對該類進(jìn)行保存操作時界赔,可以級連保存該類中此屬性所對應(yīng)的對象丢习;而對該類的屬性對應(yīng)的對象進(jìn)行保存操作時,卻不能保存該類淮悼;存在外鍵的一方時咐低,因為外鍵的約束并不能單獨保存。
??如果在該類和該類的屬性所對應(yīng)的類別中同時使用CascadeType.PERSIST袜腥,無論是從該類出發(fā)進(jìn)行保存操作见擦,還是從該類的屬性對應(yīng)的對象出發(fā)進(jìn)行保存操作,都可以保存二者羹令。

4. 刪除數(shù)據(jù)(CascadeType.REMOVE)

??現(xiàn)在有這樣的場景鲤屡,客戶需要刪除一個訂單,那么訂單中的訂單項也需要一并刪除,為了可以實現(xiàn)級連刪除的效果福侈,我們使用以下測試代碼:

@Test
public void testDelete(){
    //代碼段3
    orderRepository.delete(order);
    Assert.assertEquals(0, orderRepository.count());
    Assert.assertEquals(0, orderRepository.count());

    //代碼段4
    itemRepository.delete(items);
    Assert.assertEquals(0, orderRepository.count());
    Assert.assertEquals(0, itemRepository.count());
}

4.1 在該場景中酒来,我們分別測試如下情況:

CascadeType的用法 代碼段3結(jié)果 代碼段4結(jié)果
不使用CascadeType 報錯,被item的order_id外鍵約束 刪除item癌刽,不能刪除order
在Order類的items屬性上使用CascadeType.REMOVE 級連刪除order中items的Item對象役首;在刪除過程中,會先刪除items显拜,然后再刪除order 可以刪除item衡奥,不能刪除order
在Item類的order屬性上使用CascadeType.REMOVE 報錯,被item的order_id外鍵約束 可以刪除items及其級聯(lián)的order對象远荠,其過程是先更新items中引用的order的外鍵矮固,設(shè)置items對order的引用為空值;如果只刪除一個item譬淳,order仍有級聯(lián)的item沒有刪除档址,此時并不是刪除某一個item,而是都不能刪除邻梆,報錯守伸,外鍵約束
在Order和Item中都使用CascadeType.REMOVE order和items都被刪除 order和items都被刪除;如果只刪除部分item浦妄,order仍有級聯(lián)的item未被刪除尼摹,報錯同上

4.2 結(jié)論

??在一般的業(yè)務(wù)場景中,需求基本是在刪除order時同時級連刪除items剂娄,但反過來蠢涝,在刪除items的時候同時也要求刪除order卻不是很適合;即使刪除了所有和order相關(guān)的items阅懦,可能也需要保持住那個沒有items的order和二。
??這里的建議是,最好不要在Item類使用CascadeType.REMOVE耳胎,一是不符合業(yè)務(wù)場景要求惯吕,二是外鍵約束報錯的概率99.9999999999...%惕它。

5. 更新數(shù)據(jù)(CascadeType.MERGE)

??在業(yè)務(wù)上,經(jīng)常會有這樣一種類似的需要:查找到了一個業(yè)務(wù)實體后废登,要更新該實體怠缸,同時也需要更新該實體所關(guān)聯(lián)的其他業(yè)務(wù)實體。在我們的例子中就是钳宪,同時需要更新Order和其所關(guān)聯(lián)的Item。我們使用如下測試代碼:

@Test
public void testUpdate(){
    order.setName("order1_updated");

    items.get(0).setName("item1_order1_updated");
    items.get(1).setName("item2_order1_updated");

    //代碼段5
    orderRepository.save(order);
    Assert.assertEquals(1, orderRepository.count(new Specification<Order>(){
        public Predicate toPredicate(Root<Order> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
            return cb.equal(root.get("name").as(String.class), "order1_updated");
        }
    }));
    Assert.assertEquals(1, itemRepository.count(new Specification<Item>() {

        public Predicate toPredicate(Root<Item> root,CriteriaQuery<?> cq, CriteriaBuilder cb) {
            return cb.equal(root.get("name").as(String.class), "item1_order1_updated");
        }
    }));

    //代碼段6
    itemRepository.save(items);
    Assert.assertEquals(1, itemRepository.count(new Specification<Item>() {
        public Predicate toPredicate(Root<Item> root,CriteriaQuery<?> cq, CriteriaBuilder cb) {
            return cb.equal(root.get("name").as(String.class), "item1_order1_updated");
        }
    }));
    Assert.assertEquals(1, orderRepository.count(new Specification<Order>(){
        public Predicate toPredicate(Root<Order> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
            return cb.equal(root.get("name").as(String.class), "order1_updated");
        }
    }));
}

??在該場景中扳炬,我們分別測試如下情況:

CascadeType的用法 代碼段5結(jié)果 代碼段6結(jié)果
不CascadeType.MERGE 更新Order成功吏颖,不會級連更新items 更新items成功,不會級聯(lián)更新items所關(guān)聯(lián)的order對象
單獨在Order的items屬性上使用CascadeType.MERGE 更新order成功恨樟,并且級連更新items 更新items成功半醉,不會級聯(lián)更新order
單獨在Item的屬性order上使用CascadeType.MERGE 更新order成功,不會級聯(lián)更新items 更新items成功劝术,可以級連更新其關(guān)聯(lián)的order對象
在Order和Item中都使用CascadeType.MERGE 更新order成功缩多,并且級連更新items 更新items成功,可以級連更新其關(guān)聯(lián)的order對象

5.2 結(jié)論

??通過使用CascadeType.MERGE养晋,可以說是最容易理解的衬吆,理解了上面的PERSIST和REMOVE,MERGE就沒有問題绳泉。

6. 刷新數(shù)據(jù)(CascadeType.REFRESH)

??這里刷新數(shù)據(jù)逊抡,是對應(yīng)在這樣的業(yè)務(wù)場景下:對于業(yè)務(wù)系統(tǒng),一般會存在多個用戶零酪,如果用戶A取得了order和其對應(yīng)的items冒嫡,并且對order和items進(jìn)行了修改,同時用戶B也做了如此操作四苇,但是用戶B先保存了孝凌,然后用戶A保存時,需要先刷新order關(guān)聯(lián)的items月腋,然后再把用戶A的變更更新到數(shù)據(jù)庫蟀架。這中場景就對應(yīng)了CascadeType.REFRESH的需求。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末罗售,一起剝皮案震驚了整個濱河市辜窑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌寨躁,老刑警劉巖穆碎,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異职恳,居然都是意外死亡所禀,警方通過查閱死者的電腦和手機(jī)方面,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來色徘,“玉大人恭金,你說我怎么就攤上這事」硬撸” “怎么了横腿?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長斤寂。 經(jīng)常有香客問我耿焊,道長,這世上最難降的妖魔是什么遍搞? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任罗侯,我火速辦了婚禮,結(jié)果婚禮上溪猿,老公的妹妹穿的比我還像新娘硬鞍。我一直安慰自己扛伍,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著每瞒,像睡著了一般陨帆。 火紅的嫁衣襯著肌膚如雪华畏。 梳的紋絲不亂的頭發(fā)上谨敛,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天,我揣著相機(jī)與錄音抗悍,去河邊找鬼驹饺。 笑死,一個胖子當(dāng)著我的面吹牛缴渊,可吹牛的內(nèi)容都是我干的赏壹。 我是一名探鬼主播,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼衔沼,長吁一口氣:“原來是場噩夢啊……” “哼蝌借!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起指蚁,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤菩佑,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后凝化,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體稍坯,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了瞧哟。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片混巧。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖勤揩,靈堂內(nèi)的尸體忽然破棺而出咧党,到底是詐尸還是另有隱情,我是刑警寧澤陨亡,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布傍衡,位于F島的核電站,受9級特大地震影響负蠕,放射性物質(zhì)發(fā)生泄漏聪舒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一虐急、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧滔迈,春花似錦止吁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至谈山,卻和暖如春俄删,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背奏路。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工畴椰, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鸽粉。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓斜脂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親触机。 傳聞我的和親對象是個殘疾皇子帚戳,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,884評論 2 354

推薦閱讀更多精彩內(nèi)容