EMF Transactionについて
EMFのデータモデルをトランザクションを用いて、複数スレッドからのアクセスコントロールやデータの整合性(atomic)を守る仕組み。 EditingDomainを用いてやり取りを行います。
簡単な説明はざっくり飛ばします。参考サイトを読んでください。
予備知識:EditingDomain
editingDomainは相関のあるEMFモデルを含んでいるのとコマンドを管理するものです。モデルはリソースセットの形式で保持されていています。
コマンドとモデルの編集はドメインを経由して、コマンドスタックを使って行います。
EditingDomainの補足機能として、基本的なコマンドのオーバーライドが使えます(OverrideableCommand. を参照)
ドメインはgetParent,getChildrenメソッドの結果でモデルへ階層的に役割を課す。
これはネストしたオブジェクトの削除などに使うRemoveCommandなどの実装に便利である。
また、ネストしたコピーなどに使うCopyCommandも便利である。
クラス図的にはこんな感じ
Transactional Commands API
EditingDomain取得方法
EMFTransactionはEditingDomainを利用して、データのやり取りを行うため、EditingDomainを取得する必要があります。取得方法としてはTransactionUtilのユーティリティクラスのgetEditingDomainメソッドを利用します。
引数はEObject,Resource,ResourceSetのいずれかを入れてやれば取得が行えます。
データアクセス方法
データ取得
データの取得に関してはデータの変更が行われないため、取得方法はどのようにしても構いません。リソースからモデルを取得して任意のデータを取得することが可能です。
データ更新
コマンドを用いてデータ更新は行います。リソースから直接取得してデータ更新を行うとExceptionが投げられます。
public void setModuleDet(String moduleName, boolean detInfo) {
EObject owner = null;
EAttribute feature = null;
try{
Command cmd = editingDomain.createCommand(
SetCommand.class,
new CommandParameter(
owner,
feature,
detInfo
));
editingDomain.getCommandStack().execute(cmd);
} catch (Exception e) {
logger.warn(e.getMessage());
}
}
ロールバック
トランザクション環境で、データ整合性が失われた場合ロールバックするために、R/Wのトランザクションが発生する。しかし、基本的なコマンドスタックAPIはコマンドを失敗することはないので、
ロールバックしたときのフィードバックはない。
もし、トランザクションが始まっていなく、トランザクションがロールバックや、割り込み例外が発生したとき、トランザクションコマンドスタックはロールバック例外を投げることができる。
Library richmond = getLibrary("Richmond Branch"); // this would use a read-only transaction
TransactionalEditingDomain domain = getEditingDomain();
TransactionalCommandStack tstack = (TransactionalCommandStack) domain.getCommandStack();
Command cmd = domain.createCommand(
SetCommand.class,
new CommandParameter(richmond, EXTLibraryPackage.Literals.LIBRARY__ADDRESS, "5600 Perth St."));
try {
tstack.execute(cmd, Collections.EMPTY_MAP);
} catch (InterruptedException e) {
MessageDialog.openError(shell, "Command Failed",
"Interrupted while waiting for a read/write transaction.");
} catch (RollbackException e) {
ErrorDialog.openError(shell, "Command Failed", "Transaction rolled back",
e.getStatus());
}
コマンドオプション
コマンド実行に対して、色々オプションを付けて実行が行える。
* OPTION_NO_NOTIFICATIONS: リスナに変更通知を送らない。
* OPTION_NO_TRIGGERS: トリガーを使わない。
* OPTION_NO_VALIDATION: 変更に整合性チェックを行わない。
* OPTION_NO_UNDO: UNDO/REDOやロールバックのための変更記録を行わない。
* OPTION_UNPROTECTED: OPTION_NO_UNDO, OPTION_NO_VALIDATION, and OPTION_NO_TRIGGERSの複合ケース
TransactionalCommandStack stack;
Library library;
// don't tell the UI that we are changing the library name
stack.execute(
SetCommand.create(domain, library,
EXTLibraryPackage.Literals.LIBRARY__NAME, "Secret Name"),
Collections.singletonMap(
Transaction.OPTION_NO_NOTIFICATIONS, Boolean.TRUE));
トリガー
トリガーの実装も可能である。
// trigger ensuring that all libraries have names
class MyListener extends ResourceSetListenerImpl {
MyListener() { // only interested in changes to Library objects
super(NotificationFilter.createNotifierTypeFilter(
EXTLibraryPackage.Literals.LIBRARY));
}
public Command transactionAboutToCommit(ResourceSetChangeEvent event)
throws RollbackException {
List commands = new ArrayList();
Iterator iter = event.getNotifications().iterator();
while (iter.hasNext()) {
Notification next = (Notification) iter.next();
Library library = (Library) next.getNotifier();
if (library.getName() == null)
commands.add(SetCommand.create(
event.getEditingDomain(), library,
EXTLibraryPackage.Literals.LIBRARY__NAME, "A library"));
}
return commands.isEmpty()? null : new CompoundCommand(commands);
}
}
class MyTriggerListener extends TriggerListener {
MyListener() { // only interested in changes to Library objects
super(NotificationFilter.createNotifierTypeFilter(
EXTLibraryPackage.Literals.LIBRARY));
}
protected Command trigger(TransactionalEditingDomain domain,
Notification notification) throws RollbackException {
Library library = (Library) next.getNotifier();
if (library.getName() == null) {
return SetCommand.create(domain, library,
EXTLibraryPackage.Literals.LIBRARY__NAME, "A library");
}
return null;
}
}
参考サイト