エンティティー・マップおよびタプルとのローダーの使用

エンティティー・マネージャーは、すべてのエンティティー・オブジェクトをタプル・オブジェクトに変換してから、WebSphere® eXtreme Scale マップに保管します。どのエンティティーにもキー・タプルと値タプルがあります。 このキーと値のペアは、エンティティーの関連 eXtreme Scale マップに保管されます。eXtreme Scale マップをローダーと共に使用する場合、ローダーは、タプル・オブジェクトと対話する必要があります。

eXtreme Scale には、リレーショナル・データベースとの統合を簡素化する Loader プラグインが含まれています。Java Persistence API (JPA) ローダーは、Java Persistence API を使用して、データベースと対話し、エンティティー・オブジェクトを作成します。 この JPA ローダーは、eXtreme Scale エンティティーと互換性があります。

タプル

タプルには、エンティティーの属性およびアソシエーションに関する情報が入っています。プリミティブ値は、プリミティブ・ラッパーを使用して保管されます。他のサポートされるオブジェクト・タイプは、そのネイティブ・フォーマットで保管されます。 他のエンティティーに対するアソシエーションは、ターゲット・エンティティーのキーを表すキー・タプル・オブジェクトのコレクションとして保管されます。

各属性またはアソシエーションは、ゼロ・ベース索引を使用して保管されます。各属性の索引を getAttributePosition メソッド、または getAssociationPosition メソッドを使用して取得できます。 位置が取得されると、その位置は eXtreme Scale ライフサイクルの実行期間中は変更されません。位置が変更 される可能性があるのは、eXtreme Scale が再始動 されるときです。タプルのエレメントの更新には、setAttribute メソッド、setAssociation メソッド、および setAssociations メソッドが使用されます。

重要: タプル・オブジェクトを作成または更新する場合、各プリミティブ・フィールドを非ヌル値で更新します。int などのプリミティブ値は、ヌルであってはなりません。値をデフォルトに変更しないと、パフォーマンスが低下するという問題が起こる可能性があり、エンティティー記述子 XML ファイル内の @Version アノテーションでマークされたフィールドやバージョン属性にも影響します。

以下の例では、タプルの処理方法について詳しく説明します。 この例の場合のエンティティーの定義について詳しくは、エンティティー・マネージャーのチュートリアル: Order エンティティー・スキーマを参照してください。 WebSphere eXtreme Scale は各エンティティーでローダーを使用するよう構成されています。また、取得されるのは Order エンティティーのみで、この特定のエンティティーは Customer エンティティーと多対 1 のリレーションシップを保有しています。 属性名は customer で、これは OrderLine エンティティーと 1 対多のリレーションシップを保有しています。

プロジェクターを使用して、エンティティーから自動的にタプル・オブジェクトを作成します。プロジェクターを使用すると、Hibernate や JPA などのオブジェクト関係マッピング・ユーティリティーを使用する場合にローダーを簡素化することができます。

order.java

@Entity
public class Order
{
    @Id  String orderNumber;
    java.util.Date date;
    @OneToOne(cascade=CascadeType.PERSIST) Customer customer;
    @OneToMany(cascade=CascadeType.ALL, mappedBy="order") @OrderBy("lineNumber") List<OrderLine> lines;
}

customer.java

@Entity
public class Customer {
    @Id String id;
    String firstName;
    String surname;
    String address;
    String phoneNumber;
}

orderLine.java

@Entity
public class OrderLine
{
    @Id @ManyToOne(cascade=CascadeType.PERSIST) Order order;
    @Id int lineNumber;
    @OneToOne(cascade=CascadeType.PERSIST) Item item;
    int quantity;
    double price;
}

Loader インターフェースを実装する OrderLoader クラス を以下のコードに示します。以下の例では、関連の TransactionCallback プラグインが定義されているものとします。

orderLoader.java

public class OrderLoader implements com.ibm.websphere.objectgrid.plugins.Loader {

