JPAにおける永続性の伝播(Cascade)
JPAでは、親エンティティ(Order
)と子エンティティ(OrderProduct
)、および関連するエンティティ(Product
)の関係において、永続性の伝播(Cascade) は、親エンティティと一緒に子エンティティが自動的に保存、更新、削除されるようにするために使用されます。もし永続性の伝播が設定されていないと、関連するエンティティを手動でそれぞれ保存する必要があり、問題が発生することがあります。
永続性の伝播(Cascade)に関する主な原則
-
Cascade設定:親エンティティと子エンティティ間の関係で永続性の伝播(Cascade)を設定し、親エンティティが保存される際に子エンティティも自動的に保存されるようにします。
CascadeType.PERSIST
やCascadeType.ALL
を使用することで、親エンティティが保存される際に子エンティティも自動的に保存されます。 -
リレーションの設定:適切に一方向または双方向の関連関係を設定し、関連するエンティティのフィールド名が正確に一致していることを確認します。
サンプルコード
以下のサンプルコードは、親エンティティである PurchaseOrder
と子エンティティである PurchaseOrderItem
、そして他の関連エンティティである InventoryItem
間で永続性の伝播を設定する方法を示しています。
1. PurchaseOrder
エンティティ
PurchaseOrder
は複数の PurchaseOrderItem
と関連しており、永続性の伝播により PurchaseOrder
が保存されると PurchaseOrderItem
も一緒に保存されます。
@Entity
public class PurchaseOrder {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "purchaseOrder", cascade = CascadeType.ALL, orphanRemoval = true)
private List<PurchaseOrderItem> orderItems = new ArrayList<>();
public void addOrderItem(PurchaseOrderItem item) {
orderItems.add(item);
item.setPurchaseOrder(this);
}
// Getters and Setters
}
2. PurchaseOrderItem
エンティティ
PurchaseOrderItem
は PurchaseOrder
および InventoryItem
と関連しており、PurchaseOrder
との関係で ManyToOne
として設定されています。
@Entity
public class PurchaseOrderItem {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "purchase_order_id")
private PurchaseOrder purchaseOrder;
@ManyToOne
@JoinColumn(name = "inventory_item_id")
private InventoryItem inventoryItem;
private int quantity;
// Getters and Setters
}
3. InventoryItem
エンティティ
InventoryItem
は永続性の伝播を必要としないため、Cascade
設定は不要です。
@Entity
public class InventoryItem {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private int stockQuantity;
// 在庫管理のビジネスロジック
public void reduceStock(int quantity) {
if (this.stockQuantity < quantity) {
throw new InsufficientStockException("Not enough stock");
}
this.stockQuantity -= quantity;
}
// Getters and Setters
}
4. サービスでの永続性の伝播処理
サービス層で PurchaseOrder
とそれに関連する PurchaseOrderItem
を一緒に保存できるように、永続性の伝播を使用します。PurchaseOrder
のみを保存すれば、PurchaseOrderItem
も一緒に保存されます。また、InventoryItem
は別途保存または管理できます。
@Service
public class OrderService {
@Autowired
private PurchaseOrderRepository purchaseOrderRepository;
@Autowired
private InventoryItemRepository inventoryItemRepository;
public void createOrder(OrderRequest orderRequest) {
PurchaseOrder order = new PurchaseOrder();
for (OrderItemRequest itemRequest : orderRequest.getItems()) {
InventoryItem inventoryItem = inventoryItemRepository.findById(itemRequest.getItemId())
.orElseThrow(() -> new ItemNotFoundException("Item not found"));
// 在庫を減らす
inventoryItem.reduceStock(itemRequest.getQuantity());
// 注文アイテムを作成
PurchaseOrderItem orderItem = new PurchaseOrderItem();
orderItem.setInventoryItem(inventoryItem);
orderItem.setQuantity(itemRequest.getQuantity());
// PurchaseOrderに追加
order.addOrderItem(orderItem);
}
// PurchaseOrderと関連するPurchaseOrderItemを一緒に保存
purchaseOrderRepository.save(order);
}
}
双方向リレーションで this
を使用する理由
関連オブジェクトを設定する際に、双方向のリレーションでは this
を使用してオブジェクト間の双方向の関係を同期することが重要です。これにより、両側の関係が一貫して維持され、JPAが正しく管理できるようになります。ただし、すべてのリレーションで this
を使用する必要があるわけではなく、主に双方向リレーションでオブジェクト間の参照を相互に設定する際に使用されます。
1. 双方向リレーションで this
を使用する理由
-
双方向リレーションでは、一方のエンティティが他方のエンティティを参照し、逆にもう一方も最初のエンティティを再び参照します。例えば、
Order
とOrderItem
の関係では、Order
は複数のOrderItem
を持ち、各OrderItem
は再びOrder
を参照する構造です。 - このとき、一方だけで値を設定すると、逆側は依然として
null
になる可能性があるため、双方向の関係を同期する必要があります。そのためにthis
を使用して、現在のオブジェクトを参照する関係を設定します。
2. this
を使用する基準
双方向リレーションで関連オブジェクトを追加する際には、以下の基準で this
を使用して関係を設定します。
-
双方向リレーションの同期: 双方向リレーションでは、一方のオブジェクトに関連するオブジェクトを設定する際、逆方向のオブジェクトにも現在のオブジェクト (
this
) を参照するように設定する必要があります。 -
便利メソッドの使用: 双方向リレーションの同期を明確に管理するために、便利メソッドを使用します。このメソッドは、2つのオブジェクトの関係を一度に同期するために役立ちます。
例: Order
と OrderItem
の双方向リレーション
1. Order
エンティティ
@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private List<OrderItem> orderItems = new ArrayList<>();
// 便利メソッドを使ってOrderItemとの関係を同期
public void addOrderItem(OrderItem orderItem) {
orderItems.add(orderItem);
orderItem.setOrder(this); // OrderItemでもOrderを参照するように設定
}
public void removeOrderItem(OrderItem orderItem) {
orderItems.remove(orderItem);
orderItem.setOrder(null); // 関係を切るときもthisで設定
}
// Getters and Setters
}
2. OrderItem
エンティティ
@Entity
public class OrderItem {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "order_id")
private Order order;
// Orderを設定するときthisを使って双方向リレーションを同期
public void setOrder(Order order) {
this.order = order;
}
// Getters and Setters
}
3. 双方向リレーションを設定する際の注意事項
-
両方の側で関係を設定する必要がある: 例えば、
Order
にOrderItem
を追加するときは、必ずOrderItem
のsetOrder()
を呼び出して、逆方向も同期する必要があります。これを行わないと、JPAはデータベース上の関係を正しく認識できない可能性があります。 -
便利メソッド:
addOrderItem()
のようなメソッドを作成し、双方向リレーションを一度に同期することが便利です。
4. 単方向リレーションの場合
- 単方向リレーションでは、
this
で相互参照する必要はありません。単方向の場合、一方のオブジェクトだけが参照されるため、双方向リレーションのように相互に同期する必要はありません。
例: 単方向リレーション
@Entity
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany
private List<Order> orders = new ArrayList<>();
// Getters and Setters
}
この場合、Order
は Customer
を参照しないため、this
を使用した同期は必要ありません。
まとめ
-
永続性の伝播(CascadeType):親エンティティ(
PurchaseOrder
)と子エンティティ(PurchaseOrderItem
)間にCascadeType.ALL
を設定することで、親エンティティが保存または更新されると、子エンティティも一緒に処理されます。 -
リレーションの設定:子エンティティは
ManyToOne
で親エンティティを参照し、親エンティティはOneToMany
で子エンティティのリストを管理します。 -
在庫管理:
InventoryItem
のようなエンティティは永続性の伝播を使用せず、個別に管理できます。 -
双方向リレーションでは、
this
を使用して相互参照を設定する必要があります。これにより、両方のエンティティが一貫性を持って接続されます。 -
便利メソッドを作成して、双方向リレーションを一度に同期することをお勧めします。
-
単方向リレーションでは、相互参照がないため
this
を使用する必要はありません。