第14章 Transactional Objects for Javaを利用するアプリケーションの構築

14.1. アプリケーションの構築

JBoss Transaction Service アプリケーションの開発は主に2つのフェーズから構成されています。まず、クラス開発者が新規クラスを記述しますが、これらのクラスは永続性があるか、回復可能であるか、あるいは同時制御されている必要があります。次にアプリケーション開発者がアプリケーション内に作成したクラスを使います。これらの開発者は同一人物の場合もありますが、それぞれの役割で違った懸念が出てきます。クラス開発者は、適切なsave_state および restore_state メソッドを開発するだけでなく、操作に適切なロックを設定し、見合ったJBoss Transaction Service クラスコンストラクタを呼び出すことに焦点をおく必要があります。反対にアプリケーション開発者が気を使うのは、特にアトミックな (atomic) アクションあるいはトランザクションの利用などの面でアプリケーションの一般的な構造を定義する点です。
本項では、単純なアプリケーション、整数値に対するFIFO Queue クラスを説明します。Queue は双方向連結リスト構造を使い、単一オブジェクトとして実装されています。この例を本書の残りの部分でも利用し、JBoss Transaction Service が提供する様々なメカニズムを説明します。この例は単純ですが、アプリケーションコードに関する豊富な知識を必要とせずにJBoss Transaction Service に対し行える修正点についてすべて説明しています。
本章の例は、アプリケーションが分散されていないという前提で提供されています。分散アプリケーションの場合、コンテキスト情報を暗黙的あるいは明示的に伝播する必要があります。

14.1.1. キューの説明

キューは従来のFIFO キューで、一番前の要素を追加し、最後から削除します。キュークラスが提供するオペレーションにより、キューに値を置き (enqueue) 、キューから削除(dequeue)できるだけでなく、キューにある要素値を変更あるいは確認することができます。この実装例では、アレイを使いキューを表現しています。この例ではQUEUE_SIZE 要素の制限が課されています。

例14.1 キュークラスのJava インターフェース定義

	  public class TransactionalQueue extends LockManager
	  {
	  public TransactionalQueue (Uid uid);
	  public TransactionalQueue ();
	  public void finalize ();
	  
	  public void enqueue (int v) throws OverFlow, UnderFlow,
	  QueueError, Conflict;
	  public int dequeue  () throws OverFlow, UnderFlow,
	  QueueError, Conflict;
	  
	  public int queueSize ();
	  public int inspectValue (int i) throws OverFlow,
	  UnderFlow, QueueError, Conflict;
	  public void setValue (int i, int v) throws OverFlow,
	  UnderFlow, QueueError, Conflict;
	  
	  public boolean save_state (OutputObjectState os, int ObjectType);
	  public boolean restore_state (InputObjectState os, int ObjectType);
	  public String type ();
	  
	  public static final int QUEUE_SIZE = 40; // maximum size of the queue
	  
	  private int[QUEUE_SIZE] elements;
	  private int numberOfElements;
	  };

14.1.2. コンストラクタおよびデコンストラクタ

例14.2 既存の永続オブジェクトを利用

既存の永続オブジェクトを利用するには、永続オブジェクトのUid を取る必要のある特別なコンストラクタを使う必要があります。
	  public TransactionalQueue (Uid u)
	  {
	  super(u);
	  
	  numberOfElements = 0;
	  }

例14.3 新規永続オブジェクトの作成

	  public TransactionalQueue ()
	  {
	  super(ObjectType.ANDPERSISTENT);
	  
	  numberOfElements = 0;
	  
	  try
	  {
	  AtomicAction A = new AtomicAction();
	  
	  A.begin(0);	// Try to start atomic action
	  
	  // Try to set lock
	  
	  if (setlock(new Lock(LockMode.WRITE), 0) == LockResult.GRANTED)
	  {
	  A.commit(true);	// Commit
	  }
	  else 	// Lock refused so abort the atomic action
	  A.rollback();
	  }
	  catch (Exception e)
	  {
	  System.err.println(“Object construction error: “+e);
	  System.exit(1);
	  }
	  }
新規オブジェクトのコンストラクタ内でアトミックなアクションを使うには、前述したガイドラインに従ってください。こうすることで、適切なトップレベルのアトミックアクションがコミットされるとオブジェクトステートがオブジェクトストアに確実に書き込まれるようにします。コンストラクタでアトミックアクションを使うには、まずそのアクションを宣言し、begin メソッドを呼び出す必要があります。次に、その操作によりオブジェクトに対して適切なロックを設定しなければなりません。その後、コンストラクタの主要ボディ部分が実行されます。成功すると、アトミックアクションがコミットされ、失敗すると中断されます。
キュークラスのデストラクタが行う必要があるのは、LockManager メソッドのterminate メソッドを呼び出すだけです。

例14.4 キュークラスのデストラクタ

	  public void finalize ()
	  {
	  super.terminate();
	  }

14.1.3. save_staterestore_statetype メソッド

例14.5 save_state メソッド

	  public boolean save_state (OutputObjectState os, int ObjectType)
	  {
	  if (!super.save_state(os, ObjectType))
	  return false;
	  
	  try
	  {
	  os.packInt(numberOfElements);
	  
	  if (numberOfElements > 0)
	  {
	  for (int i = 0; i < numberOfElements; i++)
	  os.packInt(elements[i]);
	  }
	  
	  return true;
	  }
	  catch (IOException e)
	  {
	  return false;
	  }
	  }