	private EntityMetadata entityMetaData;
	public void batchUpdate(TxID txid, LogSequence sequence) 
            throws LoaderException, OptimisticCollisionException {
        ...
        }
        public List get(TxID txid, List keyList, boolean forUpdate)	
            throws LoaderException {
        ...
        }
        public void preloadMap(Session session, BackingMap backingMap) 
            throws LoaderException {
		this.entityMetaData=backingMap.getEntityMetadata();
	}

}

eXtreme Scale からの preLoadMap メソッド呼び出し中に、インスタンス変数 entityMetaData が初期化されます。エンティティーを使用するようにマップが構成されている場合、entityMetaData 変数はヌルにはなりません。それ以外の場合、値は NULL です。

batchUpdate メソッド

batchUpdate メソッドを使用することで、アプリケーションがどのアクションを実行しようとしているかを知ることができます。挿入、更新、または削除操作に基づいて、 データベースへの接続がオープンされ、作業が実行されます。キーと値のタイプは Tuple のため、これらを SQL ステートメントで意味を成す値に変換する必要があります。

以下のコードに示されているように、ORDER テーブルは、以下のデータ定義言語 (DDL) 定義 を使用して作成されました。

CREATE TABLE ORDER (ORDERNUMBER VARCHAR(250) NOT NULL, DATE TIMESTAMP, CUSTOMER_ID VARCHAR(250))
ALTER TABLE ORDER ADD CONSTRAINT PK_ORDER PRIMARY KEY (ORDERNUMBER)

以下のコードは、Tuple を Object に変換する方法を示しています。

public void batchUpdate(TxID txid, LogSequence sequence)
            throws LoaderException, OptimisticCollisionException {
        Iterator iter = sequence.getPendingChanges();
        while (iter.hasNext()) {
            LogElement logElement = (LogElement) iter.next();
            Object key = logElement.getKey();
            Object value = logElement.getCurrentValue();

            switch (logElement.getType().getCode()) {
            case LogElement.CODE_INSERT:

1)              if (entityMetaData!=null) {

// The order has just one key orderNumber
2)                  String ORDERNUMBER=(String) getKeyAttribute("orderNumber", (Tuple) key);
// Get the value of date
3)                  java.util.Date unFormattedDate = (java.util.Date) getValueAttribute("date",(Tuple)value);
// The values are 2 associations. Lets process customer because
// the our table contains customer.id as primary key
4)                  Object[] keys= getForeignKeyForValueAssociation("customer","id",(Tuple) value);
                    //Order to Customer is M to 1. There can only be 1 key
5)                  String CUSTOMER_ID=(String)keys[0];
// parse variable unFormattedDate and format it for the database as formattedDate
6)                   String formattedDate = "2007-05-08-14.01.59.780272"; // formatted for DB2
// Finally, the following SQL statement to insert the record
7) //INSERT INTO ORDER (ORDERNUMBER, DATE, CUSTOMER_ID) VALUES(ORDERNUMBER,formattedDate, CUSTOMER_ID)
                }
                break;
            case LogElement.CODE_UPDATE:
                break;
            case LogElement.CODE_DELETE:
                break;
            }
        }

    }
// returns the value to attribute as stored in the key Tuple
 private Object getKeyAttribute(String attr, Tuple key) {
        //get key metadata
        TupleMetadata keyMD = entityMetaData.getKeyMetadata();
        //get position of the attribute
        int keyAt = keyMD.getAttributePosition(attr);
        if (keyAt > -1) {
            return key.getAttribute(keyAt);
        } else { // attribute undefined
            throw new IllegalArgumentException("Invalid position index for  "+attr);
        }

    }
// returns the value to attribute as stored in the value Tuple
    private Object getValueAttribute(String attr, Tuple value) {
        //similar to above, except we work with value metadata instead
        TupleMetadata valueMD = entityMetaData.getValueMetadata();

        int keyAt = valueMD.getAttributePosition(attr);
        if (keyAt > -1) {
            return value.getAttribute(keyAt);
        } else {
            throw new IllegalArgumentException("Invalid position index for  "+attr);
        }
    }
