【轉】Jpa CascadeType 細緻講解

說明

這位大佬對這個問題講的很好,特轉載過來,以便日後複習。

 

原文鏈接: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());
}

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

  1. 沒有任何CascadeType設置,由測試結果可知,order可以被保存到數據庫,但是兩個Item卻不能。
  2. 使用CascadeType.PERSIST
    1. 單獨在Order類的items屬性上加入cascade={CascadeType.PERSIST},使用代碼段1,order和items都可以被保存到數據庫;使用代碼段2,order和items都不能被保存到數據庫。
    2. 單獨在Item類的order屬性上加入cascade={CascadeType.PERSIST},使用代碼段1,order可以被保存到數據庫,items不可以被保存到數據庫;使用代碼段2,order和items都可以被保存到數據庫。
    3. 在Order和Item類中都使用cascade={CascadeType.PERSIST},使用代碼段1,order和items都可以被保存到數據庫;使用代碼段2,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());
}

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

  1. 在Order和Item中都沒有使用CascadeType.REMOVE時,單獨刪除order對象是不成功的,這是由於數據庫在item表中有基於order的外鍵約束。單獨刪除items可以成功的。
  2. 使用CascadeType.REMOVE
    1. 在Order類的items屬性上使用CascadeType.REMOVE, 使用代碼段3,通過Order對象的刪除操作,可以級連刪除order中items的Item對象(在刪除過程中,會先刪除items,然後再刪除order);但使用代碼段4,雖然items可以成功刪除,但是其關聯的order對象卻不能級連刪除。
    2. 在Item類的order屬性上使用CascadeType.REMOVE, 使用代碼段3, Order對象的刪除操作是失敗的,這是因爲在存儲item的表中有引用order表的外鍵;但是使用代碼段4卻可以成功的刪除items及其級連的order對象。其過程是先更新items中引用的order的外鍵,設置items對order的引用爲空值。然後刪除items,然後再刪除order~~(注意,如果items爲多條,那麼會先刪除一條item,然後刪除order,然後再刪除其餘的item)~~。
    3. 在Order和Item中都使用CascadeType.REMOVE時,使用代碼段3代碼段4都可以成功,這表明,在二者都使用CascadeType.REMOVE時,既可以通過刪除order,同時級連刪除items;也可以通過刪除items,同時級連刪除order。

通過以上的分析,可以瞭解到,在一般的業務場景中,需求基本是在刪除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");
		}
	}));		
}

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

  1. 在Order和Item中都沒有使用CascadeType.MERGE時,使用代碼段5,其測試結果表明更新Order成功,但是並沒有級連更新items;使用代碼段6,更新items成功,但並沒有級連更新items所關聯的order對象。
  2. 使用CascadeType.MERGE
    1. 單獨在Order的items屬性上使用CascadeType.MERGE,使用代碼段5,其測試結果表明更新order成功,並且級連更新items也成功;使用代碼段6,其測試結果表明,更新items成功,但是並沒有級連更新order。
    2. 單獨在Item的屬性order上使用CascadeType.MERGE,使用代碼段5,其測試結果表明更新items時,可以級連更新其關聯的order對象;使用代碼段6,更新items成功,但是並沒有級連更新items關聯的order對象。
    3. 在Order和Item中都使用CascadeType.MERGE時,使用代碼段4代碼段5都可以成功,這表明,在二者都使用CascadeType.MERGE時,既可以通過更新order,同時級連更新items;也可以通過更新items,同時更新order。

通過以上的分析,可以瞭解到,通過使用CascadeType.MERGE,可以通過更新關係的一端對象,而同時更新關係令一端的數據。

場景4,刷新數據(CascadeType.REFRESH)

這裏刷新數據,是對應在這樣的業務場景下:對於業務系統,一半會存在多個用戶,如果用戶A取得了order和其對應的items,並且對order和items進行了修改,同時用戶B也做了如此操作,但是用戶B先保存了,然後用戶A保存時,需要先刷新order關聯的items,然後再把用戶A的變更更新到數據庫。這中場景就對應了CascadeType.REFRESH的需求。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章