Mapping

The features and conventions for mapping Java objects to documents and how to override those conventions with annotation based mapping metadata

Conventions

  • The Java class name is mapped to the collection name
  • The non-static fields of a Java object are used as fields in the stored document
  • The Java field name is mapped to the stored document field name
  • All nested Java object are stored as nested objects in the stored document
  • The Java class needs a constructor which meets the following criteria:
    • in case of a single constructor:
      • a non-parameterized constructor or
      • a parameterized constructor
    • in case of multiple constructors:
      • a non-parameterized constructor or
      • a parameterized constructor annotated with @PersistenceConstructor

Type conventions

ArangoDB uses VelocyPack  as it’s internal storage format which supports a large number of data types. In addition Spring Data ArangoDB offers - with the underlying Java driver - built-in converters to add additional types to the mapping.

Java typeVelocyPack type
java.lang.Stringstring
java.lang.Booleanbool
java.lang.Integersigned int 4 bytes, smallint
java.lang.Longsigned int 8 bytes, smallint
java.lang.Shortsigned int 2 bytes, smallint
java.lang.Doubledouble
java.lang.Floatdouble
java.math.BigIntegerstring
java.math.BigDecimalstring
java.lang.Numberdouble
java.lang.Characterstring
java.util.UUIDstring
java.lang.byte[]string (Base64)
java.util.Datestring (date-format ISO 8601)
java.sql.Datestring (date-format ISO 8601)
java.sql.Timestampstring (date-format ISO 8601)
java.time.Instantstring (date-format ISO 8601)
java.time.LocalDatestring (date-format ISO 8601)
java.time.LocalDateTimestring (date-format ISO 8601)
java.time.OffsetDateTimestring (date-format ISO 8601)
java.time.ZonedDateTimestring (date-format ISO 8601)

Type mapping

As collections in ArangoDB can contain documents of various types, a mechanism to retrieve the correct Java class is required. The type information of properties declared in a class may not be enough to restore the original class (due to inheritance). If the declared complex type and the actual type do not match, information about the actual type is stored together with the document. This is necessary to restore the correct type when reading from the DB. Consider the following example:

public class Person {
    private String name;
    private Address homeAddress;
    // ...

    // getters and setters omitted
}

public class Employee extends Person {
    private Address workAddress;
    // ...

    // getters and setters omitted
}

public class Address {
    private final String street;
    private final String number;
    // ...

    public Address(String street, String number) {
        this.street = street;
        this.number = number;
    }

    // getters omitted
}

@Document
public class Company {
    @Key
    private String key;
    private Person manager;

    // getters and setters omitted
}

Employee manager = new Employee();
manager.setName("Jane Roberts");
manager.setHomeAddress(new Address("Park Avenue", "432/64"));
manager.setWorkAddress(new Address("Main Street",  "223"));
Company comp = new Company();
comp.setManager(manager);

The serialized document for the DB looks like this:

{
  "manager": {
    "name": "Jane Roberts",
    "homeAddress": {
      "street": "Park Avenue",
      "number": "432/64"
    },
    "workAddress": {
      "street": "Main Street",
      "number": "223"
    },
    "_class": "com.arangodb.Employee"
  },
  "_class": "com.arangodb.Company"
}

Type hints are written for top-level documents (as a collection can contain different document types) as well as for every value if it’s a complex type and a sub-type of the property type declared. Maps and Collections are excluded from type mapping. Without the additional information about the concrete classes used, the document couldn’t be restored in Java. The type information of the manager property is not enough to determine the Employee type. The homeAddress and workAddress properties have the same actual and defined type, thus no type hint is needed.

Customizing type mapping

By default, the fully qualified class name is stored in the documents as a type hint. A custom type hint can be set with the @TypeAlias("my-alias") annotation on an entity. Make sure that it is an unique identifier across all entities. If we would add a TypeAlias("employee") annotation to the Employee class above, it would be persisted as "_class": "employee".

The default type key is _class and can be changed by overriding the typeKey() method of the ArangoConfiguration class.

If you need to further customize the type mapping process, the arangoTypeMapper() method of the configuration class can be overridden. The included DefaultArangoTypeMapper can be customized by providing a list of TypeInformationMapper s that create aliases from types and vice versa.

In order to fully customize the type mapping process you can provide a custom type mapper implementation by extending the DefaultArangoTypeMapper class.

Deactivating type mapping

To deactivate the type mapping process, you can return null from the typeKey() method of the ArangoConfiguration class. No type hints are stored in the documents with this setting. If you make sure that each defined type corresponds to the actual type, you can disable the type mapping, otherwise it can lead to exceptions when reading the entities from the DB.

Annotations

Annotation overview

annotationleveldescription
@Documentclassmarks this class as a candidate for mapping
@Edgeclassmarks this class as a candidate for mapping
@Idfieldstores the field as the system field _key
@Revfieldstores the field as the system field _rev
@Field(“alt-name”)fieldstores the field with an alternative name
@Reffieldstores the _id of the referenced document and not the nested document
@Fromfieldstores the _id of the referenced document as the system field _from
@Tofieldstores the _id of the referenced document as the system field _to
@Relationsfieldvertices which are connected over edges
@Transientfield, method, annotationmarks a field to be transient for the mapping framework, thus the property will not be persisted and not further inspected by the mapping framework
@PersistenceConstructorconstructormarks a given constructor - even a package protected one - to use when instantiating the object from the database
@TypeAlias(“alias”)classset a type alias for the class when persisted to the DB
@PersistentIndexclassdescribes a persistent index
@PersistentIndexedfielddescribes how to index the field
@GeoIndexclassdescribes a geo index
@GeoIndexedfielddescribes how to index the field
@FulltextIndexclassdescribes a fulltext index
@FulltextIndexedfielddescribes how to index the field
@TtlIndexclassdescribes a TTL index
@TtlIndexedfielddescribes how to index the field
@CreatedByfieldDeclares a field as the one representing the principal that created the entity containing the field.
@CreatedDatefieldDeclares a field as the one representing the date the entity containing the field was created.
@LastModifiedByfieldDeclares a field as the one representing the principal that recently modified the entity containing the field.
@LastModifiedDatefieldDeclares a field as the one representing the date the entity containing the field was recently modified.

Invoking conversion manually

In order to invoke entity serialization and deserialization to and from VPackSlice manually, you can inject an instance of ArangoConverter and respectively call the methods write and read on it, e.g.:

// ...

@Autowired
ArangoConverter arangoConverter;

  // ...
  VPackSlice vPackSlice = converter.write(entity);

  // ...
  MyEntity entity = converter.read(MyEntity.class, vPackSlice);

This is useful for cases where you need to use the underlying Java driver directly (accessible from ArangoOperations#driver()), while keeping Spring Data ArangoDB serialization behavior.