// returns an array of keys that refer to association.
    private Object[] getForeignKeyForValueAssociation(String attr, String fk_attr, Tuple value) {
        TupleMetadata valueMD = entityMetaData.getValueMetadata();
        Object[] ro;

        int customerAssociation = valueMD.getAssociationPosition(attr);
        TupleAssociation tupleAssociation = valueMD.getAssociation(customerAssociation);

        EntityMetadata targetEntityMetaData = tupleAssociation.getTargetEntityMetadata();

        Tuple[] customerKeyTuple = ((Tuple) value).getAssociations(customerAssociation);

        int numberOfKeys = customerKeyTuple.length;
        ro = new Object[numberOfKeys];

        TupleMetadata keyMD = targetEntityMetaData.getKeyMetadata();
        int keyAt = keyMD.getAttributePosition(fk_attr);
        if (keyAt < 0) {
            throw new IllegalArgumentException("Invalid position index for  " + attr);
        }
        for (int i = 0; i < numberOfKeys; ++i) {
            ro[i] = customerKeyTuple[i].getAttribute(keyAt);
        }

        return ro;

    }
  1. entityMetaData が非ヌルであることを確認します。これは、そのキーと値のキャッシュ・エントリーのタイプが Tuple であることを意味します。 entityMetaData から Key TupleMetaData が取り出されます。 これは、Order メタデータのキー部分のみを実際に反映したものです。
  2. KeyTuple を処理して Key Attribute orderNumber の値を取得します。
  3. ValueTuple を処理して属性の日付の値を取得します。
  4. ValueTuple を処理して、関連するカスタマーから Keys の値を取得します。
  5. CUSTOMER_ID を抽出します。 リレーションシップをベースにして、Order には単一のカスタマーのみが存在し、ユーザーは単一のキーのみを保有することができます。そのため、キーのサイズは 1 です。簡単にするために、フォーマットを訂正する日付の構文解析はスキップします。
  6. これは挿入操作のため、SQL ステートメントがデータ・ソース接続に渡されて、挿入操作が完了されます。

トランザクション区分およびデータベースへのアクセスは、ローダーの作成で取り上げています。

get メソッド

キャッシュ内でキーが検出されなかった場合は、Loader プラグインの get メソッドを呼び出し、キーを検出します。

キーは Tuple です。最初のステップは Tuple から、SELECT SQL ステートメントに渡すことができるプリミティブ値への変換を行います。データベースからすべての属性を取得したら、Tuple に変換する必要があります。以下のコードは Order クラスを示しています。
public List get(TxID txid, List keyList, boolean forUpdate)	throws LoaderException {
		System.out.println("OrderLoader: Get called");
		ArrayList returnList = new ArrayList();

1)		if (entityMetaData != null) {
			int index=0;
			for (Iterator iter = keyList.iterator(); iter.hasNext();) {
2)				Tuple orderKeyTuple=(Tuple) iter.next();

				// The order has just one key orderNumber
3)				String ORDERNUMBERKEY = (String) getKeyAttribute("orderNumber",orderKeyTuple);
				//We need to run a query to get values of
4)				// SELECT CUSTOMER_ID, date FROM ORDER WHERE ORDERNUMBER='ORDERNUMBERKEY'

5)				//1) Foreign key: CUSTOMER_ID
6)				//2) date
				// Assuming those two are returned as
7)                              String  CUSTOMER_ID = "C001"; // Assuming Retrieved and initialized
8)				java.util.Date retrievedDate = new java.util.Date(); 
                                // Assuming this date reflects the one in database

				// We now need to convert this data into a tuple before returning

				//create a value tuple
9)				TupleMetadata valueMD = entityMetaData.getValueMetadata();
				Tuple valueTuple=valueMD.createTuple();


				//add retrievedDate object to Tuple
				int datePosition = valueMD.getAttributePosition("date");
10)				valueTuple.setAttribute(datePosition, retrievedDate);

				//Next need to add the Association
11)				int customerPosition=valueMD.getAssociationPosition("customer");
				TupleAssociation customerTupleAssociation = 
                                  valueMD.getAssociation(customerPosition);
				EntityMetadata customerEMD = customerTupleAssociation.getTargetEntityMetadata();
				TupleMetadata customerTupleMDForKEY=customerEMD.getKeyMetadata();
