Skip to content

Latest commit

 

History

History
592 lines (427 loc) · 17.2 KB

MAPPING.adoc

File metadata and controls

592 lines (427 loc) · 17.2 KB

Introduction to the Mapping API

The mapping level, to put it differently, has the same goals as either the JPA or ORM. In the NoSQL world, the OxM then converts the entity object to a communication model.

This level is in charge to perform integration among technologies such as Bean Validation. The Mapping API has annotations that make the Java developer’s life easier. As a communication project, it must be extensible and configurable to keep the diversity of NoSQL database.

The Mapping structure

The mapping API has five parts:

  • The persistence-core: The mapping common project where there are annotations commons among the NoSQL types APIs.

  • The persistence-key-value: The mapping to key-value NoSQL database.

  • The persistence-column: The mapping to column NoSQL database.

  • The persistence-document: The mapping to document NoSQL database.

  • The persistence-graph: The mapping to Graph NoSQL database.

Tip
Each module works separately as a Communication API.
Important
Similar to the communication API, there is a support for database diversity. This project has extensions for each database types on the database mapping level.

Models Annotation

As previously mentioned, the Mapping API provides annotations that make the Java developer’s life easier. These annotations can be categorized in two categories:

  • Annotation Models

  • Qualifier Annotation

Annotation Models

The annotation model converts the entity model into the entity on communication, the communication entity. Beyond the Jakarta NoSQL spec (Entity, ID, and Column annotations), Eclipse JNoSQL has those annotations:

  • Embeddable

  • Convert

  • MappedSuperclass

  • Inheritance

  • DiscriminatorColumn

  • DiscriminatorValue

  • Database

Jakarta NoSQL Mapping does not require getter and setter methods to fields. However, the Entity class must have a non-private constructor with no parameters.

@Embeddable

This annotation defines a class whose instances are stored as an intrinsic part of an owning entity and share the identity of that object. The behaviour is similar to @MappedSuperclass, but this is used on composition instead of inheritance.

@Entity
public class Book {

    @Column
    private String title;

    @Column
    private Author author;
}

@Embeddable
public class Author {

    @Column
    private String author;

    @Column
    private Integer age;
}

In this example, there is a single instance in the database with columns title, author and age.

{
   "title":"Effective Java",
   "author":"Joshua Bloch",
   "age": 2019
}
@Convert

This annotation allows value conversions when mapping the value that came from the Communication API. This is useful for cases such as to cipher a field (String to String conversion), or to convert to a custom type. The Converter annotation has a single, mandatory parameter: a class that inherits from AttributeConverter that will be used to perform the conversion. The example below shows how to create a converter to a custom Money class.

@Entity
public class Employee {

    @Column
    private String name;

    @Column
    private Job job;

    @Column("money")
    @Convert(MoneyConverter.class)
    private MonetaryAmount salary;
}

public class MoneyConverter implements AttributeConverter<MonetaryAmount, String> {

    @Override
    public String convertToDatabaseColumn(MonetaryAmount appValue) {
        return appValue.toString();
    }

    @Override
    public MonetaryAmount convertToEntityAttribute(String dbValue) {
        return MonetaryAmount.parse(dbValue);
    }
}

public class MonetaryAmount {
    private final String currency;

    private final BigDecimal value;

    public String toString() {
        // specific implementation
    }

    public static MonetaryAmount parse(String string) {
        // specific implementation
    }
}
Collections

The Mapping layer supports java.util.Collection (and subclasses as defined below) mapping to simple elements such as String and Integer (that will be sent to the communication API as-is), and mapping to Entity or Embedded entities.

The following collections are supported:

  • java.util.Deque

  • java.util.Queue

  • java.util.List

  • java.util.Iterable

  • java.util.NavigableSet

  • java.util.SortedSet

  • java.util.Collection

@Entity
public class Person {

    @Id
    private Long id;

    @Column
    private String name;

    @Column
    private List<String> phones;

    @Column
    private List<Address> addresses;
}

@Embeddable
public class Address {

    @Column
    private String street;

    @Column
    private String city;
}

The above classes are mapped to:

{
   "_id":10,
   "addresses":[
      {
         "city":"São Paulo",
         "street":"Av Nove de Julho"
      },
      {
         "city":"Salvador",
         "street":"Rua Engenheiro Jose Anasoh"
      }
   ],
   "name":"Name",
   "phones":[
      "234",
      "432"
   ]
}
@MappedSuperclass

The class with the @MapperSuperclass annotation will have all attributes considered as an extension of this subclass with an @Entity annotation. In this case, all attributes are going to be stored, even the attributes inside the super class.

Using the MappedSuperclass strategy, inheritance is only evident in the class but not the entity model.

This means, that this annotation causes fields annotated with @Column in a parent class to be persisted together with the child class' fields.

@Entity
public class Dog extends Animal {

    @Column
    private String name;
}

@MappedSuperclass
public class Animal {

