GridGain Developers Hub
GitHub logo GridGain iso GridGain.com
GridGain Software Documentation

Working with Binary Objects

Overview

In GridGain, data is stored in binary format and is deserialized into objects every time you call cache methods. However, you can work directly with the binary objects avoiding deserialization.

A binary object is a wrapper over the binary representation of an entry stored in a cache. Each binary object has the field(name) method which returns the value of the given field and the type() method that extracts the information about the type of the object. Binary objects are useful when you want to work only with some fields of the objects and do not need to deserialize the entire object.

Moreover, you do not need to have the class definition to work with binary objects and can change the structure of objects dynamically without restarting the cluster.

The binary object format is universal for all supported platforms, i.e. Java, .Net, and C++. You can start a Java cluster, then connect to it from .Net or C++ clients and use binary objects in those platforms with no need to define classes on the client side.

Enabling Binary Mode for Caches

By default, when you request entries from a cache, they are returned in the deserialized format. To work with the binary format, obtain an instance of the cache using the withKeepBinary() method. This instance will return objects in the binary format (when possible) and will also pass binary objects to entry processors, if any are used.

// Create a regular Person object and put it into the cache.
Person person = new Person(1, "FirstPerson");
ignite.cache("personCache").put(1, person);

// Get an instance of binary-enabled cache.
IgniteCache<Integer, BinaryObject> binaryCache = ignite.cache("personCache").withKeepBinary();
BinaryObject binaryPerson = binaryCache.get(1);

Note that not all objects are converted to the binary object format. The following classes are never converted (e.g., the toBinary(Object) method will return the original object, and instances of these classes will be stored without changes):

  • All primitives (byte, int, etc) and their wrapper classes (Byte, Integer, etc)

  • Arrays of primitives (byte[], int[], …​)

  • String and array of Strings

  • UUID and array of UUIDs

  • Date and array of Dates

  • Timestamp and array of Timestamps

  • Enums and array of enums

  • Maps, collections and arrays of objects (but the objects inside them will be converted if they are binary)

ICache<int, IBinaryObject> binaryCache = cache.WithKeepBinary<int, IBinaryObject>();
IBinaryObject binaryPerson = binaryCache[1];
string name = binaryPerson.GetField<string>("Name");

IBinaryObjectBuilder builder = binaryPerson.ToBuilder();
builder.SetField("Name", name + " - Copy");

IBinaryObject binaryPerson2 = builder.Build();
binaryCache[2] = binaryPerson2;

Note that not all types can be represented as IBinaryObject. Primitive types, string, Guid, DateTime, collections and arrays of these types are always returned as is.

Creating and Modifying Binary Objects

Instances of binary objects are immutable. To update fields or create a new binary object, use a binary object builder. A binary object builder is a utility class that allows you to modify the fields of binary objects without having the class definition of the objects.

You can obtain an instance of the binary object builder for a specific type as follows:

BinaryObjectBuilder builder = ignite.binary().builder("com.gridgain.snippets.Person");

builder.setField("id", 2L);
builder.setField("name", "SecondPerson");

binaryCache.put(2, builder.build());
IIgnite ignite = Ignition.Start();

IBinaryObjectBuilder builder = ignite.GetBinary().GetBuilder("Book");

IBinaryObject book = builder
  .SetField("ISBN", "xyz")
  .SetField("Title", "War and Peace")
  .Build();

Builders created in this way will contain no fields.

You can also obtain a binary object builder from an existing binary object by calling the toBuilder() method. This will copy all field values from the binary object to the builder.

In the following example, we use an entry processor to update an object on the server node without having the object’s class deployed on that node and without full object deserialization.

// The EntryProcessor is to be executed for this key.
int key = 1;
ignite.cache("personCache").<Integer, BinaryObject>withKeepBinary().invoke(key,
    (entry, arguments) -> {
        // Create a builder from the old value.
        BinaryObjectBuilder bldr = entry.getValue().toBuilder();

        //Update the field in the builder.
        bldr.setField("name", "GridGain");

        // Set new value to the entry.
        entry.setValue(bldr.build());

        return null;
    });
// Not supported in C# for now

Binary Type and Binary Fields

Binary objects hold the information about the type of objects they represent. The type information includes the field names, field types and the affinity field name.

The type of each field is represented by a binary field object. Once obtained, a binary field object can be reused multiple times if you need to read the same field from each object in a collection. Reusing a binary field object is faster than reading the field value directly from each binary object. Below is an example of using a binary field.

Collection<BinaryObject> persons = getPersons();

BinaryField salary = null;
double total = 0;
int count = 0;

for (BinaryObject person : persons) {
    if (salary == null) {
        salary = person.type().field("salary");
    }

    total += (float)salary.value(person);
    count++;
}

double avg = total / count;
Unsupported

Configuring Binary Objects

In the vast majority of use cases, there is no need to configure binary objects. However, if you need to change the type and field IDs generation or plug in a custom serializer, you can do this via the configuration.

The type and fields of a binary object are identified by their IDs. The IDs are calculated as the hash codes of the corresponding string names and are stored in each binary object. You can define your own implementation of ID generation in the configuration.

The name-to-ID conversion is done in two steps. First, the type name (class name) or a field name is transformed by a name mapper, then an ID mapper calculates the IDs. You can specify a global name mapper, a global ID mapper, and a global binary serializer as well as per-type mappers and serializers. Wildcards are supported for per-type configuration, in which case the provided configuration will be applied to all types that match the type name template.

<bean id="ignite.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">
  <property name="binaryConfiguration">
    <bean class="org.apache.ignite.configuration.BinaryConfiguration">

      <property name="nameMapper" ref="globalNameMapper"/>
      <property name="idMapper" ref="globalIdMapper"/>

      <property name="typeConfigurations">
        <list>
          <bean class="org.apache.ignite.binary.BinaryTypeConfiguration">
            <property name="typeName" value="org.apache.ignite.examples.*"/>
            <property name="serializer" ref="exampleSerializer"/>
          </bean>
        </list>
      </property>
    </bean>
  </property>
</bean>
IgniteConfiguration igniteCfg = new IgniteConfiguration();

BinaryConfiguration binaryConf = new BinaryConfiguration();
binaryConf.setNameMapper(new MyBinaryNameMapper());
binaryConf.setIdMapper(new MyBinaryIdMapper());

BinaryTypeConfiguration binaryTypeCfg = new BinaryTypeConfiguration();
binaryTypeCfg.setTypeName("com.gridgain.snippets.*");
binaryTypeCfg.setSerializer(new ExampleSerializer());

binaryConf.setTypeConfigurations(Collections.singleton(binaryTypeCfg));

igniteCfg.setBinaryConfiguration(binaryConf);
var cfg = new IgniteConfiguration
{
    BinaryConfiguration = new BinaryConfiguration
    {
        NameMapper = new ExampleGlobalNameMapper(),
        IdMapper = new ExampleGlobalIdMapper(),
        TypeConfigurations = new[]
        {
            new BinaryTypeConfiguration
            {
                TypeName = "org.apache.ignite.examples.*",
                Serializer = new ExampleSerializer()
            }
        }
    }
};