12)				int customerKeyAt=customerTupleMDForKEY.getAttributePosition("id");


				Tuple customerKeyTuple=customerTupleMDForKEY.createTuple();
				customerKeyTuple.setAttribute(customerKeyAt, CUSTOMER_ID);
13)				valueTuple.addAssociationKeys(customerPosition, new Tuple[] {customerKeyTuple});


14)				int linesPosition = valueMD.getAssociationPosition("lines");
				TupleAssociation linesTupleAssociation = valueMD.getAssociation(linesPosition);
				EntityMetadata orderLineEMD = linesTupleAssociation.getTargetEntityMetadata();
				TupleMetadata orderLineTupleMDForKEY = orderLineEMD.getKeyMetadata();
				int lineNumberAt = orderLineTupleMDForKEY.getAttributePosition("lineNumber");
				int orderAt = orderLineTupleMDForKEY.getAssociationPosition("order");

				if (lineNumberAt < 0 || orderAt < 0) {
					throw new IllegalArgumentException(
                                        "Invalid position index for lineNumber or order "+ 
                                        lineNumberAt + " " + orderAt);
				}
15) // SELECT LINENUMBER FROM ORDERLINE WHERE ORDERNUMBER='ORDERNUMBERKEY'
				// Assuming two rows of line number are returned with values 1 and 2

				Tuple orderLineKeyTuple1 = orderLineTupleMDForKEY.createTuple();
				orderLineKeyTuple1.setAttribute(lineNumberAt, new Integer(1));// set Key
				orderLineKeyTuple1.addAssociationKey(orderAt, orderKeyTuple);

				Tuple orderLineKeyTuple2 = orderLineTupleMDForKEY.createTuple();
				orderLineKeyTuple2.setAttribute(lineNumberAt, new Integer(2));// Init Key
				orderLineKeyTuple2.addAssociationKey(orderAt, orderKeyTuple);

16)				valueTuple.addAssociationKeys(linesPosition, new Tuple[] 
                                    {orderLineKeyTuple1, orderLineKeyTuple2 });

				returnList.add(index, valueTuple);

				index++;

			}
		}else {
			// does not support tuples
		}
		return returnList;
	}
  1. get メソッドは、ローダーが取り出すキーと要求を ObjectGrid キャッシュによって検出できなかった場合に呼び出されます。entityMetaData 値を検査し、非ヌルであれば処理を続行します。
  2. keyList に Tuple が含まれます。
  3. 属性 orderNumber の値を取得します。
  4. 日付 (値) およびカスタマー ID (外部キー) を取得するには、照会を実行します。
  5. CUSTOMER_ID は、アソシエーション・タプルで設定する必要がある外部キーです。
  6. 日付は値で、事前に設定されている必要があります。
  7. この例は JDBC 呼び出しを実行しないため、CUSTOMER_ID が想定されます。
  8. この例は JDBC 呼び出しを実行しないため、日付が想定されます。
  9. 値 Tuple を作成します。
  10. その位置をベースにして、Tuple に日付の値を設定します。
  11. Order には 2 つのアソシエーションがあります。まず、Customer エンティティーを参照する属性 customer から開始します。Tuple に設定する ID の値が必要です。
  12. カスタマー・エンティティー上で ID の位置を検索します。
  13. アソシエーション・キーの値のみを設定します。
  14. また、行はカスタマー・アソシエーションの場合と同様にアソシエーション・キーのグループとしてセットアップする必要があるアソシエーションです。
  15. このオーダーと関連する lineNumber のキーをセットアップする必要があるため、SQL を実行して lineNumber の値を取得します。
  16. valueTuple でアソシエーション・キーをセットアップします。これで BackingMap に戻される Tuple の作成が完了します。

このトピックには、タプルの作成手順、および Order エンティティーの説明のみが含まれています。他のエンティティーや TransactionCallback プラグインと結び付けられているプロセス全体に対しても同様の手順を実行してください。詳しくは、トランザクションのライフサイクル・イベントの管理のためのプラグインを参照してください。