春のブートで深い膝、トランザクションのイベントリスナーとCglibプロキシ
10320 ワード
同僚と私は今日の春のブートアプリケーションで奇妙なバグを追跡2時間を過ごした.原因はとても面白かったので、ここでそれについて書かなければなりません.私は明らかにここで顧客プロジェクトについて書くことができないので、私は代わりにサンプルアプリケーションを使用して問題を提示しています.だからここに行く.
つの集合を持つドメイン駆動アプリケーションを持っているとしましょう.
次のアプリケーションサービスで新しい注文を作成します.
次に、新しい請求書を作成するための別のアプリケーションサービスが必要です.
それは問題があることを
ドメインイベントは、通常のアプリケーションイベント発行者を使用して発行されます.春は実際に普通を使ってキャッチします
今、我々のイベントリスナーは
このメソッドは
この問題の解決策は
これでアプリケーションを再度実行します.まだ動作しません.アプリケーションは全く同じ動作します.今何が悪いのですか.
それがわかる
The
つの集合を持つドメイン駆動アプリケーションを持っているとしましょう.
Order
and Invoice
. 我々はまた、自動的に新しい請求書を作成する必要がありますオーケストラを持って1回の順序遷移SHIPPED
状態.次のアプリケーションサービスで新しい注文を作成します.
@Service
public class OrderService {
private final OrderRepository orderRepository;
public OrderService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
@Transactional
public Long createOrder() {
var order = new Order();
return orderRepository.saveAndFlush(order).getId();
}
@Transactional
public void shipOrder(Long orderId) {
orderRepository.findById(orderId).ifPresent(order -> {
order.ship();
orderRepository.saveAndFlush(order);
});
}
}
The Order.ship()
メソッドは、SHIPPED
そして、OrderStateChangedEvent
.次に、新しい請求書を作成するための別のアプリケーションサービスが必要です.
@Service
public class InvoiceService {
private final InvoiceRepository invoiceRepository;
InvoiceService(InvoiceRepository invoiceRepository) {
this.invoiceRepository = invoiceRepository;
}
@Transactional
Long createInvoiceForOrder(Order order) {
var invoice = new Invoice(order);
return invoiceRepository.saveAndFlush(invoice).getId();
}
}
最後に、注文が出荷されたときにインボイスを作成するオーケストラが必要です.@Component
class InvoiceCreationOrchestrator {
private final OrderRepository orderRepository;
private final InvoiceService invoiceService;
InvoiceCreationOrchestrator(OrderRepository orderRepository, InvoiceService invoiceService) {
this.orderRepository = orderRepository;
this.invoiceService = invoiceService;
}
@TransactionalEventListener
public void onOrderStateChangedEvent(OrderStateChangedEvent event) {
if (event.getNewOrderState().equals(OrderState.SHIPPED)) {
orderRepository
.findById(event.getOrderId())
.ifPresent(invoiceService::createInvoiceForOrder);
}
}
}
そら!今、私たちは、アプリケーションを実行すると、新しい順序を作成し、それを出荷します.請求書は作成されません.ログには例外はありません.それで、何が間違ったのか?それは問題があることを
@TransactionalEventListener
. トランザクションがコミットされた後に実行するのはデフォルトで設定されます.これはまさに我々が望むものですが、春が実際にこれを実行する方法に注意があります.ドメインイベントは、通常のアプリケーションイベント発行者を使用して発行されます.春は実際に普通を使ってキャッチします
@EventListener
同様に.ただし、トランザクションイベントリスナーを直接呼び出す代わりに、TransactionSynchronization
とTransactionSynchronizationManager
. トランザクションが正常にコミットされた後、トランザクション同期マネージャがクリーンアップしている前にトランザクションイベントリスナーを呼び出します.今、我々のイベントリスナーは
createInvoiceForOrder
メソッド@Transactional
注釈.のデフォルト伝播@Transactional
is REQUIRED
. つまり、アクティブなトランザクションが既に存在する場合、メソッドはそれに参加する必要がありますそれ以外の場合は、独自のトランザクションを作成する必要があります.このメソッドは
TransactionSynchronization
, 実際には“アクティブ”トランザクションが、すでにコミットされています.したがって、saveAndFlush
はTransactionRequiredException
. この例外はTransactionSynchronizationUtils
(別のSpringクラス)、デバッグレベルを使用してログインします.したがって、この例外を検出する唯一の方法は、org.springframework.transaction.support
パッケージ.この問題の解決策は
InvoiceService
常に自分のトランザクションの中で実行します.そこで、以下のような方法を変更します.@Service
public class InvoiceService {
@Transactional(propagation = REQUIRES_NEW)
Long createInvoiceForOrder(Order order) {
// Rest of the method omitted
}
}
それはとにかく良いすべてのアプリケーションサービスを常に使用するように設定することですREQUIRES_NEW
彼らは責任があるので.これでアプリケーションを再度実行します.まだ動作しません.アプリケーションは全く同じ動作します.今何が悪いのですか.
それがわかる
createInvoiceForOrder
はトランザクション内で実際に実行されていません.トランザクションが開始され、呼び出しによってコミットsaveAndFlush()
リポジトリでは、そのメソッドはREQUIRED
トランザクション伝搬どうして?The
InvoiceService
はインターフェースを実装していないので、Springはトランザクションインターセプタを追加するためにCGlibプロキシを使用しています.しかしながら、方法createInvoiceForOrder
パッケージの可視性があり、トランザクションインターセプターはパブリックメソッドにのみ適用されます.したがって、メソッドをpublicに変更する必要があります.@Service
public class InvoiceService {
@Transactional(propagation = REQUIRES_NEW)
public Long createInvoiceForOrder(Order order) {
// Rest of the method omitted
}
}
今、我々はもう一度アプリケーションを実行し、それが最終的に動作する!Reference
この問題について(春のブートで深い膝、トランザクションのイベントリスナーとCglibプロキシ), 我々は、より多くの情報をここで見つけました https://dev.to/peholmst/knee-deep-in-spring-boot-transactional-event-listeners-and-cglib-proxies-1il9テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol