Javaにおけるユニットオブワークパターン:効率的なトランザクション管理のオーケストレーション
ユニットオブワークデザインパターンの目的
Javaのユニットオブワークパターンが、ビジネストランザクションの影響を受けるオブジェクトのリストをどのように専門的に管理し、維持し、データベースの変更を調整し、並行性の問題を効果的に解決するかを学びます。
実例を用いたユニットオブワークパターンの詳細な説明
実例
図書館員が、ユニットオブワークデザインパターンを使用して、借用および返却されたすべての書籍を綿密に追跡し、在庫システムを効率的に更新するシナリオを考えてみましょう。単一のトランザクションが発生するたびに図書館の在庫システムを更新する代わりに、図書館員はすべての変更のリストを保持し、1日の最後にシステムを一度更新します。このアプローチにより、すべての変更がまとめて処理され、在庫の整合性が維持され、必要な個々の更新の数が削減されます。これは、ソフトウェアにおけるユニットオブワークパターンに類似しており、オブジェクトのセットへのすべての変更が追跡され、整合性と効率を維持するために単一のトランザクションとしてコミットされます。
簡単に言うと
ユニットオブワークパターンは、トランザクション中のオブジェクトへの変更を追跡し、整合性と効率を確保するためにすべての変更を一括してコミットします。
MartinFowler.comによると
ビジネストランザクションの影響を受けるオブジェクトのリストを維持し、変更の書き込みと並行性問題の解決を調整します。
Javaにおけるユニットオブワークパターンのプログラミング例
武器商人が、武器情報を格納するデータベースを持っています。町中の商人たちが絶えずこの情報を更新しているため、データベースサーバーへの負荷が高くなっています。負荷をより管理しやすくするために、ユニットオブワークパターンを適用して、多くの小さな更新を一括で送信します。
データベースに永続化されるWeapon
エンティティを以下に示します。
@Getter
@RequiredArgsConstructor
public class Weapon {
private final Integer id;
private final String name;
}
実装の本質は、ユニットオブワークパターンを実装するArmsDealer
です。これは、実行する必要があるデータベース操作(context
)のマップを維持し、commit
が呼び出されると、それらを単一のバッチで適用します。
public interface IUnitOfWork<T> {
String INSERT = "INSERT";
String DELETE = "DELETE";
String MODIFY = "MODIFY";
void registerNew(T entity);
void registerModified(T entity);
void registerDeleted(T entity);
void commit();
}
@Slf4j
@RequiredArgsConstructor
public class ArmsDealer implements IUnitOfWork<Weapon> {
private final Map<String, List<Weapon>> context;
private final WeaponDatabase weaponDatabase;
@Override
public void registerNew(Weapon weapon) {
LOGGER.info("Registering {} for insert in context.", weapon.getName());
register(weapon, UnitActions.INSERT.getActionValue());
}
@Override
public void registerModified(Weapon weapon) {
LOGGER.info("Registering {} for modify in context.", weapon.getName());
register(weapon, UnitActions.MODIFY.getActionValue());
}
@Override
public void registerDeleted(Weapon weapon) {
LOGGER.info("Registering {} for delete in context.", weapon.getName());
register(weapon, UnitActions.DELETE.getActionValue());
}
private void register(Weapon weapon, String operation) {
var weaponsToOperate = context.get(operation);
if (weaponsToOperate == null) {
weaponsToOperate = new ArrayList<>();
}
weaponsToOperate.add(weapon);
context.put(operation, weaponsToOperate);
}
@Override
public void commit() {
if (context == null || context.isEmpty()) {
return;
}
LOGGER.info("Commit started");
if (context.containsKey(UnitActions.INSERT.getActionValue())) {
commitInsert();
}
if (context.containsKey(UnitActions.MODIFY.getActionValue())) {
commitModify();
}
if (context.containsKey(UnitActions.DELETE.getActionValue())) {
commitDelete();
}
LOGGER.info("Commit finished.");
}
private void commitInsert() {
var weaponsToBeInserted = context.get(UnitActions.INSERT.getActionValue());
for (var weapon : weaponsToBeInserted) {
LOGGER.info("Inserting a new weapon {} to sales rack.", weapon.getName());
weaponDatabase.insert(weapon);
}
}
private void commitModify() {
var modifiedWeapons = context.get(UnitActions.MODIFY.getActionValue());
for (var weapon : modifiedWeapons) {
LOGGER.info("Scheduling {} for modification work.", weapon.getName());
weaponDatabase.modify(weapon);
}
}
private void commitDelete() {
var deletedWeapons = context.get(UnitActions.DELETE.getActionValue());
for (var weapon : deletedWeapons) {
LOGGER.info("Scrapping {}.", weapon.getName());
weaponDatabase.delete(weapon);
}
}
}
アプリケーション全体の構成方法を以下に示します。
public static void main(String[] args) {
// create some weapons
var enchantedHammer = new Weapon(1, "enchanted hammer");
var brokenGreatSword = new Weapon(2, "broken great sword");
var silverTrident = new Weapon(3, "silver trident");
// create repository
var weaponRepository = new ArmsDealer(new HashMap<>(),
new WeaponDatabase());
// perform operations on the weapons
weaponRepository.registerNew(enchantedHammer);
weaponRepository.registerModified(silverTrident);
weaponRepository.registerDeleted(brokenGreatSword);
weaponRepository.commit();
}
コンソール出力は次のとおりです。
21:39:21.984 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Registering enchanted hammer for insert in context.
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Registering silver trident for modify in context.
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Registering broken great sword for delete in context.
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Commit started
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Inserting a new weapon enchanted hammer to sales rack.
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Scheduling silver trident for modification work.
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Scrapping broken great sword.
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Commit finished.
Javaでユニットオブワークパターンを使用する場合
- ユニットオブワークパターンは、単一のトランザクションとして実行する必要があるJavaにおける複数のデータベース操作を管理し、データの整合性と完全性を確保するのに最適です。
- ビジネスオブジェクトへの変更を追跡し、調整された方法で保存する必要があるシナリオに最適です。
- HibernateなどのJavaのオブジェクトリレーショナルマッピング(ORM)フレームワークを使用する場合に役立ちます。
ユニットオブワークパターンのJavaチュートリアル
Javaにおけるユニットオブワークパターンの実用例
- HibernateなどのJavaベースのORMフレームワークでの実装。
- 複数のデータベース操作をアトミックにする必要があるエンタープライズアプリケーション。
- 複数のオブジェクトが同時に変更および永続化される複雑なトランザクションシステム。
ユニットオブワークパターンのメリットとトレードオフ
メリット
- トランザクションを効果的に管理することにより、データの整合性を確保します。
- 一括処理することにより、データベースへの呼び出し回数を削減します。
- トランザクション管理をビジネスロジックから切り離すことにより、永続化ロジックを簡素化します。
トレードオフ
- ユニットオブワーク内のオブジェクトのライフサイクルの管理に複雑さを招く可能性があります。
- 特に大規模なデータセットの場合、適切に管理されない場合、パフォーマンスのオーバーヘッドが発生する可能性があります。
関連するJavaデザインパターン
- アイデンティティマップ:各オブジェクトがトランザクションごとに一度だけロードされるようにすることで、冗長性を削減し、パフォーマンスを向上させます。
- リポジトリ:永続化ロジックを抽象化し、データへのアクセス方法をよりクリーンにするために、ユニットオブワークと連携して使用されることがよくあります。
- トランザクションスクリプト:手続き型アプローチでは異なりますが、より高いレベルでトランザクションロジックを管理することにより、ユニットオブワークを補完できます。