    @Column
    private String race;

    @Column
    private Integer age;
}

Notice that the Animal doesn’t have an @Entity annotation, as it won’t be persisted in the database by itself.

On the example above, when saving a Dog instance, Animal class' fields are saved too: name, race, and age are saved in a single instance.

@Inheritance

The strategy to work with inheritance with NoSQL can be activated by adding the @Inheritance annotation to the superclass.

@Entity
@Inheritance
public abstract class Notification {
    @Id
    private Long id;

    @Column
    private String name;

    @Column
    private LocalDate createdOn;

    public abstract void send();
}
@DiscriminatorColumn

This annotation specifies the discriminator column for the inheritance mapping strategy. The strategy and the discriminator column are only specified in the root of an entity class hierarchy.

If the DiscriminatorColumn annotation is missing, and a discriminator column is required, the name of the discriminator column defaults is "type".

@Entity
@Inheritance
@DiscriminatorColumn("type")
public abstract class Notification {
    @Id
    private Long id;

    @Column
    private String name;

    @Column
    private LocalDate createdOn;

    public abstract void send();
}
@DiscriminatorValue

This annotation specifies the value of the discriminator column for entities of the given type.

The DiscriminatorValue annotation can only be specified on a concrete entity class.

If the DiscriminatorValue annotation is not specified a provider-specific function will be used to generate a value representing the entity type, the discriminator value default is the Class.getSimpleName().

@Entity
@DiscriminatorValue("SMS")
public class SmsNotification extends Notification {

    @Column
    private String phoneNumber;

    @Override
    public void send() {
        System.out.println("Sending message to sms: " + phoneNumber);
    }
}

@Entity
@DiscriminatorValue("Email")
public class EmailNotification extends Notification {

    @Column
    private String phoneNumber;

    @Override
    public void send() {
        System.out.println("Sending message to sms: " + phoneNumber);
    }
}

@Entity
// the discriminator value is SocialMediaNotification
public class SocialMediaNotification extends Notification {
    @Column
    private String username;

    @Override
    public void send() {
        System.out.println("Sending a post to: " + username);
    }
}
@Database

This annotation allows programmers to specialize @Inject annotations to choose which specific resource should be injected.

For example, when working with multiple DocumentTemplate, the following statement are ambiguous:

@Inject
DocumentTemplate templateA;

@Inject
DocumentTemplate templateB;

@Database has two attributes to help specify what resource should be injected:

  • DatabaseType: The database type (key-value, document, column, graph);

  • provider: The provider’s database name

Applying the annotation to the example above, the result is:

@Inject
@Database(value = DatabaseType.DOCUMENT, provider = "databaseA")
private DocumentTemplate templateA;

@Inject
@Database(value = DatabaseType.DOCUMENT, provider = "databaseB")
private DocumentTemplate templateB;

A producer method annotated with the same @Database values must exist as well.

Template classes

The Template offers convenient creation, update, delete, and query operations for databases. The Template instance is the root implementation for all types. So, each database type will support this instance.

@Inject
Template template;


Book book = Book.builder().id(id).title("Java Concurrency in Practice")
.author("Brian Goetz").year(Year.of(2006)).edition(1).build();
template.insert(book);
Optional<Book> optional = template.find(Book.class, id);
System.out.println("The result " + optional);
template.delete(Book.class, id);

Furthermore, in CRUD operations, Template has two queries in its fluent-API to either select or delete entities; thus, Template offers the capability for searching and removing beyond the ID attribute.

@Inject
Template template;

List<Book> books = template.select(Book.class).where("author").eq("Joshua Bloch").and("edition").gt(3).result();

template.select(Book.class).where("author").eq("Joshua Bloch").and("edition").gt(3).execute();

Graph template

This template has the responsibility to serve as the persistence of an entity in a Graph database using Apache Tinkerpop.

The GraphTemplate is the column template for synchronous tasks.

@Inject
GraphTemplate template;

Person person = new Person();
person.setAddress("Olympus");
person.setName("Artemis Good");
person.setPhones(Arrays.asList("55 11 94320121", "55 11 94320121"));
person.setNickname("artemis");

List<Person> people = Collections.singletonList(person);

Person personUpdated = template.insert(person);
template.insert(people);
template.insert(person, Duration.ofHours(1L));

template.update(person);
template.update(people);
Create the Relationship Between Them (EdgeEntity)
Person poliana = // instance;
Book shack = // instance;
EdgeEntity edge = graphTemplate.edge(poliana, "reads", shack);
reads.add("where", "Brazil");
Person out = edge.uutgoing();
Book in = edge.incoming();
Querying with Traversal

Traversals in Gremlin are spawned from a TraversalSource. The GraphTraversalSource is the typical "graph-oriented" DSL used throughout the documentation and will most likely be the most used DSL in a TinkerPop application.

To run a query in Graph with Gremlin, there are traversal interfaces. These interfaces are lazy; in other words, they just run after any finalizing method.