例14.6 restore_state メソッド

	  public boolean restore_state (InputObjectState os, int ObjectType)
	  {
	  if (!super.restore_state(os, ObjectType))
	  return false;
	  
	  try
	  {
	  numberOfElements = os.unpackInt();
	  
	  if (numberOfElements > 0)
	  {
	  for (int i = 0; i < numberOfElements; i++)
	  elements[i] = os.unpackInt();
	  }
	  
	  return true;
	  }
	  catch (IOException e)
	  {
	  return false;
	  }
	  }

例14.7 type メソッド

キュークラスはLockManager クラスから継承されているため、操作タイプはトランザクショナルキューを返すはずです。
	  public String type ()
	  {
	  return "/StateManager/LockManager/TransactionalQueue";
	  }

14.1.4. enqueue/dequeue 操作

キュークラスの操作がアトミックなアクションの場合、ガイドラインのように例14.8「enqueue メソッド」enqueue 操作が適切です。dequeue も良く似た構造ですが、例には含まれていません。

例14.8 enqueue メソッド

	  public void enqueue (int v) throws OverFlow, UnderFlow, QueueError
	  {
	  AtomicAction A = new AtomicAction();
	  boolean res = false;
	  
	  try
	  {
	  A.begin(0);
	  
	  if (setlock(new Lock(LockMode.WRITE), 0) == LockResult.GRANTED)
	  {
	  if (numberOfElements < QUEUE_SIZE)
	  {
	  elements[numberOfElements] = v;
	  numberOfElements++;
	  res = true;
	  }
	  else
	  {
	  A.rollback();
	  throw new UnderFlow();
	  }
	  }
	  
	  if (res)
	  A.commit(true);
	  else
	  {
	  A.rollback();
	  throw new Conflict();
	  }
	  }
	  catch (Exception e1)
	  {
	  throw new QueueError();
	  }
	  }

14.1.5. queueSize メソッド

	public int queueSize () throws QueueError, Conflict
	{
	AtomicAction A = new AtomicAction();
	int size = -1;
	
	try
	{
	A.begin(0);
	
	if (setlock(new Lock(LockMode.READ), 0) == LockResult.GRANTED)
	size = numberOfElements;
	
	if (size != -1)
	A.commit(true);
	else
	{
	A.rollback();
	
	throw new Conflict();
	}
	}
	catch (Exception e1)
	{
	throw new QueueError();
	}
	
	return size;
	}

14.1.6. inspectValue および setValue メソッド

注記

setValueの実装は出ていませんが、提示されているinspectValue メソッドから推測していただけるでしょう。
	public int inspectValue (int index) throws UnderFlow,
	OverFlow, Conflict, QueueError
	{
	AtomicAction A = new AtomicAction();
	boolean res = false;
	int val = -1;
	
	try
	{
	A.begin();
	
	if (setlock(new Lock(LockMode.READ), 0) == LockResult.GRANTED)
	{
	if (index < 0)
	{
	A.rollback();
	throw new UnderFlow();
	}
	else
	{
	// array is 0 - numberOfElements -1
	
	if (index > numberOfElements -1)
	{
	A.rollback();
	throw new OverFlow();
	}
	else
	{
	val = elements[index];
	res = true;
	}
	}
	}
	
	if (res)
	A.commit(true);
	else
	{
	A.rollback();
	throw new Conflict();
	}
	}
	catch (Exception e1)
	{
	throw new QueueError();
	}
	
	return val;
	}

14.1.7. クライアント

クライアントのコード例には、完全コードというよりは表現部分のみが含まれています。オブジェクトに対して操作を呼び出す前に、クライアントはそれをバインドする必要があります。クライアントがローカルで稼働している場合、オブジェクトのインスタンスを作成するだけで結構です。

例14.9 TransactionalQueue オブジェクトのインスタンスを作成

	  public static void main (String[] args)
	  {
	  TransactionalQueue myQueue = new TransactionalQueue();
キューの操作の1つを呼び出す前に、クライアントはqueueSize メソッドを使いトランザクションを開始します。

例14.10 queueSize メソッド

	  AtomicAction A = new AtomicAction();
	  int size = 0;
	  
	  try
	  {
	  A.begin(0);
	  s
	  try
	  {
	  size = queue.queueSize();
	  }
	  catch (Exception e)
	  {
	  }
	  
	  if (size >= 0)
	  {
	  A.commit(true);
	  
	  System.out.println(“Size of queue: “+size);
	  }
	  else
	  A.rollback();
	  }
	  catch (Exception e)
	  {
	  System.err.println(“Caught unexpected exception!”);
	  }

14.1.8. 注記

キューオブジェクトは永続的であるため、オブジェクトステートはオブジェクトが存在するノードに障害があっても存続されます。オブジェクトに対して実行したアトミックアクションのうち、トップレベルで最後にコミットしたアクションにより生成された状況が保持されるのです。アプリケーションが自動的にenqueue 操作を実行する必要がある場合、別のアトミックアクションの中にenqueue 操作をネストすることができます。さらに、永続オブジェクトに対する同時操作をシリアル化することで、オブジェクトの状況に矛盾が発生しないようにします。キューオブジェクトの要素が個別で同時制御されているため、同時操作の呼出しで特定の組み合わせにおいては順番に実行されることもありますが、論理的には同時実行されているようになっています。これは、キューにある要素の状況を2種類変更する場合などに発生します。