The complete Java example is here
The driver's query
method returns an instance of
java.util.concurrent.CompletableFuture
. In order to synchronously wait for the
query result, use the CompletableFuture#get(long, TimeUnit)
method.
future.get(5, TimeUnit.SECONDS);
Waiting for queries to complete is discourage in production code. Please prefer
the asynchronous API provided by the CompletableFuture
class, such as
CompletableFuture#whenComplete
or CompletableFuture#whenCompleteAsync
.
Please note that this documentation uses an overload of the CompletableFuture#get
method without a timeout. Although convenient for testing, this method can lead
to poor performance in Java 8.
An admin connection should only be used to create top level databases. After the database is created, a separate client connection should be created.
If you are using the FaunaDB-Cloud version:
- remove the 'withEndpoint' line below
- substitute "secret" for your authentication key's secret
FaunaClient adminClient = FaunaClient.builder()
.withEndpoint("http://127.0.0.1:8443")
.withSecret("secret")
.build();
String DB_NAME = "demo";
Value dbResults = adminClient.query(
Do(
If(
Exists(Database(DB_NAME)),
Delete(Database(DB_NAME)),
Value(true)
),
CreateDatabase(Obj("name", Value(DB_NAME)))
)
).get();
System.out.println("Successfully created database: " + dbResults.at("name").to(String.class).get() + "\n" + dbResults + "\n");
After the database is created, a new key specific to that database can be used to create a client connection to that database.
Value keyResults = adminClient.query(
CreateKey(Obj("database", Database(Value(DB_NAME)), "role", Value("server")))
).get();
String key = keyResults.at("secret").to(String.class).get();
FaunaClient client = adminClient.newSessionClient(key);
System.out.println("Connected to Fauna database " + DB_NAME + " with server role\n");
String SPELLS_COLLECTION = "spells";
String INDEX_NAME = "spells_index";
Value collectionResults = client.query(
CreateCollection(
Obj("name", Value(SPELLS_COLLECTION))
)
).get();
System.out.println("Create Collection for " + DB_NAME + ":\n " + collectionResults + "\n");
Value indexResults = client.query(
CreateIndex(
Obj("name", Value(INDEX_NAME), "source", Collection(Value(SPELLS_COLLECTION)))
)
).get();
System.out.println("Create Index for " + DB_NAME + ":\n " + indexResults + "\n");
Value addFireResults = client.query(
Create(
Collection(Value(SPELLS_COLLECTION)),
Obj("data",
Obj("name", Value("Fire Beak"), "element", Value("water"), "cost", Value(15))
)
)
).get();
System.out.println("Added spell to collection " + SPELLS_COLLECTION + ":\n " + addFireResults + "\n");
Value addHippoResults = client.query(
Create(
Collection(Value(SPELLS_COLLECTION)),
Obj("data",
Obj("name", Value("Hippo's Wallow"), "element", Value("water"), "cost", Value(35))
)
)
).get();
System.out.println("Added spell to collection " + SPELLS_COLLECTION + ":\n " + addHippoResults + "\n");
Adding data to a collection returns a reference to the resource with the reference, a timestamp and the corresponding object in a json structure like:
{
"object" : {
"ref" : {
....
},
"ts" : 1528414251402950,
"data" : {
....
}
}
}
Objects fields are accessed through at
methods of Value
class. It's possible to access fields by names if the value represents an object or by index if it represents an array. For example to retrieve the resource reference of the returned Value use the following to get the ref
field:
//The results at 'ref' are a resource pointer to the collection that was just created.
Value hippoRef = addHippoResults.at("ref");
System.out.println("hippoRef = " + hippoRef);
The query
method takes an Expr
object. Expr
objects can be composed with others Expr
to create complex query objects. com.faunadb.client.query.Language
is a helper class where you can find all available expressions in the library.
Value readHippoResults = client.query(
Select(Value("data"),Get(hippoRef))
).get();
System.out.println("Hippo Spells:\n " + readHippoResults + "\n");
The query
method also accepts a timeout
parameter. The timeout
value defines the maximum time a query
will be allowed to run on the server. If the value is exceeded, the query is aborted.
import java.time.Duration;
Duration timeout = Duration.ofMillis(500);
client.query(
Select(Value("data"), Get(hippoRef)),
timeout;
);
That query returns the data in the form of a json object. It's possible to convert Value
class to its primitive correspondent using to
methods specifying a type. For example the data can be extracted from the results by using:
//convert the hippo results into primitive elements
String name = readHippoResults.at("name").to(String.class).get();
Integer cost = readHippoResults.at("cost").to(Integer.class).get();
String element = readHippoResults.at("element").to(String.class).get();
System.out.println(String.format(
"Spell Details: Name=%s, Const=%d, Element=%s", name, cost, element));
Later on we will show a better method for converting to native types with User Defined types that do this transformation automatically.
This object represents the result of an operation and it might be success or a failure. All conversion operations returns an object like this. This way it's possible to avoid check for nullability everywhere in the code.
//This would return an empty option if the field is not found or the conversion fails
Optional<String> optSpellElement = readHippoResults.at("element").to(String.class).getOptional();
if (optSpellElement.isPresent()) {
String element2 = optSpellElement.get();
System.out.println("optional spell element = " + element2);
}
else {
System.out.println("Something went wrong reading the spell");
}
Optionally it's possible transform one Result<T>
into another Result<T>
of different type using map
and flatMap
. If the result
represents an failure all calls to map
and flatMap
are ignored and it returns a new failure with the same error message. See com.faunadb.client.types.Result
for details.
The query
method takes an Expr
object. Expr
objects can be composed with others Expr
to create complex query objects. com.faunadb.client.query.Language
is a helper class where you can find all available expressions in the library.
Value queryIndexResults = client.query(
SelectAll(Path("data", "id"),
Paginate(
Match(Index(Value(INDEX_NAME)))
))
).get();
That query returns a list of resource references to all the spells in the index. The list of references can be extracted from the results by using:
Collection<String> spellsRefIds = queryIndexResults.asCollectionOf(String.class).get();
System.out.println("spellsRefIds = " + spellsRefIds);
Instead of manually creating your objects via the DSL (e.g. the Obj()), you can use annotations to automatically encode and decode the collection to user-defined types. These transform the types into the equivalent Value
types.
For example a Spell collection could be used that defines the fields and constructor:
import com.faunadb.client.types.FaunaConstructor;
import com.faunadb.client.types.FaunaField;
public class Spell {
@FaunaField private String name;
@FaunaField private String element;
@FaunaField private int cost;
@FaunaConstructor
public Spell(@FaunaField("name") String name, @FaunaField("element") String element, @FaunaField("cost") int cost) {
this.name = name;
this.element = element;
this.cost = cost;
}
public String getName() {
return name;
}
public String getElement() {
return element;
}
public int getCost() {
return cost;
}
@Override
public String toString() {
return "Spell{" +
"name='" + name + '\'' +
", element='" + element + '\'' +
", cost=" + cost +
'}';
}
}
There are three attributes that can be used to change the behavior of the Encoder
and Decoder
:
FaunaField
: Used to override a custom field name and/or provide a default value for that field. If this attribute is not specified, the member name will be used instead. Can be used on fields, properties and constructor arguments.FaunaConstructor
: Used to mark a constructor or a public static method as the method used to instantiate the specified type. This attribute can be used only once per class.FaunaIgnore
: Used to ignore a specific member. Can be used on fields, properties and constructors arguments. If used on a constructor argument, that argument must have a default value.
To persist an document of Spell
in FaunaDB:
Spell newSpell = new Spell("Water Dragon's Claw", "water", 25);
Value storeSpellResult = client.query(
Create(
Collection(Value(SPELLS_COLLECTION)),
Obj("data", Value(newSpell))
)
).get();
System.out.println("Stored spell:\n " + storeSpellResult);
Read the spell we just created and convert from a Value
type back to the Spell
type:
Value dragonRef = storeSpellResult.at("ref");
Value getDragonResult = client.query(
Select(Value("data"),
Get(dragonRef)
)
).get();
Spell spell = getDragonResult.to(Spell.class).get();
System.out.println("dragon spell: " + spell);
To persist a Java list of Spell
to FaunaDB encode the list into a Value
:
Spell spellOne = new Spell("Chill Touch", "ice", 18);
Spell spellTwo = new Spell("Dancing Lights", "fire", 45);
Spell spellThree = new Spell("Fire Bolt", "fire", 32);
List<Spell> spellList = Arrays.asList(spellOne, spellTwo, spellThree);
//Lambda Variable for each spell
String NXT_SPELL = "NXT_SPELL";
//Encode the list of spells into an expression
Expr encodedSpellsList = Value(spellList);
//This query can be approximately read as for each spell in the list of spells evaluate the lambda function.
//That lambda function creates a temporary variable with each spell in the list and passes it to the create function.
//The create function then stores that spell in the database
Value spellsListSave = client.query(
Foreach(
encodedSpellsList,
Lambda(Value(NXT_SPELL),
Create(
Collection(Value(SPELLS_COLLECTION)),
Obj("data", Var(NXT_SPELL))
)
)
)
).get();
System.out.println("Created list of spells from java list: \n" + spellsListSave);
Collection<Spell> spellCollection = spellsListSave.asCollectionOf(Spell.class).get();
System.out.println("save " + spellCollection.size() + " spells:");
spellCollection.forEach(nextSpell -> System.out.println(" " + nextSpell));
Read a list of all Spells
out of the Spells
index and decode back to a Java List of Spells
:
//Lambda Variable for each spell ref
String REF_SPELL_ID = "NXT_SPELL";
//Map is equivalent to a functional map which maps over the set of all values returned by the paginate.
//Then for each value in the list it runs the lambda function which gets and returns the value.
//Map returns the data in a structure like this -> {"data": [ {"data": ...}, {"data": ...} ]} so the data field needs to be selected out.
//SelectAll does this by selecting the nested data with the Path("data", "data")
Value findAllSpells = client.query(
SelectAll(Path("data", "data"),
Map(
Paginate(
Match(Index(Value(INDEX_NAME)))
),
Lambda(Value(REF_SPELL_ID), Get(Var(REF_SPELL_ID))))
)
).get();
Collection<Spell> allSpellsCollection = findAllSpells.asCollectionOf(Spell.class).get();
System.out.println("read " + allSpellsCollection.size() + " spells:");
allSpellsCollection.forEach(nextSpell -> System.out.println(" " + nextSpell));