說明
這位大佬對這個問題講的很好,特轉載過來,以便日後複習。
原文鏈接:http://westerly-lzh.github.io/cn/2014/12/JPA-CascadeType-Explaining/
Background
網上關於JPA的CascadeType講解很多,但幾乎都說的很模糊.本文試圖使用一個具體的例子來說明CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.REMOVE, CascadeType.ALL 具體區別。
首先,我們使用一個訂單和訂單項的例子。該例子在網絡上那些介紹JPA CascadeType用法的文章鍾廣爲流傳。
/**
* 訂單
*/
@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> {
}
場景1,新增~~(保存)~~數據(CascadeType.PERSIST
)
客戶每次下完訂單後,需要保存Order,但是訂單裏含有Item,因此,在保存Order時候,Order相關聯的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);
orderRepository.save(order);
Assert.assertEquals(1,orderRepository.count());
Assert.assertEquals(2,itemRepository.count());
//代碼段1
itemRepository.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());
}
在該場景中,我們分別測試如下情況:
- 沒有任何CascadeType設置,由測試結果可知,order可以被保存到數據庫,但是兩個Item卻不能。
- 使用
CascadeType.PERSIST
- 單獨在Order類的items屬性上加入
cascade={CascadeType.PERSIST}
,使用代碼段1,order和items都可以被保存到數據庫;使用代碼段2,order和items都不能被保存到數據庫。 - 單獨在Item類的order屬性上加入
cascade={CascadeType.PERSIST}
,使用代碼段1,order可以被保存到數據庫,items不可以被保存到數據庫;使用代碼段2,order和items都可以被保存到數據庫。 - 在Order和Item類中都使用
cascade={CascadeType.PERSIST}
,使用代碼段1,order和items都可以被保存到數據庫;使用代碼段2,order和items都可以被保存到數據庫。
- 單獨在Order類的items屬性上加入
由此可以知道,在某個類的屬性上使用cascade={CascadeType.PERSIST}
,對該類進行保存操作時,可以級連保存該類中此屬性所對應的對象;而對該類的屬性對應的對象進行保存操作時,卻不能保存該類(存在外鍵時,二者都不可保存)。但是如果在該類和該類的屬性所對應的類別中同時使用cascade={CascadeType.PERSIST}
,那麼無論是從該類出發進行保存操作,還是從該類的屬性對應的對象出發進行保存操作,都可以保存二者。
場景2,刪除數據(CascadeType.REMOVE
)
現在有這樣的場景,客戶需要刪除一個訂單,那麼訂單中的訂單項也需要一併刪除,爲了可以實現級連刪除的效果,我們使用以下測試代碼:
private Order order;
private List<Item> items = new ArrayList<Item>();
@Before
public void setUp(){
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);
items.add(item1);
items.add(item2);
order.setItems(items);
orderRepository.save(order);
itemRepository.save(items);
}
@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());
}
在該場景中,我們分別測試如下情況:
- 在Order和Item中都沒有使用
CascadeType.REMOVE
時,單獨刪除order對象是不成功的,這是由於數據庫在item表中有基於order的外鍵約束。單獨刪除items可以成功的。 - 使用
CascadeType.REMOVE
- 在Order類的items屬性上使用
CascadeType.REMOVE
, 使用代碼段3,通過Order對象的刪除操作,可以級連刪除order中items的Item對象(在刪除過程中,會先刪除items,然後再刪除order);但使用代碼段4,雖然items可以成功刪除,但是其關聯的order對象卻不能級連刪除。 - 在Item類的order屬性上使用
CascadeType.REMOVE
, 使用代碼段3, Order對象的刪除操作是失敗的,這是因爲在存儲item的表中有引用order表的外鍵;但是使用代碼段4卻可以成功的刪除items及其級連的order對象。其過程是先更新items中引用的order的外鍵,設置items對order的引用爲空值。然後刪除items,然後再刪除order~~(注意,如果items爲多條,那麼會先刪除一條item,然後刪除order,然後再刪除其餘的item)~~。 - 在Order和Item中都使用
CascadeType.REMOVE
時,使用代碼段3和代碼段4都可以成功,這表明,在二者都使用CascadeType.REMOVE
時,既可以通過刪除order,同時級連刪除items;也可以通過刪除items,同時級連刪除order。
- 在Order類的items屬性上使用
通過以上的分析,可以瞭解到,在一般的業務場景中,需求基本是在刪除order時同時級連刪除items,但反過來,在刪除items的時候同時也要求刪除order卻不一定適合業務場景。即使刪除了所有和order相關的items,可能也需要保持住那個沒有items的order。所以這裏的建議是,最好不要在order和item雙方中都同時使用CascadeType.REMOVE
,即最好不要在關係的維護端(這裏指Item類,因爲item表中會有order的外鍵,所以item是關係的維護端)使用CascadeType.REMOVE
。
場景3, 更新數據(CascadeType.MERGE
)
在業務上,經常會有這樣一種類似的需要:查找到了一個業務實體後,要更新該實體,同時也需要更新該實體所關聯的其他業務實體。在我們的例子中就是,同時需要更新Order和其所關聯的Item。我們使用如下測試代碼:
private Order order;
private List<Item> items = new ArrayList<Item>();
@Before
public void setUp(){
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);
items.add(item1);
items.add(item2);
order.setItems(items);
orderRepository.save(order);
itemRepository.save(items);
}
@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");
}
}));
}
在該場景中,我們分別測試如下情況:
- 在Order和Item中都沒有使用
CascadeType.MERGE
時,使用代碼段5,其測試結果表明更新Order成功,但是並沒有級連更新items;使用代碼段6,更新items成功,但並沒有級連更新items所關聯的order對象。 - 使用
CascadeType.MERGE
- 單獨在Order的items屬性上使用
CascadeType.MERGE
,使用代碼段5,其測試結果表明更新order成功,並且級連更新items也成功;使用代碼段6,其測試結果表明,更新items成功,但是並沒有級連更新order。 - 單獨在Item的屬性order上使用
CascadeType.MERGE
,使用代碼段5,其測試結果表明更新items時,可以級連更新其關聯的order對象;使用代碼段6,更新items成功,但是並沒有級連更新items關聯的order對象。 - 在Order和Item中都使用
CascadeType.MERGE
時,使用代碼段4和代碼段5都可以成功,這表明,在二者都使用CascadeType.MERGE
時,既可以通過更新order,同時級連更新items;也可以通過更新items,同時更新order。
- 單獨在Order的items屬性上使用
通過以上的分析,可以瞭解到,通過使用CascadeType.MERGE
,可以通過更新關係的一端對象,而同時更新關係令一端的數據。
場景4,刷新數據(CascadeType.REFRESH
)
這裏刷新數據,是對應在這樣的業務場景下:對於業務系統,一半會存在多個用戶,如果用戶A取得了order和其對應的items,並且對order和items進行了修改,同時用戶B也做了如此操作,但是用戶B先保存了,然後用戶A保存時,需要先刷新order關聯的items,然後再把用戶A的變更更新到數據庫。這中場景就對應了CascadeType.REFRESH
的需求。