この章では、テキスト・ファイルから読み取ったドメイン・オブジェクトをCoherenceキャッシュに移入する方法を学習します。
この章の内容は次のとおりです。
これまでは、オブジェクトの挿入と取得を個別に行ってきました。この方法では、putを行うたびに、特にパーティション・キャッシュおよびレプリケーション・キャッシュでネットワーク・トラフィックが増加する可能性があります。また、putをコールするたびにキャッシュで置換されたばかりのオブジェクトが返され、不要なオーバーヘッドが追加されます。かわりにputAllメソッドを使用してキャッシュをロードすることで、効率を大幅に向上させることができます。
この章では、「複合オブジェクトの作成およびキャッシング」を完了していると想定しています。また、java.io.BufferedReaderを使用したテキスト・ファイルの読取り、java.lang.String.splitメソッドを使用したテキスト・ファイルの解析、およびjava.text.SimpleDateFormatを使用した日付の解析をユーザーが十分に理解していると想定しています。
この演習では、Coherenceキャッシュにドメイン・オブジェクトを移入するコンソール・アプリケーションを作成する方法を示します。このアプリケーションでは、Coherenceのcom.tangosol.io.pof.PortableObjectの実装を使用できます。
Contactオブジェクト、キャッシュにデータを提供するジェネレータ、およびキャッシュをロードするローダーを取得するためのキーを作成します。
次の手順に従い、ドメイン・オブジェクトのキーを含むクラスを作成します。
Loadingという新規プロジェクトを作成します。
新規プロジェクトの作成の詳細は、「複合オブジェクトの作成およびキャッシング」を参照してください。
クラスパスにcoherence.jarを追加します。前述の演習(Contacts)で作成したAddressクラス、PhoneNumberクラスおよびContactクラスに関連するクラスおよびファイルも追加します。これらのファイルはc:\home\oracle\labsおよびc:\home\oracle\labs\Contacts\classesにあります(ヒント: プロジェクトを右クリックし、「プロジェクト・プロパティ」→「ライブラリとクラスパス」を選択します。「プロジェクト・クラスパスへのJARおよびライブラリの追加」を参照してください)。
情報追跡の対象となる従業員のキーを提供する連絡先IDクラスを作成します。Javaクラスの作成の詳細は、「Javaクラスの作成」を参照してください。
連絡先IDを従業員のフル・ネームに基づいて作成します。このオブジェクトは、Contactオブジェクトを取得するキーとして機能します。
このクラスはPOFシリアライズを使用するため、PortableObjectを実装し、writeExternalおよびreadExternal PortableObjectメソッドと、equals、hashCodeおよびtoStringオブジェクト・メソッドを実装する必要があります。
|
注意: キャッシュのキーと値は、シリアライズ可能(たとえば、java.io.Serializable)である必要があります。さらに、キャッシュ・キーはhashCode()メソッドとequals()メソッドを実装する必要があり、これらのメソッドはクラスタ・ノード間で一貫性のある結果を返す必要があります。これは、hashCode()およびequals()の実装は、オブジェクトのシリアライズ可能な状態(つまり、オブジェクトの一時的でないフィールド)にのみ基づいている必要があることを示しています。String、Integer、Dateなどのほとんどの組込みのJava型はこの要件を満たしています。一部のキャッシュの実装(特にパーティション・キャッシュ)では、等価の検証にキー・オブジェクトのシリアライズされた形式を使用します。つまり、equals()でtrueを返すキーは、同様の方法でシリアライズされる必要があります。ほとんどの組込みのJava型はこの要件を満たしています。 |
例5-1に、可能な解決策を示します。
例5-1 単純な連絡先IDクラス
package com.oracle.handson;
import com.tangosol.io.pof.PofReader;
import com.tangosol.io.pof.PofWriter;
import com.tangosol.io.pof.PortableObject;
import com.tangosol.util.Base;
import com.tangosol.util.HashHelper;
import java.io.IOException;
/**
* ContactId is a key to the person for whom information is
* tracked.
*/
public class ContactId
implements PortableObject
{
// ----- constructors ---------------------------------------------------
/**
* Default constructor (necessary for PortableObject implementation).
*/
public ContactId() {
}
/**
* Construct a contact person.
*
*/
public ContactId(String FirstName, String LastName)
{
super();
this.FirstName = FirstName;
this.LastName = LastName;
}
// ----- accessors ------------------------------------------------------
/**
* Return the first name.
*
*/
public String getFirstName()
{
return FirstName;
}
/**
* Return the last name.
*
*/
public String getLastName()
{
return LastName;
}
// ----- PortableObject interface ---------------------------------------
public void readExternal(PofReader reader)
throws IOException
{
FirstName = reader.readString(0);
LastName = reader.readString(1);
}
public void writeExternal(PofWriter writer)
throws IOException
{
writer.writeString(0, FirstName);
writer.writeString(1, LastName);
}
// ----- Object methods -------------------------------------------------
public boolean equals(Object oThat)
{
if (this == oThat)
{
return true;
}
if (oThat == null)
{
return false;
}
ContactId that = (ContactId) oThat;
return Base.equals(getFirstName(), that.getFirstName()) &&
Base.equals(getLastName(), that.getLastName());
}
public int hashCode()
{
return HashHelper.hash(getFirstName(),
HashHelper.hash(getLastName(), 0));
}
public String toString()
{
return getFirstName() + " " + getLastName();
}
// ----- data members ---------------------------------------------------
/**
* First name.
*/
private String FirstName;
/**
* Last name.
*/
private String LastName;
}
POF構成ファイルを編集します。ContactIDの<user-type>エントリをcontacts-pof-config.xmlに追加します。ファイルは例5-2のようになります。
例5-2 ContactIdエントリのあるPOF構成ファイル
<?xml version="1.0"?>
<!DOCTYPE pof-config SYSTEM "pof-config.dtd">
<pof-config>
<user-type-list>
<!-- coherence POF user types -->
<include>coherence-pof-config.xml</include>
<!-- com.tangosol.examples package -->
<user-type>
<type-id>1001</type-id>
<class-name>com.oracle.handson.Contact</class-name>
</user-type>
<user-type>
<type-id>1002</type-id>
<class-name>com.oracle.handson.Address</class-name>
</user-type>
<user-type>
<type-id>1003</type-id>
<class-name>com.oracle.handson.PhoneNumber</class-name>
</user-type>
<user-type>
<type-id>1004</type-id>
<class-name>com.oracle.handson.ContactId</class-name>
</user-type>
</user-type-list>
<allow-interfaces>true</allow-interfaces>
<allow-subclasses>true</allow-subclasses>
</pof-config>
ランダムな従業員の名前と住所を生成するDataGeneratorという名前のJavaクラスを作成します。詳細は、「Javaクラスの作成」を参照してください。
前述の演習で作成したAddressクラス、PhoneNumberクラスおよびContactクラスを使用します。ランダムな名前、住所、電話番号および年齢を生成するには、java.util.Randomを使用します。
例5-3に、可能な解決策を示します。この解決策では、従業員の連絡先情報を含むテキスト・ファイルcontacts.cvsを作成します。プロジェクト・ディレクトリのルート(この場合はC:\home\oracle\labs\Loading)に、このファイルを格納します。
例5-3 データ生成クラスのサンプル
package com.oracle.handson;
import com.oracle.handson.Address;
import com.oracle.handson.Contact;
import com.oracle.handson.PhoneNumber;
import com.tangosol.util.Base;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.sql.Date;
import java.util.Collections;
import java.util.Random;
/**
* DataGenerator is a generator of sample contacts.
*/
public class DataGenerator
{
// ----- static methods -------------------------------------------------
/**
* Generate contacts.
*/
public static void main(String[] asArg)
throws IOException
{
String sFile = asArg.length > 0 ? asArg[0] : FILENAME;
int cCon = asArg.length > 1 ? Integer.parseInt(asArg[1]) : 1000;
OutputStream out = new FileOutputStream(sFile);
generate(out, cCon);
out.close();
}
/**
* Generate the contacts and write them to a file.
*/
public static void generate(OutputStream out, int cContacts)
throws IOException
{
PrintWriter writer = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(out)));
for (int i = 0; i < cContacts; ++i)
{
StringBuffer sb = new StringBuffer(256);
//contact person
sb.append("John,")
.append(getRandomName())
.append(',');
// home and work addresses
sb.append(Integer.toString(Base.getRandom().nextInt(999)))
.append(" Beacon St.,,") /*street1,empty street2*/
.append(getRandomName()) /*random city name*/
.append(',')
.append(getRandomState())
.append(',')
.append(getRandomZip())
.append(",US,Yoyodyne Propulsion Systems,")
.append("330 Lectroid Rd.,Grover's Mill,")
.append(getRandomState())
.append(',')
.append(getRandomZip())
.append(",US,");
// home and work telephone numbers
sb.append("home,")
.append(Base.toDelimitedString(getRandomPhoneDigits(), ","))
.append(",work,")
.append(Base.toDelimitedString(getRandomPhoneDigits(), ","))
.append(',');
// random birth date in millis before or after the epoch
sb.append(getRandomDateInMillis());
writer.println(sb);
}
writer.flush();
}
/**
* Return a random name.
*
*/
private static String getRandomName()
{
Random rand = Base.getRandom();
int cCh = 4 + rand.nextInt(7);
char[] ach = new char[cCh];
ach[0] = (char) ('A' + rand.nextInt(26));
for (int of = 1; of < cCh; ++of)
{
ach[of] = (char) ('a' + rand.nextInt(26));
}
return new String(ach);
}
/**
* Return a random phone muber.
* The phone number includes access, country, area code, and local
* number.
*
*/
private static int[] getRandomPhoneDigits()
{
Random rand = Base.getRandom();
return new int[] {
11, // access code
rand.nextInt(99), // country code
rand.nextInt(999), // area code
rand.nextInt(9999999) // local number
};
}
/**
* Return a random Phone.
*
*/
private static PhoneNumber getRandomPhone()
{
int[] anPhone = getRandomPhoneDigits();
return new PhoneNumber((short)anPhone[0], (short)anPhone[1],
(short)anPhone[2], anPhone[3]);
}
/**
* Return a random Zip code.
*
*/
private static String getRandomZip()
{
return Base.toDecString(Base.getRandom().nextInt(99999), 5);
}
/**
* Return a random state.
*
*/
private static String getRandomState()
{
return STATE_CODES[Base.getRandom().nextInt(STATE_CODES.length)];
}
/**
* Return a random date in millis before or after the epoch.
*
*/
private static long getRandomDateInMillis()
{
return (Base.getRandom().nextInt(40) - 20) * Contact.MILLIS_IN_YEAR;
}
/**
* Generate a Contact with random information.
*
*/
public static Contact getRandomContact()
{
return new Contact("John",
getRandomName(),
new Address("1500 Boylston St.", null, getRandomName(),
getRandomState(), getRandomZip(), "US"),
new Address("8 Yawkey Way", null, getRandomName(),
getRandomState(), getRandomZip(), "US"),
Collections.singletonMap("work", getRandomPhone()),
new Date(getRandomDateInMillis()));
}
// ----- constants ------------------------------------------------------
/**
* US Postal Service two letter postal codes.
*/
private static final String[] STATE_CODES = {
"AL", "AK", "AS", "AZ", "AR", "CA", "CO", "CT", "DE", "OF", "DC",
"FM", "FL", "GA", "GU", "HI", "ID", "IL", "IN", "IA", "KS", "KY",
"LA", "ME", "MH", "MD", "MA", "MI", "MN", "MS", "MO", "MT", "NE",
"NV", "NH", "NJ", "NM", "NY", "NC", "ND", "MP", "OH", "OK", "OR",
"PW", "PA", "PR", "RI", "SC", "SD", "TN", "TX", "UT", "VT", "VI",
"VA", "WA", "WV", "WI", "WY"
};
/**
* Default contacts file name.
*/
public static final String FILENAME = "contacts.csv";
}
LoaderExampleというJavaクラスを作成します。詳細は、「Javaクラスの作成」を参照してください。
このクラスは、前項のプログラムで生成された従業員データをキャッシュにロードします。contacts.csvファイルに含まれるすべての従業員情報を単一のCoherenceキャッシュにロードするには、入力ストリームおよびバッファ・リーダーを使用します。
このアプリケーションには、データ・ファイル内の従業員情報を解析するコードが含まれている必要があります。この情報を取得したら、キャッシュに挿入する個別の連絡先を構成します。処理作業を軽減し、ネットワーク・トラフィックを最低限に抑えるには、putAllメソッドを使用してキャッシュをロードします。
例5-4に、可能な解決策を示します。
例5-4 キャッシュ・ロード・プログラムのサンプル
package com.oracle.handson;
import com.tangosol.net.CacheFactory;
import com.tangosol.net.NamedCache;
import com.oracle.handson.ContactId;
import com.oracle.handson.Address;
import com.oracle.handson.PhoneNumber;
import com.oracle.handson.Contact;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.sql.Date;
import java.util.HashMap;
import java.util.Map;
/**
* LoaderExample loads contacts into the cache from a files.
*
*/
public class LoaderExample
{
// ----- static methods -------------------------------------------------
/**
* Load contacts.
*/
public static void main (String[] asArg)
throws IOException
{
String sFile = asArg.length > 0 ? asArg[0] : DataGenerator.FILENAME;
String sCache = asArg.length > 1 ? asArg[1] : CACHENAME;
System.out.println("input file: " + sFile);
System.out.println("cache name: " + sCache);
new LoaderExample().load(CacheFactory.getCache(sCache),
new FileInputStream(sFile));
CacheFactory.shutdown();
}
/**
* Load cache from stream.
*
*/
public void load(NamedCache cache, InputStream in)
throws IOException
{
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
Map mapBatch = new HashMap(1024);
String sRecord;
int cRecord = 0;
while ((sRecord = reader.readLine()) != null)
{
// parse record
String[] asPart = sRecord.split(",");
int ofPart = 0;
String sFirstName = asPart[ofPart++];
String sLastName = asPart[ofPart++];
ContactId id = new ContactId(sFirstName, sLastName);
Address addrHome = new Address(
/*streetline1*/ asPart[ofPart++],
/*streetline2*/ asPart[ofPart++],
/*city*/ asPart[ofPart++],
/*state*/ asPart[ofPart++],
/*zip*/ asPart[ofPart++],
/*country*/ asPart[ofPart++]);
Address addrWork = new Address(
/*streetline1*/ asPart[ofPart++],
/*streetline2*/ asPart[ofPart++],
/*city*/ asPart[ofPart++],
/*state*/ asPart[ofPart++],
/*zip*/ asPart[ofPart++],
/*country*/ asPart[ofPart++]);
Map mapTelNum = new HashMap();
for (int c = asPart.length - 1; ofPart < c; )
{
mapTelNum.put(/*type*/ asPart[ofPart++], new PhoneNumber(
/*access code*/ Short.parseShort(asPart[ofPart++]),
/*country code*/ Short.parseShort(asPart[ofPart++]),
/*area code*/ Short.parseShort(asPart[ofPart++]),
/*local num*/ Integer.parseInt(asPart[ofPart++])));
}
Date dtBirth = new Date(Long.parseLong(asPart[ofPart]));
// Construct Contact and add to batch
Contact con1 = new Contact(sFirstName, sLastName, addrHome, addrWork, mapTelNum, dtBirth); System.out.println(con1); mapBatch.put(id, con1);
++cRecord;
if (cRecord % 1024 == 0)
{
// load batch
cache.putAll(mapBatch);
mapBatch.clear();
System.out.print('.');
System.out.flush();
}
}
if (!mapBatch.isEmpty())
{
// load final batch
cache.putAll(mapBatch);
}
System.out.println("Added " + cRecord + " entries to cache");
}
// ----- constants ------------------------------------------------------
/**
* Default cache name.
*/
public static final String CACHENAME = "ContactsCache";
}
contacts-cache-server.cmdファイルを編集して、Loadingプロジェクトのコンパイル済クラス(この場合はC:\home\oracle\labs\Loading\classes)を追加します。キャッシュ構成ファイルが格納されているディレクトリ(この場合はC:\home\oracle\labs)のパスが記載されていることを確認します。
contacts-cache-server.cmdファイルは、例5-5のようになります。
例5-5 contacts-cache-server.cmdファイルのサンプル
@echo off
setlocal
if (%COHERENCE_HOME%)==() (
set COHERENCE_HOME=c:\oracle\product\coherence
)
set COH_OPTS=%COH_OPTS% -server -cp %COHERENCE_HOME%\lib\coherence.jar;C:\home\oracle\labs\Contacts\classes;C:\home\oracle\labs\Loading\classes;C:\home\oracle\labs;
set COH_OPTS=%COH_OPTS% -Dtangosol.coherence.cacheconfig=\home\oracle\labs\contacts-cache-config.xml
java %COH_OPTS% -Xms1g -Xmx1g -Xloggc: com.tangosol.net.DefaultCacheServer %2 %3 %4 %5 %6 %7
:exit
次の手順に従い、キャッシュ・ロード例を実行します。
「プロジェクト・プロパティ」→「実行/デバッグ/プロファイル」の「Javaオプション」フィールドにキャッシュ構成のパスを追加します。
-Dtangosol.coherence.cacheconfig=\home\oracle\labs\contacts-cache-config.xml
LoadingプロジェクトでまだJavaファイルがコンパイルされていない場合は、これらをコンパイルします。JDeveloperでこのプロジェクトを右クリックし、「Loading.jprのメイク」を選択します。
稼働しているキャッシュ・サーバーがあれば停止します。contacts-cache-server.cmdファイルでキャッシュ・サーバーを起動します。
JDeveloperからDataGeneratorを実行し、LoaderExampleを実行します。
従業員の連絡先情報のストリームは、JDeveloperの出力ウィンドウに表示されます。図5-2に例を示します。
この演習では、キャッシュ内のデータの問合せおよび集計の概念を紹介します。この演習では、次の方法を説明します。
キャッシュでの特定データの問合せ
キャッシュ内の情報の集計
名前付きキャッシュに複合オブジェクトを挿入した後で、グリッド内の情報を問い合せ、集計することができます。com.tangosol.util.QueryMapインタフェースで、キャッシュ内の値やキーを管理できます。フィルタを使用して結果を制限できます。また、索引を定義して問合せを最適化することもできます。
Coherenceでは格納時に情報がシリアライズされるため、問合せの際はデシリアライズによるオーバーヘッドも発生します。索引を追加すると、索引自体に保存されている値はデシリアライズされるため、アクセス時間が短縮されます。索引付けされるオブジェクトは、常にシリアライズされています。
QueryMapインタフェースには、よく使用される次のようなメソッドがあります。
Set entrySet(Filterfilter): マップ内のエントリのうち、フィルタ条件を満たす一連のエントリを返します。
addIndex(ValueExtractorextractor,booleanfOrdered, Comparator comparator): 索引を追加します。
Set keySet(Filter filter): entrySetに似ていますが、値ではなくキーを返します。
フィルタリングはキャッシュ・エントリ所有者レベルで発生することに注意してください。パーティション化されたトポロジでは、フィルタリングを行うのはプライマリ・パーティションであるため、フィルタリングはパラレル実行できます。QueryMapインタフェースでは、Filterクラスが使用されます。これらのクラスの詳細は、com.tangosol.util.filterパッケージのAPIを参照してください。
CoherenceのNamedCacheはすべて、com.tangosol.util.QueryMapインタフェースを実装します。これにより、NamedCacheでは、キャッシュ内でなんらかの条件を満たすキーまたはエントリの検索がサポートされています。この条件は、com.tangosol.util.Filterインタフェースを実装するオブジェクトとして表現できます。
com.tangosol.util.filterパッケージには、標準的な問合せ式を実装する事前定義されたクラスがいくつか含まれています。このようなクラスの例として、GreaterFilter、GreaterEquals、LikeFilter、NotEqualsFilter、InFilterなどがあります。これらのフィルタを使用すると、ほとんどのSQL WHERE句式をオブジェクトベースで表現した式を構成できます。
|
注意: CoherenceにはSQLFilterはありません。これは、キャッシュ内に配置されたオブジェクトがリレーショナル形式で、つまり行と列を使用してモデリングされる可能性が低いためです(オブジェクトは通常、データベースで表現されるため)。また、キャッシュ内に配置されたオブジェクトは通常、大規模なBLOBなどのリレーショナル・モデルを使用して簡単にモデリングはできません。 |
Filterクラスは、標準的なJavaメソッド・リフレクションを使用してテスト条件を表現します。たとえば、次のフィルタは、キャッシュ内のオブジェクトに対するgetHomeAddress.getStateメソッドから返された値がMassachusetts用(MA)である条件を表します。
(new EqualsFilter("getHomeAddress.getState", "MA");
このフィルタでテストしたオブジェクトにgetメソッドが指定されていない場合、テストは失敗します。
次の2つの例を見ると、より明確になります。
名前がSで始まる都市に住む人のセットを返します。
Set sPeople = cache.entrySet(new LikeFilter("getHomeAddress.getCity","S%");
42歳を超える人を含むセットを返します。
final int nAge = 42;// Find all contacts who are older than nAge
Set sSeniors = cache.entrySet(new GreaterFilter("getAge", nAge));
QueryMapインタフェースで定義されるentrySetメソッドとkeySetメソッドのほかに、Coherenceでは、問合せのパフォーマンスを改善するため、addIndexメソッドを使用した索引の定義もサポートされています。既知かつ厳密に施行された名前付き列のコレクション(スキーマ)に従って索引が定義されるリレーショナル・データベース・システムとは異なり、Coherenceにはスキーマがありません。データの形式的スキーマがないと、柔軟性とポリモフィズムが大幅に向上しますが、アプリケーション内では、索引を定義するため、つまり問合せのパフォーマンスを改善するために、従来のデータベース・システムとは異なる方法が必要となります。
キャッシュに配置されているオブジェクトごとに索引付けされる値を定義するために、CoherenceではValueExtractorの概念が導入されています。com.tangosol.util.ValueExtractorは、extractメソッドを定義する単純なインタフェースです。オブジェクトのパラメータを指定すると、ValueExtractorはそのパラメータに基づいてなんらかの値を返します。
ValueExtractor実装の簡単な例com.tangosol.util.extractor.ReflectionExtractorでは、リフレクションを使用して、オブジェクトに対するメソッド・コールの結果を返します。次に例を示します。
new ReflectionExtractor("getCity")
ValueExtractorsは、Coherence API全体で使用できます。ただし通常は、索引の定義に使用します。
特に役立つエクストラクタのタイプはChainedExtractorです。これは、エクストラクタの配列に基づく複合的なValueExtractor実装です。配列内のエクストラクタは左から右に順次適用され、前のエクストラクタの結果が次のエクストラクタのターゲット・オブジェクトとなります。次に例を示します。
new ChainedExtractor(new ReflectionExtractor("getHomeAddress"), new ReflectionExtractor("getState"))
この例では、HomeAddressとStateが複合Contactオブジェクトに属していることを前提としています。ChainedExtractorはまず、リフレクションを使用して、キャッシュされた各Contactオブジェクトに対してgetHomeAddressをコールします。その後、リフレクションを再度使用して、返されたHomeAddressのセットに対してgetStateをコールします。
問合せを実行するQueryExampleという新規Javaクラスを作成します。このクラスにmainメソッドが存在することを確認します。詳細は、「Javaクラスの作成」を参照してください。
entrySetメソッドを使用して、次の従業員連絡先情報を取得します。
マサチューセッツに住むすべての従業員。ヒント:
cache.entrySet(new EqualsFilter("getHomeAddress.getState", "MA"));
マサチューセッツに住み、別の場所で働くすべての従業員。ヒント:
cache.entrySet(new AndFilter(
new EqualsFilter("getHomeAddress.getState", "MA"),
new NotEqualsFilter("getWorkAddress.getState", "MA")));
名前がSで始まる都市に住むすべての従業員。ヒント:
cache.entrySet(new LikeFilter("getHomeAddress.getCity", "S%"));
姓がSで始まり、マサチューセッツに住むすべての従業員。問合せにキーと値の両方を使用します。キャッシュ・エントリでは、キーとしてContactIdオブジェクトが使用されています。KeyExtractorを使用して、これらの値を取得できます。KeyExtractorは、値ではなく主要なオブジェクトに対して問合せを実行する必要があることを示す、特殊な値エクストラクタです。ヒント:
cache.entrySet(new AndFilter(
new LikeFilter(new KeyExtractor("getLastName"), "S%",
(char) 0, false),
new EqualsFilter("getHomeAddress.getState", "MA")));
指定された年齢を超えているすべての従業員。ヒント:
final int nAge = 42;
setResults = cache.entrySet(new GreaterFilter("getAge", nAge));
パフォーマンスを向上させるには、索引を使用します。ヒント: QueryMapインタフェースに関するJavadocでaddIndexメソッドを探します。
例5-6に、可能な解決策を示します。
例5-6 QueryExampleクラスのサンプル
package com.oracle.handson;
import com.tangosol.net.CacheFactory;
import com.tangosol.net.NamedCache;
import com.tangosol.util.extractor.ChainedExtractor;
import com.tangosol.util.extractor.KeyExtractor;
import com.tangosol.util.extractor.ReflectionExtractor;
import com.tangosol.util.filter.AlwaysFilter;
import com.tangosol.util.filter.AndFilter;
import com.tangosol.util.filter.EqualsFilter;
import com.tangosol.util.filter.GreaterFilter;
import com.tangosol.util.filter.LikeFilter;
import com.tangosol.util.filter.NotEqualsFilter;
import java.util.Iterator;
import java.util.Set;
/**
* QueryExample runs sample queries for contacts.
*
*/
public class QueryExample{
// ----- QueryExample methods ---------------------------------------
public static void main(String[] args) {
NamedCache cache = CacheFactory.getCache("ContactsCache");
query(cache);
}
/**
* Perform the example queries
*
*/
public static void query(NamedCache cache)
{
// Add indexes to make queries more efficient
ReflectionExtractor reflectAddrHome =
new ReflectionExtractor("getHomeAddress");
// Add an index for the age
cache.addIndex(new ReflectionExtractor("getAge"), true, null);
// Add index for state within home address
cache.addIndex(new ChainedExtractor(reflectAddrHome,
new ReflectionExtractor("getState")), true, null);
// Add index for state within work address
cache.addIndex(new ChainedExtractor(
new ReflectionExtractor("getWorkAddress"),
new ReflectionExtractor("getState")), true, null);
// Add index for city within home address
cache.addIndex(new ChainedExtractor(reflectAddrHome,
new ReflectionExtractor("getCity")), true, null);
// Find all contacts who live in Massachusetts
Set setResults = cache.entrySet(new EqualsFilter(
"getHomeAddress.getState", "MA"));
printResults("MA Residents", setResults);
// Find all contacts who live in Massachusetts and work elsewhere
setResults = cache.entrySet(new AndFilter(
new EqualsFilter("getHomeAddress.getState", "MA"),
new NotEqualsFilter("getWorkAddress.getState", "MA")));
printResults("MA Residents, Work Elsewhere", setResults);
// Find all contacts whose city name begins with 'S'
setResults = cache.entrySet(new LikeFilter("getHomeAddress.getCity",
"S%"));
printResults("City Begins with S", setResults);
final int nAge = 42;
// Find all contacts who are older than nAge
setResults = cache.entrySet(new GreaterFilter("getAge", nAge));
printResults("Age > " + nAge, setResults);
// Find all contacts with last name beginning with 'S' that live
// in Massachusetts. Uses both key and value in the query.
setResults = cache.entrySet(new AndFilter(
new LikeFilter(new KeyExtractor("getLastName"), "S%",
(char) 0, false),
new EqualsFilter("getHomeAddress.getState", "MA")));
printResults("Last Name Begins with S and State Is MA", setResults);
}
/**
* Print results of the query
*
* @param sTitle the title that describes the results
* @param setResults a set of query results
*/
private static void printResults(String sTitle, Set setResults)
{
System.out.println(sTitle);
for (Iterator iter = setResults.iterator(); iter.hasNext(); )
{
System.out.println(iter.next());
}
}
}
稼働しているキャッシュ・サーバーをすべて停止します。contacts-cache-server.cmdで、Contactsキャッシュ・サーバーを再起動します。DataGeneratorファイル、LoadExampleファイル、QueryExampleファイルを順に実行します。キャッシュ内のすべての連絡先情報が出力されると、問合せ結果が表示されます。結果は図5-3のようになります。
QueryExample.javaに、キャッシュ内のデータに対して集計を実行するコードを追加します。EntryAggregator(com.tangosol.util.InvocableMap.EntryAggregator)を使用すると、すべてのオブジェクトまたは特定のオブジェクト・セットに対して操作を行い、集計を返すことができます。EntryAggregatorsは、クラスタ内のデータに対してサービスをパラレル実行する必須エージェントです。集計はパラレル実行されるため、クラスタ・メンバーを追加したときに利点が得られます。
集計には、キーのコレクションに関する集計と、フィルタ指定による集計の2つの方法があります。例5-7に、これらの集計を実行するメソッドを示します。
例5-7 キーに関する集計とフィルタ指定による集計を行うメソッド
Object aggregate(Collection keys, InvocableMap.entryAggregator agg) Object aggregate(Filter filter, InvocableMap.entryAggregator agg)
次の例ではフィルタが使用されます。
集計を使用して、QueryExampleクラスに次の計算を行うコードを記述します。
指定された年齢を超えている従業員の数。ヒント: GreaterFilterおよびCountクラスを使用します。
cache.aggregate(new GreaterFilter("getAge", nAge), new Count())
従業員のセットの中で最も低い年齢。ヒント: AlwaysFilterおよびLongMinクラスを使用します。
cache.aggregate(AlwaysFilter.INSTANCE, new LongMin("getAge"))
従業員のセットの中で最も高い年齢。ヒント: AlwaysFilterおよびLongMaxクラスを使用します。
cache.aggregate(AlwaysFilter.INSTANCE, new LongMax("getAge"))
従業員の平均年齢。ヒント: AlwaysFilterおよびDoubleAverageクラスを使用します。
cache.aggregate(AlwaysFilter.INSTANCE, new DoubleAverage("getAge")
アグリゲータ・クラスCount、DoubleAverage、LongMaxおよびLongMinをインポートします。
import com.tangosol.util.aggregator.Count; import com.tangosol.util.aggregator.DoubleAverage; import com.tangosol.util.aggregator.LongMax; import com.tangosol.util.aggregator.LongMin;
これでQueryExample.javaファイルは例5-8のようになります。
例5-8 集計のあるQueryExample
package com.oracle.handson; import com.tangosol.net.CacheFactory; import com.tangosol.net.NamedCache; import com.tangosol.util.aggregator.Count; import com.tangosol.util.aggregator.DoubleAverage; import com.tangosol.util.aggregator.LongMax; import com.tangosol.util.aggregator.LongMin; import com.tangosol.util.extractor.ChainedExtractor; import com.tangosol.util.extractor.KeyExtractor; import com.tangosol.util.extractor.ReflectionExtractor; import com.tangosol.util.filter.AlwaysFilter; import com.tangosol.util.filter.AndFilter; import com.tangosol.util.filter.EqualsFilter; import com.tangosol.util.filter.GreaterFilter; import com.tangosol.util.filter.LikeFilter; import com.tangosol.util.filter.NotEqualsFilter; import java.util.Iterator; import java.util.Set; /** * QueryExample runs sample queries for contacts. */ public class QueryExample{ // ----- QueryExample methods --------------------------------------- public static void main(String[] args) { NamedCache cache = CacheFactory.getCache("ContactsCache"); query(cache); } /** * Perform the example queries * */ public static void query(NamedCache cache) { // Add indexes to make queries more efficient ReflectionExtractor reflectAddrHome = new ReflectionExtractor("getHomeAddress"); cache.addIndex(new ReflectionExtractor("getAge"), true, null); cache.addIndex(new ChainedExtractor(reflectAddrHome, new ReflectionExtractor("getState")), true, null); cache.addIndex(new ChainedExtractor( new ReflectionExtractor("getWorkAddress"), new ReflectionExtractor("getState")), true, null); cache.addIndex(new ChainedExtractor(reflectAddrHome, new ReflectionExtractor("getCity")), true, null); // Find all contacts who live in Massachusetts Set setResults = cache.entrySet(new EqualsFilter( "getHomeAddress.getState", "MA")); printResults("MA Residents", setResults); // Find all contacts who live in Massachusetts and work elsewhere setResults = cache.entrySet(new AndFilter( new EqualsFilter("getHomeAddress.getState", "MA"), new NotEqualsFilter("getWorkAddress.getState", "MA"))); printResults("MA Residents, Work Elsewhere", setResults); // Find all contacts whose city name begins with 'S' setResults = cache.entrySet(new LikeFilter("getHomeAddress.getCity", "S%")); printResults("City Begins with S", setResults); final int nAge = 42; // Find all contacts who are older than nAge setResults = cache.entrySet(new GreaterFilter("getAge", nAge)); printResults("Age > " + nAge, setResults); // Find all contacts with last name beginning with 'S' that live // in Massachusetts. Uses both key and value in the query. setResults = cache.entrySet(new AndFilter( new LikeFilter(new KeyExtractor("getLastName"), "S%", (char) 0, false), new EqualsFilter("getHomeAddress.getState", "MA"))); printResults("Last Name Begins with S and State Is MA", setResults); // Count contacts who are older than nAge System.out.println("count > " + nAge + ": "+ cache.aggregate( new GreaterFilter("getAge", nAge), new Count())); // Find minimum age System.out.println("min age: " + cache.aggregate(AlwaysFilter.INSTANCE, new LongMin("getAge"))); // Calculate average age System.out.println("avg age: " + cache.aggregate(AlwaysFilter.INSTANCE, new DoubleAverage("getAge"))); // Find maximum age System.out.println("max age: " + cache.aggregate(AlwaysFilter.INSTANCE, new LongMax("getAge"))); } /** * Print results of the query * */ private static void printResults(String sTitle, Set setResults) { System.out.println(sTitle); for (Iterator iter = setResults.iterator(); iter.hasNext(); ) { System.out.println(iter.next()); } } }
次の手順に従い、キャッシュの問合せおよび結果の集計を行います。
Contactsキャッシュ・サーバーを停止し、contacts-cache-server.cmdファイルで再起動します。
DataGenerator、LoaderExampleおよびQueryExampleの各アプリケーションを実行します。
JDeveloperには図5-4のような出力が表示されます。