The SpecificAvroBinding
interface
represents values as instances of a generated Avro class which implements
SpecificRecord
. A single schema
binding is created using
AvroCatalog.getSpecificBinding()
.
A multiple schema binding is created using
AvroCatalog.getSpecificMultiBinding()
.
Avro-specific classes provide type safety and ease of use. Properties are accessed through getter/setter methods with parameters and return values of the correct type, as defined in the schema.
Further, when using Avro-specific classes, the client application does not need to be aware of the Avro schema at runtime. The generated class has an internal reference to its associated schema, and this schema is used by the binding, so the application does not need to explicitly supply a schema. Schemas are supplied at build time, when the source code for the specific class is generated.
The disadvantage to using specific bindings is that the set of specific classes is fixed at build time. Applications that wish to treat values generically, or dynamically based on the schema, should use a generic or JSON binding instead.
When you use a specific binding, you provide your
schema to your application using a generated class. You
can do this using the
org.apache.avro.compiler.specific.SchemaTask
tool.
An example Ant file that uses
SchemaTask
can be found in
KVHOME/examples/avro/generate-specific.xml
.
It automatically downloads all library dependencies required to
generate the Avro class, and then creates generated classes
for all schema found in the local directory. The
example assumes your schema is contained in flat text
files that use the avsc
suffix.
For example, suppose you had a schema defined in a file
named my-schema.avsc
. Then to
generate an Avro-specific class, place
generate-specific.xml
and
my-schema.avsc
in the same directory
and run:
ant -f generate-specific.xml
After Ant downloads all the necessary library
dependencies, it will create whatever generated classes
are required by the contents of my-schema.avsc
.
The generated classes are named after the
name
field in the Avro schema. So if
you had the (trivial) schema:
{ "type": "record", "name": "MyInfoString", "namespace": "avro", "fields": [ {"name": "ID", "type": "int"} ] }
the generated class name would be
MyInfoString.java
. It contains
getter and setter methods that you can use to access
the fields in the schema. For example, a generated
class for the above schema would contain the fields
MyInfoString.setID()
and
MyInfoString.getID()
.
To use a schema encapsulated in a generated Avro-specific class, you first provide the schema to the store as described in Managing Avro Schema in the Store.
After that, you access the schema using the generated class. To do this, you need handles to the store, an Avro catalog, and a specific binding:
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);
Having done that, you can use the binding to serialize
and deserialize value objects that conform to the
generated class' schema. For example, suppose you
performed a store get()
and retrieved a ValueVersion
.
Then you can access its information in the following
way:
final MyInfoString member; final int ID; member = binding.toObject(valueVersion.getValue()); System.out.println("ID: " + member.getID());
To serialize the object for placement in the store, you
use the generated class' setter method, as well as the
binding's toValue()
method:
member.setID(2045); /* key creation omitted */ store.put(key, binding.toValue(member));
If your client code needs to use multiple specific
bindings, then you use
AvroCatalog.getSpecificMultiBinding()
to support the multiple schemas. For example, suppose
your client code required the following two schemas:
{ "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"} ] }
Then put Avro.PersonInfo
in a file
(call it PersonSchema.avsc
) and
Avro.AnimalInfo
in a second file
(AnimalSchema.avsc
). Add these
schemas to your store using the command line
interface.
Next, generate your specific schema classes using:
ant -f generate-specific.xml
as discussed in
Generating Specific Avro Classes.
This results in two generated classes:
PersonInfo.java
and
AnimalInfo.java
. To use these
classes in your client code, you can use a single
binding, which you create using
AvroCatalog.getSpecificMultiBinding()
. Note that to do this,
you must use
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();
You can then create PersonInfo
and AnimalInfo
objects,
serialize them, and write them to the store, like this:
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));
Retrieval of the stored objects is performed with a
normal store get()
.
However, to deserialize the retrieved objects you
must identify the object's type. You can do this
using either the Java instanceof
operator, or by examining the schema's name, as
follows:
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(). */ }