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的需求。