固有バインディング

Avro固有クラスの生成
Avro固有バインディングの使用
複数のAvro固有バインディングの使用

SpecificAvroBindingインタフェースでは、生成されたAvroクラス(SpecificRecordを実装)のインスタンスとして値を表します。1つのスキーマのバインディングはAvroCatalog.getSpecificBinding()を使用して作成します。複数のスキーマのバインディングはAvroCatalog.getSpecificMultiBinding()を使用して作成します。

Avro固有のクラスは、型の安全性と使いやすさを備えています。getter/setterメソッドにパラメータを指定してプロパティにアクセスすると、スキーマに定義されている正しい型の値が返されます。

また、Avro固有のクラスを使用する場合、クライアント・アプリケーションでは実行時にAvroスキーマを意識する必要はありません。生成されたクラスには、関連付けられたスキーマに対する内部参照があり、このスキーマをバインディングが使用するので、アプリケーションで明示的にスキーマを指定する必要がありません。固有クラスのソース・コードが生成されている場合、スキーマはビルド時に指定されます。

固有バインディングを使用するデメリットは、一連の固有クラスがビルド時に固定されることです。値をスキーマに基づいて汎用的または動的に扱う必要のあるアプリケーションでは、かわりに汎用バインディングまたはJSONバインディングを使用する必要があります。

Avro固有クラスの生成

固有バインディングを使用する場合は、生成されたクラスを使用してアプリケーションにスキーマを指定します。これを行うには、org.apache.avro.compiler.specific.SchemaTaskツールを使用します。

SchemaTaskを使用するサンプルのAntファイルがKVHOME/examples/avro/generate-specific.xmlに用意されています。これはAvroクラスの生成に必要なすべてのライブラリ依存性を自動的にダウンロードしてから、ローカル・ディレクトリにあるすべてのスキーマの生成されたクラスを作成します。このサンプルでは、avsc接尾辞が使用されているフラット・テキスト・ファイルにユーザーのスキーマが含まれていることを前提としています。

たとえば、my-schema.avscという名前のファイルにスキーマが定義されているとします。この場合、Avro固有のクラスを生成するには、generate-specific.xmlmy-schema.avscを同じディレクトリに配置して、次のように実行します。

ant -f generate-specific.xml

Antでは、必要なライブラリ依存性をすべてダウンロードした後で、my-schema.avscの内容で必要とされるすべての生成されたクラスを作成します。生成されたクラスは、Avroスキーマのnameフィールドに基づいて命名されます。したがって、次の(簡単な)スキーマでは、

{
    "type": "record",
    "name": "MyInfoString",
    "namespace": "avro",
    "fields": [
        {"name": "ID", "type": "int"}
    ]
} 

生成されたクラスの名前はMyInfoString.javaになります。getterメソッドとsetterメソッドを備え、それを使用してスキーマのフィールドにアクセスできます。たとえば、前述のスキーマの生成されたクラスには、MyInfoString.setID()MyInfoString.getID()というフィールドがあります。

Avro固有バインディングの使用

生成されたAvro固有のクラスにカプセル化されたスキーマを使用するには、まずストアにスキーマを格納します(「ストアのAvroスキーマの管理」で説明)。

その後で、生成されたクラスを使用してスキーマにアクセスします。これを行うには、次のように、ストア、Avroカタログおよび固有バインディングに対するハンドルが必要です。

package avro;

import oracle.kv.KVStore;
import oracle.kv.ValueVersion;
import oracle.kv.avro.AvroCatalog;
import oracle.kv.avro.SpecificAvroBinding;

...


private final KVStore store;
private final AvroCatalog catalog;
private final SpecificAvroBinding<MyInfoString> binding;

...

/* store configuration and open omitted for brevity */

...

catalog = store.getAvroCatalog();
binding = catalog.getSpecificBinding(MyInfoString.class); 

こうしておくと、バインディングを使用して、生成されたクラスのスキーマに適合する値オブジェクトをシリアライズおよびデシリアライズできます。たとえば、store get()を実行して、ValueVersionを取得したとします。次のようにして、その情報にアクセスできます。