For example, In this scenario, there is a marketing campaign, and the target is:

  • An engineer

  • The salary is higher than $3,000

  • The age is between 20 and 25 years old

List<Person> developers = graph.traversalVertex()
       .has("salary", gte(3_000D))
       .has("age", between(20, 25))
       .has("occupation", "Developer")
       .<Person>stream().collect(toList());

The next step is to return the engineer’s friends.

List<Person> developers = graph.traversalVertex()
        .has("salary", gte(3_000D))
        .has("age", between(20, 25))
        .has("occupation", "Developer")
        .<Person>stream().out("knows").collect(toList());

To use a graph template, just follow the CDI style and precede the field with the @Inject annotation.

@Inject
private GraphTemplate template;

You can work with several graph database instances through CDI qualifier. To identify each database instance, make a Graph visible for CDI by putting the @Produces and the @Database annotations in the method.

@Inject
@Database(value = DatabaseType.GRAPH, provider = "databaseA")
private GraphTemplate templateA;

@Inject
@Database(value = DatabaseType.GRAPH, provider = "databaseB")
private GraphTemplate templateB;

// producers methods
@Produces
@Database(value = DatabaseType.GRAPH, provider = "databaseA")
public Graph getManagerA() {
    return graph;
}

@Produces
@Database(value = DatabaseType.GRAPH, provider = "databaseB")
public Graph getManagerB() {
    return graph;
}

JNoSQL templates

Beyond the ColumnTemplate`and `DocumentTemplate Eclipse JNoSQL has support for two specializations: JNoSQLColumnTemplate and JNoSQLDocumentTemplate; those templates have more methods, such as operations exploring communication query, findAll, and deleteAll methods.

@Inject
JNoSQLColumnTemplate template;
...
Stream<Person> people = template.findAll(Person.class);
template.deleteAll(Person.class);
long count = template.count(Person.class);
ColumnQuery query = select().from("Book").build();
Stream<Book> books = template.select(query);
@Inject
JNoSQLDocumentTemplate template;
...
Stream<Person> people = template.findAll(Person.class);
template.deleteAll(Person.class);
long count = template.count(Person.class);
DocumentQuery query = select().from("Book").build();
Stream<Book> books = template.select(query);

Querying by Text with the Mapping API

Similar to the Communication layer, the Mapping layer has query by text. Both Communication and Mapping have the query and prepare methods, however, the Mapping API will convert the fields and entities to native names from the Entity and Column annotations.

Key-Value Database Types

In the Key-Value database, a KeyValueTemplate is used in this NoSQL storage technology. Usually, all the operations are defined by the ID. Therefore, it has a smooth query.

KeyValueTemplate template = // instance;
Stream<User> users = template.query("get \"Diana\"");
template.query("remove \"Diana\"");
Column-Family Database Types

The Column-Family database has a more complex structure; however, a search from the key is still recommended. For example, both Cassandra and HBase have a secondary index, yet, neither have a guarantee about performance, and they usually recommend having a second table whose row key is the "secondary index" and is only being used to find the row key needed for the actual table. Given a Person class as an entity, we would like to operate from the field ID, which is the entity from the Entity.

ColumnTemplate template = // instance;
Stream<Person> result = template.query("select * from Person where id = 1");
Document Database Types

The Document database allows for more complex queries, so with more complex entities within a Document database, a developer can more easily and naturally find from different fields. Also, there are Document databases that support an aggregations query. However, Eclipse JNoSQL does not yet support this. From the Eclipse JNoSQL API perspective, the Document and Column-Family types are pretty similar, but with the Document database type, a Java developer might initiate a query from a field that isn’t a key, and neither returns an unsupported operation exception or adds a secondary index for this. So, given the same Person class as an entity with the Document database type, a developer can do more with queries, such as "person" between "age."

DocumentTemplate template = // instance;
Stream<Person> result = template.query("select * from Person where age > 10");
Graph Database Types

If an application needs a recommendation engine or a full detail about the relationship between two entities in your system, it requires a Graph database type. A graph database contains a vertex and an edge. The edge is an object that holds the relationship information about the edges and has direction and properties that make it perfect for maps or human relationship. For the Graph API, Eclipse JNoSQL uses the Apache Tinkerpop. Likewise, the GraphTemplate is a wrapper to convert a Java entity to a Vertex in TinkerPop.

GraphTemplate template = // instance;
Stream<City> cities = template.query("g.V().hasLabel('City')");
PreparedStatement preparedStatement = documentTemplate
        .prepare("select * from Person where name = @name");

preparedStatement.bind("name", "Ada");

Stream<Person> adas = preparedStatement.result();

// Keep using gremlin for Graph databases
PreparedStatement prepare = graphTemplate().prepare("g.V().hasLabel(param)");

prepare.bind("param", "Person");

Stream<Person> people = preparedStatement.result();