40 リポジトリAPIの使用
CoherenceリポジトリAPIを使用すると、Coherenceをデータ・ストアとして使用するアプリケーションの実装に使用するフレームワークに関係なく、アプリケーション内のデータ・アクセス・レイヤーの実装が容易になります。プレーンなJavaアプリケーションや、CDIを使用するアプリケーションの場合も同様に機能し、独自のリポジトリ実装を簡単に作成できます。
また、Micronaut Data (Micronaut Data with Coherenceを参照)およびSpring Dataリポジトリ実装の基盤でもあります。この章で説明するすべての機能は、これらのフレームワークを使用する際にも使用できます。唯一の違いは、独自のリポジトリを定義する方法です。これはフレームワーク固有であり、個別に文書化されています。
リポジトリAPIは、NamedMap
APIの上に実装され、Coherenceがキーと値のデータ・ストアとして使用される多くの一般的なユース・ケースで簡単に使用できる多くの機能を提供します。
- 機能および利点
基本的なCRUD (作成、読取り、更新、削除)機能に加えて、リポジトリAPIには一般的なデータ管理タスクを簡素化する多くの機能があります。 - リポジトリの実装
Coherenceには、抽象ベース・クラスcom.oracle.coherence.repository.AbstractRepository
が用意されています。これは、カスタム・リポジトリ実装で3つの抽象メソッドを拡張および実装する必要があるためです。 - 基本的なCRUD操作の実行
基本的な操作を使用して、リポジトリの追加、削除、更新および問合せを行うことができます - サーバー側の予測の実行
いくつかの基準を満たすエンティティのコレクションについてリポジトリに問い合せることは確かに一般的で便利な操作ですが、エンティティ内のすべての属性を必要としない場合もあります。 - インプレース更新の実行
最新のアプリケーションでデータを更新するための最も一般的なアプローチは、読取り/変更/書込みパターンです。 - ストリームAPIおよびデータ集計APIの使用
リポジトリに問い合せると、getAll
メソッドおよびFilter
を使用して、エンティティのサブセットを取得できますが、場合によっては、エンティティ自体ではなく、リポジトリ内のエンティティのサブセットに適用された一部の計算の結果が必要になることがあります。 - イベント・リスナーの作成
Coherenceでは、データ・エンティティを効率的に格納、変更、問合せおよび集計できるだけでなく、リポジトリ内のエンティティが変更されるたびにイベント通知を受信するように登録することもできます。 - 非同期リポジトリAPIの使用
同期リポジトリAbstractRepository<ID, T>
に加えて、非同期バージョンAbstractAsyncRepository<ID, T>
があります。
親トピック: データ・グリッド操作の実行
機能および利点
基本的なCRUD (作成、読取り、更新、削除)機能に加えて、リポジトリAPIには一般的なデータ管理タスクを簡素化する多くの機能があります。
- 強力な予測機能
- 柔軟なインプレース・エンティティ更新
- 高水準のデータ集計のサポート
- ストリームAPIのサポート
- 非同期APIのサポート
- イベント・リスナーのサポート
- 宣言的高速化および索引作成
- CDI (Contexts and Dependency Injection)のサポート
親トピック: リポジトリAPIの使用
リポジトリの実装
Coherenceには、抽象ベース・クラスcom.oracle.coherence.repository.AbstractRepository
が用意されています。これは、カスタム・リポジトリ実装で3つの抽象メソッドを拡張および実装する必要があるためです。
/**
* Return the identifier of the specified entity instance.
*
* @param entity the entity to get the identifier from
*
* @return the identifier of the specified entity instance
*/
protected abstract ID getId(T entity);
/**
* Return the type of entities in this repository.
*
* @return the type of entities in this repository
*/
protected abstract Class<? extends T> getEntityType();
/**
* Return the map that is used as the underlying entity store.
*
* @return the map that is used as the underlying entity store
*/
protected abstract M getMap();
String
識別子を使用してPerson
エンティティを格納するために使用できるリポジトリ実装は、次のように単純にすることができます。public class PeopleRepository
extends AbstractRepository<String, Person>
{
private NamedMap<String, Person> people;
public PeopleRepository(NamedMap<String, Person> people)
{
this.people = people;
}
protected NamedMap<String, Person> getMap()
{
return people;
}
protected String getId(Person person)
{
return person.getSsn();
}
protected Class<? extends Person> getEntityType()
{
return Person.class;
}
}
getMap
メソッドは、リポジトリのバッキング・データ・ストアとして使用する必要があるNamedMap
を返します。この場合、コンストラクタ引数を使用して指定します。ただし、CDI (Contexts and Dependency Injection)を使用して簡単に注入できます。getId
メソッドは、指定されたエンティティの識別子を返します。getEntityType
メソッドは、リポジトリに格納されているエンティティのクラスを返します。
前述の例のような簡単なリポジトリ実装では、拡張したAbstractRepository
クラスによって提供されるすべてのリポジトリAPI機能にアクセスできます。
public Collection<Person> findByName(String name)
{
Filter<Person> filter = Filters.like(Person::getFirstName, name)
.or(Filters.like(Person::getLastName, name));
return getAll(filter);
}
findByName
メソッドを直接呼び出して、姓または名が文字'A'で始まるすべての人を検索できます。次に例を示します。for (Person p : people.findByName("A%"))
{
// processing
}
親トピック: リポジトリAPIの使用
基本的なCRUD操作の実行
基本的な操作を使用して、リポジトリの追加、削除、更新および問合せを行うことができます
リポジトリに新しいエンティティを追加したり、既存のエンティティを置き換えるために、save
またはsaveAll
メソッドを使用できます。
save
メソッドは、単一のエンティティを引数として受け取り、それをバッキングNamedMap
に格納します。people.save(new Person("555-11-2222", "Aleks", 46));
saveAll
メソッドを使用すると、エンティティのコレクションまたはストリームを引数として渡すことで、エンティティのバッチを格納できます。一部のエンティティがリポジトリに格納された後、get
およびgetAll
メソッドを使用してリポジトリに問い合せることができます。// gets a single person by identifier
Person person = people.get("555-11-2222");
assert person.getName().equals("Aleks");
assert person.getAge() == 46;
// fetches all the people from the repository
Collection<Person> allPeople = people.getAll();
// fetches all the people from the repository, that are aged 18 years or above.
Collection<Person> allAdults = people.getAll(Filters.greaterOrEqual(Person::getAge, 18));
ソートされた結果を取得するには、getAllOrderedBy
メソッドをコールし、メソッド参照を使用してComparable
プロパティを指定します。次の例の結果には、リポジトリにあるすべての個人の名前が、最年少から最年長まで年齢でソートして表示されます。
Collection<Person> peopleOrderedByAge = people.getAllOrderedBy(Person::getAge)
Comparator
を指定できます。たとえば、以前に使用したfindByName
メソッドの結果をソートする場合(リポジトリの実装を参照)、最初に姓、次に名でソートすると、次のように再実装できます。public Collection<Person> findByName(String name)
{
Filter<Person> filter = Filters.like(Person::getFirstName, name)
.or(Filters.like(Person::getLastName, name));
return getAllOrderedBy(filter,
Remote.comparator(Person::getLastName)
.thenComparing(Person::getFirstName));
この例では、標準のJava Comparator
のかわりにCoherence Remote.comparator
を使用して、指定したコンパレータがシリアライズ可能であり、リモート・クラスタ・メンバーに送信できるようにします。
remove
メソッドのいずれかを使用できます。// removes specified entity from the repository
boolean fRemoved = people.remove(person);
// removes entity with the specified identifier from the repository
boolean fRemoved = people.removeById("111-22-3333");
前述のどちらの例でも、結果は、エンティティが実際にバッキングNamedMap
から削除されたかどうかを示すブール値になり、エンティティがリポジトリに存在しなかった場合はfalse
になることがあります。
// removes specified entity from the repository and returns it as the result
Person removed = people.remove(person, true);
// removes entity with the specified identifier from the repository and returns it as the result
Person removed = people.removeById("111-22-3333", true);
ノート:
この削除によって、追加のネットワーク・トラフィックが発生します。したがって、削除したエンティティが本当に必要でないかぎり、そのエンティティを要求しないことをお薦めします。removeAll
メソッドのいずれかを使用する必要があります。これらのメソッドでは、識別子を明示的に指定するか、Filter
を使用して削除する基準を指定することで、エンティティのセットを削除できます。
// removes all men from the repository and returns 'true' if any entity has been removed
boolean fChanged = people.removeAll(Filters.equal(Person::getGender, Gender.MALE));
// removes entities with the specified identifiers from the repository and returns
// 'true' if any entity has been removed
boolean fChanged = people.removeAllById(Set.of("111-22-3333", "222-33-4444"));
// removes the names of all men from the repository and returns the map of removed
// entities, keyed by the identifier
Map<String, Person> mapRemoved =
people.removeAll(Filters.equal(Person::getGender, Gender.MALE), true);
// removes the entities with the specified identifiers from the repository and returns
// the map of removed entities, keyed by the identifier
Map<String, Person> mapRemoved =
people.removeAllById(Set.of("111-22-3333", "222-33-4444"), true);
親トピック: リポジトリAPIの使用
サーバー側の予測の実行
Person
インスタンスに含まれるすべての情報を問い合せて破棄することは不要であり、時間の無駄です。
SELECT * FROM PEOPLE
SELECT name FROM PEOPLE
// returns the name of the person with a specified identifier
String name = people.get("111-22-3333", Person::getName);
// returns the map of names of all the people younger than 18, keyed by the person’s identifier
Map<String, String> mapNames =
people.getAll(Filters.less(Person::getAge, 18), Person::getName);
// returns a fragment containing the name and age of the person with a specified identifier
Fragment<Person> fragment = people.get("111-22-3333",
Extractors.fragment(Person::getName, Person::getAge));
// retrieves the person’s name from a fragment
String name = fragment.get(Person::getName);
// retrieves the person’s age from a fragment
int age = fragment.get(Person::getAge);
getAll
メソッドのいずれかを使用して、複数のエンティティ間で同じ予測を実行できます。//returns a map of fragments containing the name and age of all the people younger than 18, keyed by the person’s identifier
Map<String, Fragment<Person>> fragments = people.getAll(
Filters.less(Person::getAge, 18),
Extractors.fragment(Person::getName, Person::getAge));
Person
クラスには、ネストされたAddress
オブジェクトが属性として存在し、その属性にはstreet
、city
およびcountry
属性があります。リポジトリ内の個人の名前および国を取得する場合は、次の例を参照してください。// returns a fragment containing the name and the 'Address' fragment of the person with a specified identifier
Fragment<Person> person = people.get(
"111-22-3333",
Extractors.fragment(Person::getName,
Extractors.fragment(Person::getAddress, Address::getCountry)));
// retrieves the person’s name from the 'Person' fragment
String name = person.get(Person::getName);
// retrievea the 'Address' fragment from the 'Person' fragment
Fragment<Address> address = person.getFragment(Person::getAddress);
// retrieves the person’s country from the 'Address' fragment
String country = address.get(Address::getCountry);
親トピック: リポジトリAPIの使用
インプレース更新の実行
Person
の属性を更新する一般的なコードは次のようになります。
Person person = people.get("111-22-3333");
person.setAge(55);
people.save(person);
これは、基礎となるデータ・ストアが、より適切で効率的なデータ更新方法を提供するかどうかに関係なく当てはまります。たとえば、RDBMSは、その目的のためにストアド・プロシージャを提供しますが、これは使いづらく、JPA、Spring Data、Micronaut Dataなどの一般的なアプリケーション・フレームワークに適合しないため、使用する開発者はほとんどいません。また、コード・ベースをある程度断片化し、ビジネス・ロジックをアプリケーションおよびデータ・ストアに分割し、いくつかのアプリケーション・コードをSQLで記述する必要があります。
- アプリケーションがデータ・ストアに対して行うネットワーク呼出しの数が2倍になり、操作の全体的な待機時間が増加します。
- 必要以上に多くの(大量になる場合がある)データをネットワーク上で移動します。
- 単一属性の非常に単純な更新操作を実行するために、複雑なエンティティのコストがかかる構築が必要になる場合があります(これは特にJPAおよびRDBMSバックエンドの場合に当てはまります)。
- データ・ストアに不要な負荷が加えられます。これは通常、アプリケーションのスケーリングが最も難しいコンポーネントです。
- 同時実行性の問題(つまり、データ・ストア内のエンティティが初期読取りと後続の書込みの間に変更された場合の処理)が発生し、通常は読取りと書込みの両方が同じトランザクション内で発生することが必要になります。
更新を実行するためのはるかに優れた効率的な方法は、更新関数をデータ・ストアに送信し、データ・ストア自体の中でローカルに実行することです(ストアド・プロシージャはほとんどそのためにあります)。
people.update("111-22-3333", Person::setAge, 55);
前述のコードは、引数として数値55を使用したsetAge
メソッドをコールして、指定された識別子でPerson
インスタンスを更新するようにCoherenceに指示しています。このコードは非常に効率的であるだけでなく、より短く、読み書きも簡単になっています。
特定の識別子を持つPerson
インスタンスがクラスタ内のどこにあるかを知ることは、重要ではありません。Coherenceは、指定されたIDを持つエンティティに対してプライマリ所有者でsetAge
メソッドを呼び出し、フォルト・トレランスのために変更されたエンティティのバックアップを自動的に作成することを保証します。
また、前述のアプローチでは、RDBMSでストアド・プロシージャが実行するのと同じメリットが得られますが、デメリットが無い、つまりJavaですべてのコードを記述し、同じ場所に保持するという点を指摘しておきます。このアプローチにより、データに対してリッチ・ドメイン・モデルを実装し、エンティティに対するビジネス・ロジックをリモートで実行できます。これは、DDDアプリケーションと非常にうまく連携しています。
void
が返されますが、更新後のエンティティ値が何かを知りたいことがよくあります。その問題の解決は簡単です。Coherenceは指定されたメソッド呼出しの結果を返すため、setAge
メソッドを変更して適切なAPIを実装するだけで済みます。public Person setAge(int age)
{
this.age = age;
return this;
}
update
呼出しの結果として、変更されたPerson
インスタンスが取得されます。
Person person = people.update("111-22-3333", Person::setAge, 55);
assert person.getAge() == 55;
update
オーバーロードを使用できます。Person person = people.update("111-22-3333", p ->
{
p.setAge(55);
p.setGender(Gender.MALE);
return p;
});
assert person.getAge() == 55;
assert person.getGender() == Gender.MALE;
このようにして、実行される更新ロジックおよび戻り値を完全に制御できます。
Cart
が存在するかどうかを確認し、カートが存在しない場合は新しいものを作成するコードを実装できますが、Cart::addItem
コールの一部としてCart
インスタンスを作成するだけで回避できるネットワーク呼出しが結果として発生します。リポジトリAPIでは、オプションのEntityFactory
引数を使用してこれを実行できます。
carts.update(customerId, // the cart/customer identifier
Cart::addItem, // the method to invoke on a target 'Cart' instance
item, // the 'CartItem' to add to the cart
Cart::new // the 'EntityFactory' to use to create a new 'Cart' instance if the
// cart with the specified identifier does not exist
);
EntityFactory
インタフェースは単純です。@FunctionalInterface
public interface EntityFactory<ID, T>
extends Serializable
{
/**
* Create an entity instance with the specified identity.
*
* @param id identifier to create entity instance with
*
* @return a created entity instance
*/
T create(ID id);
}
create
メソッドがあります。前述の例では、Cart
クラスに次のようなコンストラクタがあることを示しています。public Cart(Long cartId)
{
this.cartId = cartId;
}
update
メソッドに加えて、1回の呼出しで複数のエンティティを変更するために使用できるupdateAll
メソッドも多数あります。これが役立つ例としては、株式分割を実行する場合のように、まったく同じ関数を複数のエンティティに適用する場合です。positions.updateAll(
Filters.equal(Position::getSymbol, "AAPL"),
Position::split, 5);
- 最初の引数は、更新するポジションのセットを決定するために使用される
Filter
です。 - 2番目の引数は、各ポジションに適用する関数です。この場合、
split(5)
は、AAPL
記号を持つ各Position
エンティティでコールされます。
単一エンティティの更新と同様に、各関数呼出しの結果がクライアントに返され、今回は、処理されたエンティティの識別子をキーとして含むMap
の形式で、そのエンティティに適用された関数の結果が値として返されます。
親トピック: リポジトリAPIの使用
ストリームAPIおよびデータ集計APIの使用
getAll
メソッドおよびFilter
を使用して、エンティティのサブセットを取得できますが、場合によっては、エンティティ自体ではなく、リポジトリ内のエンティティのサブセットに適用された一部の計算の結果が必要になることがあります。たとえば、部門内のすべての従業員の平均給与や、ポートフォリオ内のすべての株式ポジションの合計値を計算することが必要な場合があります。
処理する必要のあるエンティティについてリポジトリに問合せを実行し、クライアント上で処理自体を実行することは確かにできますが、これは、ネットワークを介して大量のデータを移動し、クライアント側の処理後に破棄するだけになる可能性があるため、タスクを達成するには非常に非効率な方法です。
Coherenceには、様々なタイプの分散処理を効率的に実行できる多数の機能があります。インプレース更新でCoherenceエントリ・プロセッサAPIを利用してデータを格納するクラスタ・メンバーでデータ変更を実行するのと同様に、リポジトリAPIのデータ集計のサポートでは、Coherenceリモート・ストリームAPIおよび集計APIを利用して、読取り専用分散計算を効率的に実行します。この機能を使用すると、処理をデータに移動したり(その逆ではなく)、クライアントに少数の(または多くの場合は1つのみの)コアではなく、クラスタに存在する数のCPUコアでパラレルに計算を実行できます。
double avgSalary = employees.stream()
.collect(RemoteCollectors.averagingDouble(Employee::getSalary));
double avgSalary = employees.stream()
.filter(e -> e.getDepartmentId == departmentId)
.collect(RemoteCollectors.averagingDouble(Employee::getSalary));
ただし、前述のコードは機能しますが、指定した部門に属するかどうかを判断するために、リポジトリ内のすべての従業員の名前を処理し、デシリアライズする可能性もあるため、理想的ではありません。
stream
メソッドのオーバーロードを使用することです。これにより、Filter
を指定して次に基づいてストリームを作成できます。double avgSalary = employees.stream(Filters.equal(Employee::getDepartmentId, departmentId))
.collect(RemoteCollectors.averagingDouble(Employee::getSalary));
その違いは微妙ですが、重要です。前の例とは異なり、この例では、Coherenceはストリームを作成する前に問合せを実行し、プロセス内の索引を利用できます。これにより、大量のデータ・セットを処理する際のオーバーヘッドを大幅に削減できます。
double avgSalary = employees.average(Employee::getSalary);
double avgSalary = employees.average(
Filters.equal(Employee::getDepartmentId, departmentId),
Employee::getSalary);
リポジトリ集計メソッドを直接使用する場合の例を次に示します。これにより、エンティティ属性のmin
、max
、average
およびsum
の検索などの一般的なタスクが単純になります。
groupBy
やtop
など、より高度な集計もあります。Map<Gender, Set<Person>> peopleByGender = people.groupBy(Person::getGender);
Map<Long, Double> avgSalaryByDept =
employees.groupBy(Employee::getDepartmentId, averagingDouble(Employee::getSalary));
List<Double> top5salaries = employees.top(Employee::getSalary, 5);
単純なものは、count
およびdistinct
です。
min
、max
またはtop
値だけでなく、これらの値が属するエンティティも必要になります。このような状況では、minBy
、maxBy
およびtopBy
メソッドを使用して、属性の最小値、最大値および上位値を含むエンティティをそれぞれ返します。Optional<Person> oldestPerson = people.maxBy(Person::getAge);
Optional<Person> youngestPerson = people.minBy(Person::getAge);
List<Employee> highestPaidEmployees = employees.topBy(Employee::getSalary, 5);
親トピック: リポジトリAPIの使用
宣言的高速化および索引作成の使用
Coherenceは、索引を使用して問合せおよび集計を最適化します。索引を使用すると、クラスタ全体に格納されているエンティティのデシリアライズを回避できます。これは、複雑なエンティティ・クラスを持つ大規模なデータ・セットがある場合、コストのかかる操作です。索引自体もソートできます。これは、less
、greater
、between
などの範囲ベースの問合せを実行する場合に役立ちます。
索引を作成する標準的な方法は、NamedMap.addIndex
メソッドをコールすることです。ただし、リポジトリAPIでは、よりシンプルで宣言的な索引作成方法が導入されています。
@Indexed
注釈を付けます。public class Person
{
//defines an unordered index on 'Person::getName', which is suitable for filters such as 'equal', 'like', and 'regex'
@Indexed
public String getName()
{
return name;
}
//defines an ordered index on 'Person::getAge', which is better suited for filters such as 'less', 'greater', and 'between'
@Indexed(ordered = true)
public int getAge()
{
return age;
}
}
リポジトリが作成されると、@Indexed
注釈のエンティティ・クラスがイントロスペクトされ、この注釈を持つ各属性の索引が自動的に作成されます。作成された索引は、その属性が問合せ式内で参照されるたびに使用されます。
場合によっては、デシリアライズされたエンティティ・インスタンスを破棄するのではなく、保持したい場合があります。インスタンスを保持すると、問合せや集計を頻繁に行ったり、ストリームAPIを使用したり、インプレース更新や予測を行う場合に役立ちます。これは、すべての属性で個々の索引をメンテナンスするコストが、デシリアライズされたエンティティ・インスタンスを保持するコストよりも大きくなる可能性があるためです。
DeserializationAccelerator
を提供します。ただし、リポジトリAPIを使用している場合は、より簡単に構成できます。@Accelerated
注釈を使用して、エンティティ・クラスまたはリポジトリ・クラス自体に注釈を付けます。@Accelerated
public class Person
{
}
すべてのエンティティのシリアライズ済コピーとデシリアライズ済コピーの両方を格納するために、クラスタに追加の記憶域容量が必要になりますが、状況によっては、パフォーマンス上の利点がコストを大幅に上回る場合があります。言い換えれば、高速化は時間と空間のトレードオフの典型的な例であり、それをいつ使用するのが適切であるかを決めるのは完全にあなた次第です。
親トピック: ストリームAPIおよびデータ集計APIの使用
イベント・リスナーの作成
Coherenceでは、データ・エンティティを効率的に格納、変更、問合せおよび集計できるだけでなく、リポジトリ内のエンティティが変更されるたびにイベント通知を受信するように登録することもできます。
public static class PeopleListener
implements PeopleRepository.Listener<Person>
{
public void onInserted(Person personNew)
{
// handle INSERT event
}
public void onUpdated(Person personOld, Person personNew)
{
// handle UPDATE event
}
public void onRemoved(Person personOld)
{
// handle REMOVE event
}
}
people.addListener(new PeopleListener());
people.addListener("111-22-3333", new PeopleListener());
people.addListener(Filters.greater(Person::getAge, 17), new PeopleListener());
people.addListener
は、リポジトリ内のエンティティが挿入、更新または削除されるたびに通知されるリスナーを登録します。- 2行目は、指定した識別子を持つエンティティが挿入、更新または削除されたときに通知されるリスナーを登録します。
- 3行目は、17より古い
Person
が挿入、更新または削除されたときに通知されるリスナーを登録します。
前述の例に示すように、関心のあるイベントのみを登録することで、受信するイベントの数とネットワーク経由で送信されるデータの量を減らす方法がいくつかあります。
ノート:
前述の例で使用されているすべてのリスナー・メソッドには、デフォルトのno-op実装があります。したがって、実際に処理したいものだけを実装すれば済みます。people.addListener(
people.listener()
.onInsert(personNew -> { /* handle INSERT event */ })
.onUpdate((personOld, personNew) -> { /* handle UPDATE event with old value */ })
.onUpdate(personNew -> { /* handle UPDATE event without old value */ })
.onRemove(personOld -> { /* handle REMOVE event */ })
.build()
);
ノート:
リスナー・ビルダーAPIを使用する場合、onUpdate
イベント・ハンドラ引数リストから古いエンティティ値を省略するオプションがあります。同じイベント・タイプに複数のハンドラを指定することもできます。その場合は、指定した順序で構成され、呼び出されます。
people.addListener(
people.listener()
.onEvent(person -> { /* handle all events */ })
.build()
);
リスナー・クラスを明示的に実装する場合と同様に、エンティティ識別子またはフィルタを最初の引数としてaddListener
メソッドに渡して、受信するイベントのスコープを制限できます。
親トピック: リポジトリAPIの使用
非同期リポジトリAPIの使用
AbstractRepository<ID, T>
に加えて、非同期バージョンAbstractAsyncRepository<ID, T>
があります。リポジトリの実装で説明したものと同じ抽象メソッドを実装する必要があります。
2つのAPIの主な違いは、非同期APIが戻り型のjava.util.CompletableFuture
を返すことです。たとえば、ブロッキング・バージョンのCollection<T> getAll()
は、リポジトリAPIの非同期バージョンではCompletableFuture<Collection<T>>
になります。
非同期APIは、返される前に結果をコレクションにバッファリングするのではなく、操作の結果が使用可能になると渡されるコールバックも提供します。この機能を使用すると、すべての結果をメモリーに蓄積するコストを支払うことなく、非常に大きな結果セットをストリーミングおよび処理できますが、これはブロッキングAPIでは不可能です。
例40-1 AbstractAsyncRepositoryの例
public class AsyncPeopleRepository
extends AbstractAsyncRepository<String, Person>
{
private AsyncNamedMap<String, Person> people;
public AsyncPeopleRepository(AsyncNamedMap<String, Person> people)
{
this.people = people;
}
protected AsyncNamedMap<String, Person> getMap()
{
return people;
}
protected String getId(Person entity)
{
return entity.getSsn();
}
protected Class<? extends Person> getEntityType()
{
return Person.class;
}
}
getMap
メソッドは、リポジトリのバッキング・データ・ストアとして使用する必要があるAsyncNamedMap
を返します。この場合、コンストラクタ引数によって指定します。ただし、CDI (Contexts and Dependency Injection)を使用して簡単に注入することもできます。getId
メソッドは、指定されたエンティティの識別子を返します。getEntityType
メソッドは、リポジトリに格納されているエンティティのクラスを返します。
AsyncPersonRepository
を使用してエンティティの単純な問合せを作成します。String upercaseName = asyncPeople.get("111-22-3333")
.thenApply(Person::getName)
.thenApply(String::toUpperCase)
.get()
- 最初の行は、IDに基づいて
CompletableFuture<Person
を取得します。 - futureが完了すると、2行目は
Person
インスタンスから個人の名前を取得します。 - 3行目は、名前を大文字に変換します。
- 3行目は、大文字の名前をブロックして返します。
この使用パターンは、CompletableFuture
を返すすべてのメソッドで類似しています。
親トピック: リポジトリAPIの使用
非同期コールバックの使用
結果に対して実現されるコレクション全体を処理するかわりに、結果が使用可能になると呼び出されるコールバックを定義できます。これらのAPIは、すべての結果が処理されたことを知らせるCompletableFuture<Void>
を返します。
asyncPeople.getAll(person -> System.out.println(person.getName()))
.thenApply(done -> System.out.println("DONE!"))
- コードの最初の行には、リポジトリ内の各
Person
の名前が出力されます。 - 2行目では、すべての人の名前が処理されると
DONE!
が出力されます。
ValueExtractor
を指定して、name属性を抽出することもできます。この場合、ネットワーク上で移動するデータを減らすために、前述の例のコードを次のように書き換えることができます。asyncPeople.getAll(Person::getName, (id, name) -> System.out.println(name))
.thenApply(done -> System.out.println("DONE!"))
- コードの最初の行には、リポジトリ内の各
Person
の名前が出力されます。 - 2行目では、すべての人の名前が処理されると
DONE!
が出力されます。
前述の例では、コールバックは、エンティティ識別子および抽出された値を引数として受け取るBiConsumer
として実装されています。フラグメント・エクストラクタを前述のgetAll
メソッドの最初の引数として使用することもできます。この場合、コールバックの2番目の引数はname属性のみでなくFragment<Person>
になります。
親トピック: 非同期リポジトリAPIの使用