With the strategy to divide and conquer on Eclipse JNoSQL, the communication API was born. It has the goal to make the communication layer easy and extensible. The extensibility is more than important, that is entirely necessary once the API must support specific feature in each database. Nonetheless, the advantage of a common API in a change to another database provider has lesser than using the specific API.
To cover the three of the four database types, this API includes three packages, one for each database.
-
org.eclipse.jnosql.communication.column
-
org.eclipse.jnosql.communication.document
-
org.eclipse.jnosql.communication.keyvalue
Note
|
A package for the Graph database type is not included in this API because we leverage the Graph communication API provided by Apache TinkerPop. |
So, if a database is multi-model, i.e., has support for more than one database type, it will implement an API for each database it supports. Also, each API has the TCK to prove if the database is compatible with the API. Even from different NoSQL types, it tries to use the same nomenclature:
-
Configuration: It is a function that reads from `Settings and then creates a manager factory instance.
-
Manager’s Factory: The manager factory instance creates a manager instance from the database name.
-
Manager: The manager instance bridges the Eclipse JNoSQL and the NoSQL vendor.
-
Entity: The communication entity
-
Value: the information unit
Structure | Key-value | Column | Document |
---|---|---|---|
Configuration |
KeyValueConfiguration |
ColumnConfiguration |
DocumentConfiguration |
Factory |
BucketManagerFactory |
ColumnManagerFactory |
DocumentManagerFactory |
Manager’s Factory |
BucketManager |
ColumnManager |
DocumentManager |
Entity |
KeyValueEntity |
ColumnEntity |
DocumentEntity |
The codes below show several CRUD operations using the communication layer.
Settings settings = Settings.builder().put("credential", "value").build();
DocumentConfiguration configuration = new NoSQLDocumentProvider();
DocumentManagerFactory factory = configuration.apply(settings);
try (DocumentManager manager = factory.apply("database")) {
DocumentEntity entity = DocumentEntity.of("entity");
entity.add("id", 10L);
entity.add("version", 0.001);
entity.add("name", "Diana");
manager.insert(entity);
DocumentQuery query = select().from("entity").where("version").gte(0.001).build();
Stream<DocumentEntity> entities = manager.select(query);
DocumentDeleteQuery delete = delete().from("entity").where("id").eq(10L).build();
manager.delete(delete);
}
Settings settings = Settings.builder().put("credential", "value").build();
ColumnConfiguration configuration = new NoSQLColumnProvider();
ColumnManagerFactory factory = configuration.apply(settings);
try (ColumnManager manager = factory.apply("database")) {
ColumnEntity entity = ColumnEntity.of("entity");
entity.add("id", 10L);
entity.add("version", 0.001);
entity.add("name", "Diana");
manager.insert(entity);
ColumnQuery query = select().from("entity").where("version").gte(0.001).build();
Stream<ColumnEntity> entities = manager.select(query);
ColumnDeleteQuery delete = delete().from("entity").where("id").eq(10L).build();
manager.delete(delete);
}
Settings settings = Settings.builder().put("credential", "value").build();
KeyValueConfiguration configuration = new NoSQLKeyValueProvider();
BucketManagerFactory factory = configuration.apply(settings);
try (BucketManager manager = factory.apply("database")) {
KeyValueEntity entity = KeyValueEntity.of(12, "Poliana");
manager.put(entity);
manager.delete(12);
}
The communication has four projects:
-
The communication-core: The Eclipse JNoSQL API communication common to all database types.
-
The communication-key-value: The Eclipse JNoSQL communication API layer to a key-value database.
-
The communication-column: The Eclipse JNoSQL communication API layer to a column database.
-
The communication-document: The Eclipse JNoSQL communication API layer to a document database.
Each module works separately such that a NoSQL vendor just needs to implement the specific type. For example, a key-value provider will apply a key-value API. If a NoSQL driver already has a driver, this API can work as an adapter with the current one. For a multi-model NoSQL database, providers will implement the APIs they need.
Warning
|
To the Graph communication API, there is the Apache TinkerPop that won’t be covered in this documentation. |
This interface represents the value that will store, that is, a wrapper to serve as a bridge between the database and the application. For example, if a database does not support a Java type, it may do the conversion with ease.
Value value = Value.of(12);
String string = value.get(String.class);
List<Integer> list = value.get(new TypeReference<List<Integer>>() {});
Set<Long> set = value.get(new TypeReference<Set<Long>>() {});
Stream<Integer> stream = value.get(new TypeReference<Stream<Integer>>() {});
Object integer = value.get();
As mentioned before, the Value
interface is used to store the cost information into a database. The API already has support to the Java type such as primitive types, wrappers types, new Java 8 date/time. Furthermore, the developer can create a custom converter quickly and easily. It has two interfaces:
-
ValueWriter
: This interface represents an instance ofValue
to write in a database. -
ValueReader
: This interface represents how theValue
will convert to Java application. This interface will use the<T> T get(Class<T> type)
and<T> T get(TypeSupplier<T> typeSupplier)
.
Both class implementations load from the Java SE ServiceLoader resource. So for the Communication API to learn a new type, just register on ServiceLoader. Consider the following Money
class:
import java.math.BigDecimal;
import java.util.Currency;
import java.util.Objects;
public class Money {
private final Currency currency;
private final BigDecimal value;
private Money(Currency currency, BigDecimal value) {
this.currency = currency;
this.value = value;
}
public Currency getCurrency() {
return currency;
}
public BigDecimal getValue() {
return value;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Money money = (Money) o;
return Objects.equals(currency, money.currency) &&
Objects.equals(value, money.value);
}
@Override
public int hashCode() {
return Objects.hash(currency, value);
}
@Override
public String toString() {
return currency.getCurrencyCode() + ' ' + value;
}
public static Money of(Currency currency, BigDecimal value) {
return new Money(currency, value);
}
public static Money parse(String text) {
String[] texts = text.split(" ");
return new Money(Currency.getInstance(texts[0]),
BigDecimal.valueOf(Double.valueOf(texts[1])));
}
}
Note
|
Just to be more didactic, the book creates a simple money representation. As everyone knows, reinventing the wheel is not a good practice. In a production environment, the Java developer should use mature Money APIs such as Moneta, the reference implementation of JSR 354. |
The first step is to create the converter to a custom type database, the ValueWriter
.
import org.eclipse.jnosql.communication.ValueWriter;
public class MoneyValueWriter implements ValueWriter<Money, String> {
@Override
public boolean isCompatible(Class type) {
return Money.class.equals(type);
}
@Override
public String write(Money money) {
return money.toString();
}
}
With the MoneyValueWriter
created and the Money
type will save as String, then the next step is read information to Java application. As can be seen, a ValueReader
implementation.
import org.eclipse.jnosql.communication.ValueReader;
public class MoneyValueReader implements ValueReader {
@Override
public boolean isCompatible(Class type) {
return Money.class.equals(type);
}
@Override
public <T> T read(Class<T> type, Object value) {
return (T) Money.parse(value.toString());
}
}
After both implementations have been completed, the last step is to register them into two files:
-
META-INF/services/org.eclipse.jnosql.communication.ValueReader
-
META-INF/services/org.eclipse.jnosql.communication.ValueWriter
Each file will have the qualifier of its respective implementation:
The file org.eclipse.jnosql.communication.ValueReader
will contain:
my.company.MoneyValueReader
The file org.eclipse.jnosql.communication.ValueWriter
will contain:
my.company.MoneyValueWriter
Value value = Value.of("BRL 10.0");
Money money = value.get(Money.class);
List<Money> moneys = value.get(new TypeReference<List<Money>>() {});
Set<Money> moneys = value.get(new TypeReference<Set<Money>>() {});;
The Element Entity is a small piece of a body, except for the key-value structure type, once this structure is simple. For example, in the column family structure, the entity has columns, the element entity with column has a tuple where the key is the name, and the value is the information as an implementation of Value
.
-
Document
-
Column
The Document
is a small piece of a Document entity. Each document has a tuple where the key is the document name, and the value is the information itself as Value
.
Document document = Document.of("name", "value");
Value value = document.value();
String name = document.name();
The document might have a nested document, that is, a sub-document.
Document subDocument = Document.of("subDocument", document);
The way to store information in sub-documents will also depend on the implementation of each database driver.
To access the information from Document
, it has an alias method to Value
. In other words, it does a conversion directly from Document
interface.
Document age = Document.of("age", 29);
String ageString = age.get(String.class);
List<Integer> ages = age.get(new TypeReference<List<Integer>>() {});
Object ageObject = age.get();
The Column is a small piece of the Column Family entity. Each column has a tuple where the name represents a key and the value itself as a Value
implementation.
Column document = Column.of("name", "value");
Value value = document.value();
String name = document.name();
With this interface, we may have a column inside a column.
Column subColumn = Column.of("subColumn", column);
The way to store a sub-column will also depend on each driver’s implementation as well as the information.
To access the information from Column
, it has an alias method to Value
. Thus, you can convert directly from a Column
interface.
Column age = Column.of("age", 29);
String ageString = age.get(String.class);
List<Integer> ages = age.get(new TypeReference<List<Integer>>() {});
Object ageObject = age.get();
The Entity is the body of the information that goes to the database. Each database has an Entity:
-
ColumnEntity
-
DocumentEntity
-
KeyValueEntity
The ColumnEntity
is an entity to the Column Family database type. It is composed of one or more columns. As a result, the Column
is a tuple of name and value.
ColumnEntity entity = ColumnEntity.of("entity");
entity.add("id", 10L);
entity.add("version", 0.001);
entity.add("name", "Diana");
entity.add("options", Arrays.asList(1, 2, 3));
List<Column> columns = entity.getColumns();
Optional<Column> id = entity.find("id");
The DocumentEntity
is an entity to Document collection database type. It is composed of one or more documents. As a result, the Document
is a tuple of name and value.
DocumentEntity entity = DocumentEntity.of("documentFamily");
String name = entity.name();
entity.add("id", 10L);
entity.add("version", 0.001);
entity.add("name", "Diana");
entity.add("options", Arrays.asList(1, 2, 3));
List<Document> documents = entity.documents();
Optional<Document> id = entity.find("id");
entity.remove("options");
The KeyValueEntity
is the simplest structure. It has a tuple and a key-value structure. As the previous entity, it has direct access to information using alias method to Value
.
KeyValueEntity<String> entity = KeyValueEntity.of("key", Value.of(123));
KeyValueEntity<Integer> entity2 = KeyValueEntity.of(12, "Text");
String key = entity.key();
Value value = entity.vaalue();
Integer integer = entity.get(Integer.class);
The Manager
is the class that pushes information to a database and retrieves it.
-
DocumentManager
-
ColumnConfiguration
-
BucketManager
The DocumentManager
is the class that manages the persistence on the synchronous way to document collection.
DocumentEntity entity = DocumentEntity.of("collection");
Document diana = Document.of("name", "Diana");
entity.add(diana);
List<DocumentEntity> entities = Collections.singletonList(entity);
DocumentManager manager = // instance;
// Insert operations
manager.insert(entity);
manager.insert(entity, Duration.ofHours(2L)); // inserts with two hours of TTL
manager.insert(entities, Duration.ofHours(2L)); // inserts with two hours of TTL
// Update operations
manager.update(entity);
manager.update(entities);
The Document Communication API supports retrieving information from a DocumentQuery
instance.
By default, there are two ways to create a DocumentQuery
instance that are available as a static method in the same class:
-
The select methods follow the fluent-API principle; thus, it is a safe way to create a query using a DSL code. Therefore, each action will only show the reliability option as a menu.
-
The builder methods follow the builder pattern; it is not more intelligent and safer than the previous one. However, it allows for running more complex queries and combinations.
Both methods should guarantee the validity and consistency`DocumentQuery` instance.
In the next step, there are a couple of query creation samples using both select and builder methods.
-
Select all fields from the document collection Person.
Using the select method:
DocumentQuery query = DocumentQuery.select().from("Person").build();
//static imports
DocumentQuery query = select().from("Person").build();
Using the builder method:
DocumentQuery query = DocumentQuery.builder().from("Person").build();
//static imports
DocumentQuery query = builder().from("Person").build();
-
Select all fields where the "name" equals "Ada Lovelace" from the document collection Person.
Using the select method:
DocumentQuery query = DocumentQuery.select()
.from("Person").where("name").eq("Ada Lovelace")
.build();
//static imports
DocumentQuery query = select()
.from("Person").where("name").eq("Ada Lovelace")
.build();
Using the builder method:
DocumentQuery query = DocumentQuery.builder()
.from("Person").where(DocumentCondition.eq("name", "Ada Lovelace"))
.build();
//static imports
DocumentQuery query = builder().from("Person")
.where(eq("name", "Ada Lovelace"))
.build();
-
Select the field name where the "name" equals "Ada Lovelace" from the document collection Person.
Using the select method:
DocumentQuery query = DocumentQuery.select("name")
.from("Person").where("name").eq("Ada Lovelace")
.build();
//static imports
DocumentQuery query = select("name")
.from("Person")
.where("name").eq("Ada Lovelace")
.build();
Using the builder method:
DocumentQuery query = DocumentQuery.builder("name")
.from("Person").where(DocumentCondition.eq("name", "Ada Lovelace"))
.build();
//static imports
DocumentQuery query = builder("name")
.from("Person").where(eq("name", "Ada Lovelace"))
.build();
-
Select the fields name and age where the "name" is "Ada Lovelace" and the "age" is greater than twenty from the document collection Person.
Using the select method:
DocumentQuery query = DocumentQuery.select("name", "age")
.from("Person")
.where("name").eq("Ada Lovelace")
.and("age").gt(20)
.build();
//static imports
DocumentQuery query = select("name", "age")
.from("Person")
.where("name").eq("Ada Lovelace")
.and("age").gt(20)
.build();
Using the builder method:
DocumentQuery query = DocumentQuery.builder("name", "age")
.from("Person")
.where(DocumentCondition.and(DocumentCondition.eq("name", "Ada Lovelace"),
DocumentCondition.gt("age", 20)))
.build();
//static imports
DocumentQuery query = builder("name", "age")
.from("Person")
.where(and(eq("name", "Ada Lovelace"),
gt("age", 20)))
.build();
-
Select the fields name and age where the "name" is "Ada Lovelace" or the "age" is greater than twenty from the document collection Person.
Using the select method:
DocumentQuery query = DocumentQuery.select("name", "age")
.from("Person")
.where("name").eq("Ada Lovelace")
.or("age").gt(20)
.build();
//static imports
DocumentQuery query = select("name", "age")
.from("Person")
.where("name").eq("Ada Lovelace")
.or("age").gt(20)
.build();
Using the builder method:
DocumentQuery query = DocumentQuery.builder("name", "age")
.from("Person")
.where(DocumentCondition.or(DocumentCondition.eq("name", "Ada Lovelace"),
DocumentCondition.gt("age", 20)))
.build();
//static imports
DocumentQuery query = builder("name", "age")
.from("Person")
.where(or(eq("name", "Ada Lovelace"),
gt("age", 20)))
.build();
-
Select the fields name and age where the "name" is "Ada Lovelace" or the "age" is greater than twenty; skip the first element, and the max return is two from the document collection Person.
Using the select method:
DocumentQuery query = DocumentQuery.select("name", "age")
.from("Person")
.where("name").eq("Ada Lovelace")
.or("age").gt(20)
.skip(1)
.limit(2)
.build();
//static imports
DocumentQuery query = select("name", "age")
.from("Person")
.where("name").eq("Ada Lovelace")
.or("age").gt(20)
.skip(1)
.limit(2)
.build();
Using the builder method:
DocumentQuery query = DocumentQuery.builder("name", "age")
.from("Person")
.where(DocumentCondition.or(DocumentCondition.eq("name", "Ada Lovelace"),
DocumentCondition.gt("age", 20)))
.skip(1).limit(2)
.build();
//static imports
DocumentQuery query = builder("name", "age")
.from("Person")
.where(or(eq("name", "Ada Lovelace"),
gt("age", 20)))
.skip(1).limit(2)
.build();
-
Select the fields name and age where the "name" is "Ada Lovelace" or the "age" is greater than twenty; skip the first element, and the max return is two sorts ascending by name and descending by age from the document collection Person.
Using the select method:
DocumentQuery query = DocumentQuery.select("name", "age")
.from("Person")
.where("name").eq("Ada Lovelace")
.or("age").gt(20)
.orderBy("name").asc()
.orderBy("desc").desc()
.build();
//static imports
DocumentQuery query = select("name", "age")
.from("Person")
.where("name").eq("Ada Lovelace")
.or("age").gt(20)
.orderBy("name").asc()
.orderBy("desc").desc()
.build();
Using the builder method:
DocumentQuery query = DocumentQuery.builder("name", "age")
.from("Person")
.where(DocumentCondition.or(DocumentCondition.eq("name", "Ada Lovelace"),
DocumentCondition.gt("age", 20)))
.sort(Sort.asc("name"), Sort.desc("age"))
.build();
//static imports
DocumentQuery query = builder("name", "age")
.from("Person")
.where(or(eq("name", "Ada Lovelace"),
gt("age", 20)))
.sort(asc("name"), desc("age"))
.build();
Similar to DocumentQuery
, there is a class to remove information from the document database type: A DocumentDeleteQuery
type.
It is more efficient than DocumentQuery
because there is no pagination and sort feature as this information is unnecessary to remove information from database.
It follows the same principle of the query where it has the build and select methods.
DocumentManager manager = // instance;
DocumentDeleteQuery query = DocumentQueryBuilder.delete().from("collection")
.where("age").gt(10).build();
manager.delete(query);
//using builder
DocumentDeleteQuery query = DocumentQueryBuilder.builder().from("collection")
.where(DocumentCondition.gt("age", 10).build();
The DocumentCondition
has support for both DocumentQuery
and DocumentDeleteQuery
on fluent and builder patterns.
The main difference is that you’ll combine all the options manually on the builder instead of being transparent as the fluent way does.
Thus, it is worth checking the DocumentCondition to see all the filter options.
The ColumnManager
is the class that manages the persistence on the synchronous way to a Column Family database.
ColumnEntity entity = ColumnEntity.of("entity");
Column diana = Column.of("name", "Diana");
entity.add(diana);
List<ColumnEntity> entities = Collections.singletonList(entity);
ColumnManager manager = // instance;
// Insert operations
manager.insert(entity);
manager.insert(entity, Duration.ofHours(2L)); // inserts with two hours of TTL
manager.insert(entities, Duration.ofHours(2L)); // inserts with two hours of TTL
// Update operations
manager.update(entity);
manager.update(entities);
The Column Communication API supports retrieving information from a ColumnQuery
instance.
By default, there are two ways to create a ColumnQuery
instance that are available as a static method in the same class:
-
The select methods follow the fluent-API principle; thus, it is a safe way to create a query using a DSL code. Therefore, each action will only show the reliability option as a menu.
-
The builder methods follow the builder pattern; it is not more intelligent and safer than the previous one. However, it allows for running more complex queries and combinations.
Both methods should guarantee the validity and consistency`ColumnQuery` instance.
In the next step, there are a couple of query creation samples using both select and builder methods.
-
Select all fields from the column family Person.
Using the select method:
ColumnQuery query = ColumnQuery.select().from("Person").build();
//static imports
ColumnQuery query = select().from("Person").build();
Using the builder method:
ColumnQuery query = ColumnQuery.builder().from("Person").build();
//static imports
ColumnQuery query = builder().from("Person").build();
-
Select all fields where the "name" equals "Ada Lovelace" from the column family Person.
Using the select method:
ColumnQuery query = ColumnQuery.select()
.from("Person").where("name").eq("Ada Lovelace")
.build();
//static imports
ColumnQuery query = select()
.from("Person").where("name").eq("Ada Lovelace")
.build();
Using the builder method:
ColumnQuery query = ColumnQuery.builder()
.from("Person").where(ColumnCondition.eq("name", "Ada Lovelace"))
.build();
//static imports
ColumnQuery query = builder().from("Person")
.where(eq("name", "Ada Lovelace"))
.build();
-
Select the field name where the "name" equals "Ada Lovelace" from the column family Person.
Using the select method:
ColumnQuery query = ColumnQuery.select("name")
.from("Person").where("name").eq("Ada Lovelace")
.build();
//static imports
ColumnQuery query = select("name")
.from("Person")
.where("name").eq("Ada Lovelace")
.build();
Using the builder method:
ColumnQuery query = ColumnQuery.builder("name")
.from("Person").where(ColumnCondition.eq("name", "Ada Lovelace"))
.build();
//static imports
ColumnQuery query = builder("name")
.from("Person").where(eq("name", "Ada Lovelace"))
.build();
-
Select the fields name and age where the "name" is "Ada Lovelace" and the "age" is greater than twenty from the column family Person.
Using the select method:
ColumnQuery query = ColumnQuery.select("name", "age")
.from("Person")
.where("name").eq("Ada Lovelace")
.and("age").gt(20)
.build();
//static imports
ColumnQuery query = select("name", "age")
.from("Person")
.where("name").eq("Ada Lovelace")
.and("age").gt(20)
.build();
Using the builder method:
ColumnQuery query = ColumnQuery.builder("name", "age")
.from("Person")
.where(ColumnCondition.and(ColumnCondition.eq("name", "Ada Lovelace"),
DocumentCondition.gt("age", 20)))
.build();
//static imports
ColumnQuery query = builder("name", "age")
.from("Person")
.where(and(eq("name", "Ada Lovelace"),
gt("age", 20)))
.build();
-
Select the fields name and age where the "name" is "Ada Lovelace" or the "age" is greater than twenty from the column family Person.
Using the select method:
ColumnQuery query = ColumnQuery.select("name", "age")
.from("Person")
.where("name").eq("Ada Lovelace")
.or("age").gt(20)
.build();
//static imports
ColumnQuery query = select("name", "age")
.from("Person")
.where("name").eq("Ada Lovelace")
.or("age").gt(20)
.build();
Using the builder method:
ColumnQuery query = ColumnQuery.builder("name", "age")
.from("Person")
.where(ColumnCondition.or(ColumnCondition.eq("name", "Ada Lovelace"),
ColumnCondition.gt("age", 20)))
.build();
//static imports
ColumnQuery query = builder("name", "age")
.from("Person")
.where(or(eq("name", "Ada Lovelace"),
gt("age", 20)))
.build();
-
Select the fields name and age where the "name" is "Ada Lovelace" or the "age" is greater than twenty; skip the first element, and the max return is two from the column family Person.
Using the select method:
ColumnQuery query = ColumnQuery.select("name", "age")
.from("Person")
.where("name").eq("Ada Lovelace")
.or("age").gt(20)
.skip(1)
.limit(2)
.build();
//static imports
ColumnQuery query = select("name", "age")
.from("Person")
.where("name").eq("Ada Lovelace")
.or("age").gt(20)
.skip(1)
.limit(2)
.build();
Using the builder method:
ColumnQuery query = ColumnQuery.builder("name", "age")
.from("Person")
.where(ColumnCondition.or(ColumnCondition.eq("name", "Ada Lovelace"),
ColumnCondition.gt("age", 20)))
.skip(1).limit(2)
.build();
//static imports
ColumnQuery query = builder("name", "age")
.from("Person")
.where(or(eq("name", "Ada Lovelace"),
gt("age", 20)))
.skip(1).limit(2)
.build();
-
Select the fields name and age where the "name" is "Ada Lovelace" or the "age" is greater than twenty; skip the first element, and the max return is two sorts ascending by name and descending by age from the column family Person.
Using the select method:
ColumnQuery query = ColumnQuery.select("name", "age")
.from("Person")
.where("name").eq("Ada Lovelace")
.or("age").gt(20)
.orderBy("name").asc()
.orderBy("desc").desc()
.build();
Using the builder method:
ColumnQuery query = ColumnQuery.builder("name", "age")
.from("Person")
.where(DocumentCondition.or(DocumentCondition.eq("name", "Ada Lovelace"),
DocumentCondition.gt("age", 20)))
.sort(Sort.asc("name"), Sort.desc("age"))
.build();
//static imports
ColumnQuery query = builder("name", "age")
.from("Person")
.where(or(eq("name", "Ada Lovelace"),
gt("age", 20)))
.sort(asc("name"), desc("age"))
.build();
Similar to ColumnQuery
, there is a class to remove information from the document database type: A ColumnDeleteQuery
type.
It is more efficient than ColumnQuery
because there is no pagination and sort feature as this information is unnecessary to remove information from database.
It follows the same principle of the query where it has the build and select methods.
ColumnManager manager = // instance;
ColumnDeleteQuery query = ColumnDeleteQuery.delete().from("collection")
.where("age").gt(10).build();
manager.delete(query);
//using builder
ColumnDeleteQuery query = ColumnDeleteQuery.builder().from("collection")
.where(DocumentCondition.gt("age", 10).build();
The ColumnCondition
has support for both ColumnQuery
and ColumnDeleteQuery
on fluent and builder patterns.
The main difference is that you’ll combine all the options manually on the builder instead of being transparent as the fluent way does.
Thus, it is worth checking the ColumnCondition to see all the filter options.
The BucketManager
is the class which saves the KeyValueEntity
in a synchronous way in Key-Value database.
BucketManager bucketManager = //instance;
KeyValueEntity<String> entity = KeyValueEntity.of("key", 1201);
Set<KeyValueEntity<String>> entities = Collections.singleton(entity);
bucketManager.put("key", "value");
bucketManager.put(entity);
bucketManager.put(entities);
bucketManager.put(entities, Duration.ofHours(2)); // inserts with two hours TTL
bucketManager.put(entity, Duration.ofHours(2)); // inserts with two hours TTL
With a simple structure, the bucket needs a key to both retrieve and delete information from the database.
Optional<Value> value = bucketManager.get("key");
Iterable<Value> values = bucketManager.get(Collections.singletonList("key"));
bucketManager.remove("key");
bucketManager.remove(Collections.singletonList("key"));
The factory class creates the Managers.
-
BucketManagerFactory: The factory classes have the responsibility to create the
BucketManager
. -
ColumnManagerFactory: The factory classes have the duty to create the Column manager.
-
DocumentManagerFactory: The factory classes have the duty to create the document collection manager.
The configuration classes create a Manager Factory. This class has all the configuration to build the database connection.
There are a large number of diversity configuration flavors such as P2P, master/slave, thrift communication, HTTP, etc. The implementation may be different, however, but they have a method to return a Manager Factory. It is recommended that all database driver providers have a properties file to read this startup information.
The Settings
interface represents the settings used in a configuration. It extends looks like a Map<String, Object>
; for this reason, gives a key that can set any value as configuration.
Settings settings = Settings.builder()
.put("key", "value")
.build();
Map<String, Object> map = //instance;
Settings settings = Settings.of(map);
For the Document collection configuration, DocumentConfiguration
configures and creates DocumentManagerFactory
.
Settings settings = Settings.builder()
.put("key", "value")
.build();
DocumentConfiguration configuration = //instance;
DocumentManagerFactory managerFactory = configuration.apply(settings);
For the Column Family configuration, ColumnConfiguration
creates and configures ColumnManagerFactory
.
Settings settings = Settings.builder()
.put("key", "value")
.build();
ColumnConfiguration configuration = //instance;
ColumnManagerFactory managerFactory = configuration.apply(settings);
For the key-value configuration, there is KeyValueConfiguration
to BucketManagerFactory
.
Settings settings = Settings.builder()
.put("key", "value")
.build();
KeyValueConfiguration configuration = //instance;
BucketManagerFactory managerFactory = configuration.apply(settings);
The Communication API allows queries to be text. These queries are converted to an operation that already exists in the Manager interface from the query
method. An UnsupportedOperationException
is thrown if a NoSQL database doesn’t have support for that procedure.
Queries follow these rules:
-
All instructions end with a like break
\n
-
It is case-sensitive
-
All keywords must be in lowercase
-
The goal is to look like SQL, however simpler
-
Even if a query has valid sintax a specific implementation may not support an operation. For example, a Column family database may not support queries in a different field that is not the ID field.
Key-Value databases support three operations: get
, del
and put
.
Use the get
statement to retrie data related to a key
get_statement ::= get ID (',' ID)*
//examples
get "Apollo" //to return an element where the id is 'Apollo'
get "Diana" "Artemis" //to return a list of values from the keys
Use the del
statement to delete one or more entities
del_statement ::= del ID (',' ID)*
//examples
del "Apollo"
del "Diana" "Artemis"
The queries have syntax similar to SQL queries. But keep in mind that it has a limitation: joins are not supported.
They have four operations: insert
, update
, delete
, and select
.
Use the insert
statement to store data for an entity
insert_statement ::= insert ENTITY_NAME (NAME = VALUE, (`,` NAME = VALUE) *) || JSON [ TTL ]
//examples
insert Deity (name = "Diana", age = 10)
insert Deity (name = "Diana", age = 10, powers = {"sun", "moon"})
insert Deity (name = "Diana", age = 10, powers = {"sun", "moon"}) 1 day
insert Deity {"name": "Diana", "age": 10, "powers": ["hunt", "moon"]}
insert Deity {"name": "Diana", "age": 10, "powers": ["hunt", "moon"]} 1 day
Use the update
statement to update the values of an entity
update_statement ::= update ENTITY_NAME (NAME = VALUE, (`,` NAME = VALUE) *) || JSON
//examples
update Deity (name = "Diana", age = 10)
update Deity (name = "Diana", age = 10, power = {"hunt", "moon"})
update Deity {"name": "Diana", "age": 10, "power": ["hunt", "moon"]}
Use the delete
statement to remove fields or entities
delete_statement ::= delete [ simple_selection ( ',' simple_selection ) ]
from ENTITY_NAME
[ where WHERE_CLAUSE ]
//examples
delete from Deity
delete power, age from Deity where name = "Diana"
The select
statement reads one or more fields for one or more entities. It returns a result-set of the entities matching the request, where each entity contains the fields corresponding to the query.
select_statement ::= select ( SELECT_CLAUSE | '*' )
from ENTITY_NAME
[ where WHERE_CLAUSE ]
[ skip (INTEGER) ]
[ limit (INTEGER) ]
[ order by ORDERING_CLAUSE ]
//examples
select * from Deity
select name, age, adress.age from Deity order by name desc age desc
select * from Deity where birthday between "01-09-1988" and "01-09-1988" and salary = 12
select name, age, adress.age from Deity skip 20 limit 10 order by name desc age desc
The where
keyword specifies a filter (WHERE_CLAUSE
) to the query. A filter is composed of boolean statements called conditions
that are combined using and
or or
operators.
WHERE_CLAUSE ::= CONDITION ([and | or] CONDITION)*
Conditions are boolean statements that operate on data being queried. They are composed of three elements:
-
Name: the data source, or target, to apply the operator
-
Operator, defines comparing process between the name and the value.
-
Value, that data that receives the operation.
The Operators are:
Operator | Description |
---|---|
= |
Equal to |
> |
Greater than |
< |
Less than |
>= |
Greater than or equal to |
⇐ |
Less than or equal to |
BETWEEN |
TRUE if the operand is within the range of comparisons |
NOT |
Displays a record if the condition(s) is NOT TRUE |
AND |
TRUE if all the conditions separated by AND is TRUE |
OR |
TRUE if any of the conditions separated by OR is TRUE |
LIKE |
TRUE if the operand matches a pattern |
IN |
TRUE if the operand is equal to one of a list of expressions |
The value is the last element in a condition, and it defines what’ll go to be used, with an operator, in a field target.
There are six types:
-
Number is a mathematical object used to count, measure and also label, where if it is a decimal, will become double, otherwise, long. E.g.:
age = 20
,salary = 12.12
-
String: one or more characters among either two double quotes,
"
, or single quotes,'
. E.g.:name = "Ada Lovelace"
,name = 'Ada Lovelace'
-
Convert: convert is a function where given the first value parameter as number or string, it will convert to the class type of the second one. E.g.:
birthday = convert("03-01-1988", java.time.LocalDate)
-
Parameter: the parameter is a dynamic value, which means it does not define the query; it’ll replace in the execution time. The parameter is at
@
followed by a name. E.g.:age = @age
-
Array: A sequence of elements that can be either number or string that is between braces
{ }
. E.g.:power = {"Sun", "hunt"}
-
JSON: JavaScript Object Notation is a lightweight data-interchange format. E.g.:
siblings = {"apollo": "brother", "zeus": "father"}
The order by
option allows defining the order of the returned results. It takes as argument (ORDERING_CLAUSE) a list of column names along with the ordering for the column (asc
for ascendant, which is the default, and desc
for the descendant).
ORDERING_CLAUSE ::= NAME [asc | desc] ( NAME [asc | desc])*
Both the INSERT and PUT commands support setting a time for data in an entity to expire. It defines the time to live of an object that is composed of the integer value and then the unit that might be day
, hour
, minute
, second
, millisecond
, nanosecond
. E.g.: ttl 10 second
To dynamically run a query, use the prepare
method in the manager for instance. It will return a PreparedStatement
interface. To define a parameter to key-value, document, and column query, use the "@" in front of the name.
PreparedStatement preparedStatement = documentManager
.prepare("select * from Person where name = @name");
preparedStatement.bind("name", "Ada");
Stream<DocumentEntity> adas = preparedStatement.result();
Warning
|
For more information on Apache TinkerPop and the Gremlin API, please visit this website. |