samples
folder, which contains an end-to-end example of using the Dgraph Java client. Follow the instructions in the README of that project. IntroThis library supports two styles of clients, the synchronous client DgraphClient
and the async client DgraphAsyncClient
. A DgraphClient
or DgraphAsyncClient
can be initialized by passing it a list of DgraphBlockingStub
clients. The anyClient()
API can randomly pick a stub, which can then be used for gRPC operations. Using the synchronous client You can find a DgraphJavaSample project, which contains an end-to-end working example of how to use the Java client. Creating a clientThe following code snippet shows how to create a synchronous client using three connections.
ManagedChannel channel1 = ManagedChannelBuilder
.forAddress("localhost", 9080)
.usePlaintext().build();
DgraphStub stub1 = DgraphGrpc.newStub(channel1);
ManagedChannel channel2 = ManagedChannelBuilder
.forAddress("localhost", 9082)
.usePlaintext().build();
DgraphStub stub2 = DgraphGrpc.newStub(channel2);
ManagedChannel channel3 = ManagedChannelBuilder
.forAddress("localhost", 9083)
.usePlaintext().build();
DgraphStub stub3 = DgraphGrpc.newStub(channel3);
DgraphClient dgraphClient = new DgraphClient(stub1, stub2, stub3);
Login using access control listsIf Access Control Lists (ACL) is enabled then you can log-in to the default namespace (0
) with the following method:
dgraphClient.login(USER_ID, USER_PASSWORD);
Multi-tenancyIf multi-tenancy is enabled, by default the login method on client logs into the namespace 0
. In order to log into a different namespace, use the loginIntoNamespace
method on the client:
dgraphClient.loginIntoNamespace(USER_ID, USER_PASSWORD, NAMESPACE);
Once logged-in, the dgraphClient
object can be used to do any further operations. Creating a secure client using TLSTo setup a client using TLS, you could use the following code snippet. The server needs to be setup using the instructions provided here. If you are doing client verification, you need to convert the client key from PKCS#1 format to PKCS#8 format. By default, g doesn’t support reading PKCS#1 format keys. To convert the format, you could use the openssl
tool. First, let’s install the openssl
tool: Now, use the following command to convert the key:
openssl pkcs8 -in client.name.key -topk8 -nocrypt -out client.name.java.key
Now, you can use the following code snippet to connect to Alpha over TLS:
SslContextBuilder builder = GrpcSslContexts.forClient();
builder.trustManager(new File("<path to ca.crt>"));
// Skip the next line if you are not performing client verification.
builder.keyManager(new File("<path to client.name.crt>"), new File("<path to client.name.java.key>"));
SslContext sslContext = builder.build();
ManagedChannel channel = NettyChannelBuilder.forAddress("localhost", 9080)
.sslContext(sslContext)
.build();
DgraphGrpc.DgraphStub stub = DgraphGrpc.newStub(channel);
DgraphClient dgraphClient = new DgraphClient(stub);
Check Dgraph versionChecking the version of the Dgraph server this client is interacting with is as easy as:
Version v = dgraphClient.checkVersion();
System.out.println(v.getTag());
Checking the version, before doing anything else can be used as a test to find out if the client is able to communicate with the Dgraph server. This also helps reduce the latency of the first query/mutation which results from some dynamic library loading and linking that happens in JVM (see this issue for more details). Altering the databaseTo set the schema, create an Operation
object, set the schema and pass it to DgraphClient#alter
method.
String schema = "name: string @index(exact) .";
Operation operation = Operation.newBuilder().setSchema(schema).build();
dgraphClient.alter(operation);
Starting Dgraph version 20.03.0, indexes can be computed in the background. You can call the function setRunInBackground(true)
as shown below before calling alter
. You can find more details here.
String schema = "name: string @index(exact) .";
Operation operation = Operation.newBuilder()
.setSchema(schema)
.setRunInBackground(true)
.build();
dgraphClient.alter(operation);
Operation
contains other fields as well, including drop predicate and drop all. Drop all is useful if you wish to discard all the data, and start from a clean slate, without bringing the instance down.
// Drop all data including schema from the dgraph instance. This is useful
// for small examples such as this, since it puts dgraph into a clean
// state.
dgraphClient.alter(Operation.newBuilder().setDropAll(true).build());
Creating a transactionThere are two types of transactions in dgraph, queries (reads) and mutations (writes). Both the synchronous DgraphClient
and the asynchronous DgraphAsyncClient
clients support the two types of transactions by providing the newTransaction
and the newReadOnlyTransaction
APIs. Creating a transaction is a local operation and incurs no network overhead. In most of the cases, the normal read-write transactions is used, which can have any number of query or mutate operations. However, if a transaction only has queries, you might benefit from a read-only transaction, which can share the same read timestamp across multiple such read-only transactions and can result in lower latencies. For normal read-write transactions, it’s a good practice to call Transaction#discard()
in a finally
block after running the transaction. Calling Transaction#discard()
after Transaction#commit()
is a no-op and you can call discard()
multiple times with no additional side-effects.
Transaction txn = dgraphClient.newTransaction();
try {
// Do something here
// ...
} finally {
txn.discard();
}
For read-only transactions, there is no need to call Transaction.discard
, which is equivalent to a no-op.
Transaction readOnlyTxn = dgraphClient.newReadOnlyTransaction();
Read-only transactions can be set as best-effort. Best-effort queries relax the requirement of linearizable reads. This is useful when running queries that do not require a result from the latest timestamp.
Transaction bestEffortTxn = dgraphClient.newReadOnlyTransaction()
.setBestEffort(true);
Running a mutationTransaction#mutate
runs a mutation. It takes in a Mutation
object, which provides two main ways to set data: JSON and RDF N-Quad. You can choose whichever way is convenient. We’re going to use JSON. First we define a Person
class to represent a person. This data is serialized into JSON.
class Person {
String name
Person() {}
}
Next, we initialize a Person
object, serialize it and use it in Mutation
object.
// Create data
Person person = new Person();
person.name = "Alice";
// Serialize it
Gson gson = new Gson();
String json = gson.toJson(person);
// Run mutation
Mutation mu = Mutation.newBuilder()
.setSetJson(ByteString.copyFromUtf8(json.toString()))
.build();
txn.mutate(mu);
Sometimes, you only want to commit mutation, without querying anything further. In such cases, you can use a CommitNow
field in Mutation
object to indicate that the mutation must be immediately committed. Mutation can be run using the doRequest
function as well.
Request request = Request.newBuilder()
.addMutations(mu)
.build();
txn.doRequest(request);
Committing a transactionA transaction can be committed using the Transaction#commit()
method. If your transaction consisted solely of calls to Transaction#query()
, and no calls to Transaction#mutate()
, then calling Transaction#commit()
isn’t necessary. An error is returned if other transactions running concurrently modify the same data that was modified in this transaction. It is up to the user to retry transactions when they fail.
Transaction txn = dgraphClient.newTransaction();
try {
// …
// Perform any number of queries and mutations
// …
// and finally …
txn.commit()
} catch (TxnConflictException ex) {
// Retry or handle exception.
} finally {
// Clean up. Calling this after txn.commit() is a no-op
// and hence safe.
txn.discard();
}
Running a queryYou can run a query by calling Transaction#query()
. You need to pass in a DQL query string, and a map (optional, could be empty) of any variables that you might want to set in the query. The response would contain a JSON
field, which has the JSON encoded result. You need to decode it before you can do anything useful with it. Let’s run the following query:
query all($a: string) {
all(func: eq(name, $a)) {
name
}
}
First we must create a People
class that helps us deserialize the JSON result:
class People {
List<Person> all;
People() {}
}
Then we run the query, deserialize the result and print it out:
// Query
String query =
"query all($a: string){\n" +
" all(func: eq(name, $a)) {\n" +
" name\n" +
" }\n" +
"}\n";
Map<String, String> vars = Collections.singletonMap("$a", "Alice");
Response response = dgraphClient.newReadOnlyTransaction().queryWithVars(query, vars);
// Deserialize
People ppl = gson.fromJson(response.getJson().toStringUtf8(), People.class);
// Print results
System.out.printf("people found: %d\n", ppl.all.size());
ppl.all.forEach(person -> System.out.println(person.name));
This should print: You can also use doRequest
function to run the query.
Request request = Request.newBuilder()
.setQuery(query)
.build();
txn.doRequest(request);
Running a Query with RDF responseYou can get query results as an RDF response by calling either queryRDF()
or queryRDFWithVars()
. The response contains the getRdf()
method, which provides the RDF encoded output.
If you are querying for uid
values only, use a JSON format response.
// Query
String query = "query me($a: string) { me(func: eq(name, $a)) { name }}";
Map<String, String> vars = Collections.singletonMap("$a", "Alice");
Response response =
dgraphAsyncClient.newReadOnlyTransaction().queryRDFWithVars(query, vars).join();
// Print results
System.out.println(response.getRdf().toStringUtf8());
This should print (assuming Alice’s uid
is 0x2
): Running an upsert: query + mutationThe txn.doRequest
function allows you to run upserts consisting of one query and one mutation. Variables can be defined in the query and used in the mutation. You could also use txn.doRequest
to perform a query followed by a mutation.
String query = "query {\n" +
"user as var(func: eq(email, \"wrong_email@dgraph.io\"))\n" +
"}\n";
Mutation mu = Mutation.newBuilder()
.setSetNquads(ByteString.copyFromUtf8("uid(user) <email> \"correct_email@dgraph.io\" ."))
.build();
Request request = Request.newBuilder()
.setQuery(query)
.addMutations(mu)
.setCommitNow(true)
.build();
txn.doRequest(request);
Running a conditional upsertThe upsert block also allows specifying a conditional mutation block using an @if
directive. The mutation is executed only when the specified condition is true. If the condition is false, the mutation is silently ignored. See more about Conditional Upsert Here.
String query = "query {\n" +
"user as var(func: eq(email, \"wrong_email@dgraph.io\"))\n" +
"}\n";
Mutation mu = Mutation.newBuilder()
.setSetNquads(ByteString.copyFromUtf8("uid(user) <email> \"correct_email@dgraph.io\" ."))
.setCond("@if(eq(len(user), 1))")
.build();
Request request = Request.newBuilder()
.setQuery(query)
.addMutations(mu)
.setCommitNow(true)
.build();
txn.doRequest(request);
Setting deadlinesIt is recommended that you always set a deadline for each client call, after which the client terminates. This is in line with the recommendation for any gRPC client. Read this forum post for more details.
channel = ManagedChannelBuilder.forAddress("localhost", 9080).usePlaintext(true).build();
DgraphGrpc.DgraphStub stub = DgraphGrpc.newStub(channel);
ClientInterceptor timeoutInterceptor = new ClientInterceptor(){
@Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
return next.newCall(method, callOptions.withDeadlineAfter(500, TimeUnit.MILLISECONDS));
}
};
stub.withInterceptors(timeoutInterceptor);
DgraphClient dgraphClient = new DgraphClient(stub);
Setting metadata headersCertain headers such as authentication tokens need to be set globally for all subsequent calls. Below is an example of setting a header with the name “auth-token”:
// create the stub first
ManagedChannel channel =
ManagedChannelBuilder.forAddress(TEST_HOSTNAME, TEST_PORT).usePlaintext(true).build();
DgraphStub stub = DgraphGrpc.newStub(channel);
// use MetadataUtils to augment the stub with headers
Metadata metadata = new Metadata();
metadata.put(
Metadata.Key.of("auth-token", Metadata.ASCII_STRING_MARSHALLER), "the-auth-token-value");
stub = MetadataUtils.attachHeaders(stub, metadata);
// create the DgraphClient wrapper around the stub
DgraphClient dgraphClient = new DgraphClient(stub);
// trigger a RPC call using the DgraphClient
dgraphClient.alter(Operation.newBuilder().setDropAll(true).build());
Helper methods Delete multiple edgesThe example below uses the helper method Helpers#deleteEdges
to delete multiple edges corresponding to predicates on a node with the given UID. The helper method takes an existing mutation, and returns a new mutation with the deletions applied.
Mutation mu = Mutation.newBuilder().build()
mu = Helpers.deleteEdges(mu, uid, "friends", "loc");
dgraphClient.newTransaction().mutate(mu);
Closing the database connectionTo disconnect from Dgraph, call ManagedChannel#shutdown
on the gRPC channel object created when creating a Dgraph client. Using the asynchronous clientDgraph Client for Java also bundles an asynchronous API, which can be used by instantiating the DgraphAsyncClient
class. The usage is almost exactly the same as the DgraphClient
(show in previous section) class. The main differences is that the DgraphAsyncClient#newTransacation()
returns an AsyncTransaction
class. The API for AsyncTransaction
is exactly Transaction
. The only difference is that instead of returning the results directly, it returns immediately with a corresponding CompletableFuture<T>
object. This object represents the computation which runs asynchronously to yield the result in the future. Read more about CompletableFuture<T>
in the Java 8 documentation. Here is the asynchronous version of the preceding code, which runs a query.
// Query
String query =
"query all($a: string){\n" +
" all(func: eq(name, $a)) {\n" +
" name\n" +
"}\n" +
"}\n";
Map<String, String> vars = Collections.singletonMap("$a", "Alice");
AsyncTransaction txn = dgraphAsyncClient.newTransaction();
txn.query(query).thenAccept(response -> {
// Deserialize
People ppl = gson.fromJson(res.getJson().toStringUtf8(), People.class);
// Print results
System.out.printf("people found: %d\n", ppl.all.size());
ppl.all.forEach(person -> System.out.println(person.name));
});
Checking the request latencyIf you would like to see the latency for either a mutation or query request, the latency field in the returned result can be helpful. Here is an example to log the latency of a query request:
Response resp = txn.query(query);
Latency latency = resp.getLatency();
logger.info("parsing latency:" + latency.getParsingNs());
logger.info("processing latency:" + latency.getProcessingNs());
logger.info("encoding latency:" + latency.getEncodingNs());
Similarly you can get the latency of a mutation request:
Assigned assignedIds = dgraphClient.newTransaction().mutate(mu);
Latency latency = assignedIds.getLatency();
Concurrent mutations and conflictsThis how-to guide provides an example on how to handle concurrent modifications using a multi-threaded Java Program. The example demonstrates transaction conflicts in Dgraph. Steps to run this example are as follows. Step 1: start a new terminal and launch Dgraph with the following command line.
docker run -it -p 8080:8080 -p 9080:9080 dgraph/standalone:%VERSION_HERE
Step 2: check out the source code from the ‘samples’ directory in dgraph4j repo. This particular example can found at the path samples/concurrent-modification
. In order to run this example, execute the following maven command from the ‘concurrent-modification’ folder.
mvn clean install exec:java
Step 3: on running the example, the program initializes Dgraph with the following schema.
<clickCount>: int @index(int) .
<name>: string @index(exact) .
Step 4: the program also initializes user “Alice” with a ‘clickCount’ of value ‘1’, and then proceeds to increment ‘clickCount’ concurrently in two threads. Dgraph throws an exception if a transaction is updating a given predicate that is being concurrently modified. As part of the exception handling logic, the program sleeps for 1 second on receiving a concurrent modification exception (“TxnConflictException”), and then retries. The logs below show that two threads are increasing clickCount for the same user named Alice (note the same UID). Thread #1 succeeds immediately, and Dgraph throws a concurrent modification conflict on Thread 2. Thread 2 sleeps for 1 second and retries, and this time succeeds.
1599628015260 Thread #2 increasing clickCount for uid 0xe, Name: Alice
1599628015260 Thread #1 increasing clickCount for uid 0xe, Name: Alice
1599628015291 Thread #1 succeeded after 0 retries
1599628015297 Thread #2 found a concurrent modification conflict, sleeping for 1 second...
1599628016297 Thread #2 resuming
1599628016310 Thread #2 increasing clickCount for uid 0xe, Name: Alice
1599628016333 Thread #2 succeeded after 1 retries
Step 5: please note that the final value of clickCount is 3 (initial value was 1), which is correct. Query:
{
Alice(func: has(<name>)) @filter(eq(name,"Alice" )) {
uid
name
clickCount
}
}
Response:
{
"data": {
"Alice": [
{
"uid": "0xe",
"name": "Alice",
"clickCount": 3
}
]
}
}
SummaryConcurrent modifications to the same predicate causes the “TxnConflictException” exception. When several transactions hit the same node’s predicate at the same time, the first one succeeds, while the other receives the “TxnConflictException”. Upon constantly retrying, the transactions begin to succeed one after another, and given enough retries, correctly completes its work.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4