final MyInfoString member;
final int ID;

member = binding.toObject(valueVersion.getValue());

System.out.println("ID: " + member.getID()); 

オブジェクトをシリアライズしてストアに配置するには、生成されたクラスのsetterメソッドとバインディングのtoValue()メソッドを使用します。

member.setID(2045);
/* key creation omitted */
store.put(key, binding.toValue(member)); 

複数のAvro固有バインディングの使用

クライアント・コードで複数の固有バインディングを使用する必要がある場合は、複数のスキーマをサポートするAvroCatalog.getSpecificMultiBinding()を使用します。たとえば、クライアント・コードで次の2つのスキーマが必要だとします。

{
 "type": "record",
 "namespace": "avro",
 "name": "PersonInfo",
 "fields": [
   { "name": "first", "type": "string" },
   { "name": "last", "type": "string" },
   { "name": "age", "type": "int" }
 ]
}


{
 "type": "record",
 "namespace": "avro",
 "name": "AnimalInfo",
 "fields": [
   { "name": "species", "type": "string"},
   { "name": "name", "type": "string"},
   { "name": "age", "type": "int"}
 ]
} 

その場合は、1つのファイル(PersonSchema.avsc)にAvro.PersonInfoを、2つ目のファイル(AnimalSchema.avsc)にAvro.AnimalInfoをそれぞれ格納します。コマンドライン・インタフェースを使用して、これらのスキーマをストアに追加します。

続いて、次のようにして、固有スキーマのクラスを生成します。

ant -f generate-specific.xml

(「Avro固有クラスの生成」を参照)。これにより、2つのクラス(PersonInfo.javaAnimalInfo.java)が生成されます。これらのクラスをクライアント・コードで使用するには、AvroCatalog.getSpecificMultiBinding()で1つのバインディングを作成し、それを使用します。これを行うには、org.apache.avro.specific.SpecificRecordを使用する必要があることに注意してください。

package avro;

import java.util.Arrays;

import oracle.kv.Key;
import oracle.kv.ValueVersion;
import oracle.kv.avro.AvroCatalog;
import oracle.kv.avro.SpecificAvroBinding;

import org.apache.avro.specific.SpecificRecord;

...

private final KVStore store;
private final AvroCatalog catalog;
private final SpecificAvroBinding<SpecificRecord> binding;

/*
 * Store creation is skipped for the sake of brevity.
 */

...

catalog = store.getAvroCatalog();
binding = catalog.getSpecificMultiBinding(); 

その後で、PersonInfoオブジェクトとAnimalInfoオブジェクトを作成してシリアライズし、それらをストアに書き込みます(次の例を参照)。

final Key key1 = Key.createKey(Arrays.asList("person", "11"));
final Key key2 = Key.createKey(Arrays.asList("animal", "11"));

final PersonInfo pi = new PersonInfo();
pi.setFirst("Jack");
pi.setLast("Smith");
pi.setAge(38);
store.put(key1, binding.toValue(pi));

final AnimalInfo ai = new AnimalInfo();
ai.setSpecies("Dog");
ai.setName("Bowzer");
ai.setAge(6);
store.put(key2, binding.toValue(ai));

格納されたオブジェクトを取得するには、通常のstore get()を実行します。ただし、取得したオブジェクトをデシリアライズするには、オブジェクトの型を識別する必要があります。これを行うには、Javaのinstanceof演算子を使用するか、次のようにスキーマの名前を調べます。

final ValueVersion vv = store.get(someKey);
if (vv != null) {
    SpecificRecord sr = binding.toObject(vv.getValue());
    if (sr.getSchema().getFullName().equals("avro.PersonInfo")) {
        PersonInfo o = (PersonInfo) sr;
        /*
         * The object is now deserialized. You can access the object's
         * data using the specific class' getXXX() methods. For example,
         * o.getFirst().
         */
    } else {
        AnimalInfo o = (AnimalInfo) sr;
        /*
         * The object is now deserialized. You can access the object's
         * data using the specific class' getXXX() methods. For example,
         * o.getSpecies().
         */
    }