- Notifications
You must be signed in to change notification settings - Fork65
Official Dgraph Java client
License
dgraph-io/dgraph4j
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
A minimal implementation for a Dgraph client for Java 11 and above, usinggrpc.
Note: v24.0.0 features an upgraded protobuf dependency which requires an upgrade to JDK 11. Onaccount of this breaking change, all legacy applications built upon JDK 8 would be impacted.
This client follows theDgraph Go client closely.
Before using this client, we highly recommend that you go throughdocs.dgraph.io, and understandhow to run and work with Dgraph.
UseDiscuss Issues for reporting issues aboutthis repository.
- Table of Contents
- Download
- Supported Versions
- Quickstart
- Intro
- Using the Synchronous Client
- Creating a Client
- Creating a Client for Dgraph Cloud
- Creating a Secure Client using TLS
- Check Dgraph version
- Login Using ACL
- Altering the Database
- Creating a Transaction
- Running a Mutation
- Committing a Transaction
- Running a Query
- Running a Query with RDF response
- Running an Upsert: Query + Mutation
- Running a Conditional Upsert
- Setting Deadlines
- Setting Metadata Headers
- Helper Methods
- Closing the DB Connection
- Using the Asynchronous Client
- Checking the request latency
- Development
grab via Maven:
<dependency> <groupId>io.dgraph</groupId> <artifactId>dgraph4j</artifactId> <version>24.2.0</version></dependency>
or Gradle:
compile'io.dgraph:dgraph4j:24.2.0'Depending on the version of Dgraph that you are connecting to, you will have to use a differentversion of this client.
| Dgraph version | dgraph4j version | java version |
|---|---|---|
| 1.0.X | 1.X.X | 1.9.X |
| 1.1.0 - 2.X.X | 2.X.X | 1.9.X |
| 20.03.X-20.07.X | 20.03.X | 1.9.X |
| 20.11.X | 20.11.X | 1.9.X |
| >= 21.XX.X | 21.XX.X | 1.9.X |
| >= 24.X.X | 24.X.X | 11 |
v24.0.0 features an upgraded protoc-protobuf dependency that requires an upgrade to JDK 11. Thisversion is incompatible with Java 1.8 and and requires an upgrade to Java 11.
The following is only applicable to dgraph4j versions < v24.X.X.
If you aren't using gRPC with TLS, then the above version table will work for you with Java 1.8.xtoo.
If you're using gRPC with TLS on Java 1.8.x, then you will need to follow gRPC docshere. Basically,it will require you to add the following dependency in your app with correct version for thecorresponding
grpc-nettyversion used bydgraph4j. You can find out the correct version of thedependency to use from the version combination table inthis section ingrpc-nettydocs.For maven:
<dependency> <groupId>io.netty</groupId> <artifactId>netty-tcnative-boringssl-static</artifactId> <version><!-- See table in gRPC docs for correct version--></version></dependency>
For Gradle:
compile'io.netty:netty-tcnative-boringssl-static:<See table in gRPC docs for correct version>'The following table lists the
grpc-nettyversions used by differentdgraph4jversions overtime, along with the supported versions ofnetty-tcnative-boringssl-staticfor the correspondinggrpc-nettyversion:dgraph4j version grpc-netty version netty-tcnative-boringssl-static version >= 24.0.0 1.65.1 4.1.100.Final >= 24.1.1 1.68.2 4.1.110.Final >= 24.2.0 1.69.1 4.1.111.Final For example, when using
dgraph4j v24.0.0, the version of thenetty-tcnative-boringssl-staticdependency to be used is4.1.100.Final, as suggested by gRPC docs forgrpc-netty v1.65.1.
Build and run theDgraphJavaSample project in thesamples folder, which contains an end-to-endexample of using the Dgraph Java client. Follow the instructions in the README of that project.
This library supports two styles of clients, the synchronous clientDgraphClient and the asyncclientDgraphAsyncClient. ADgraphClient orDgraphAsyncClient can be initialised by passing ita list ofDgraphBlockingStub clients. TheanyClient() API can randomly pick a stub, which canthen be used for GRPC operations. In the next section, we will explain how to create a synchronousclient and use it to mutate or query dgraph. For the async client, more details can be found in theUsing the Asynchronous Client section.
This library supports connecting to a Dgraph cluster using connection strings. Dgraph connectionsstrings take the formdgraph://{username:password@}host:port?args.
username andpassword are optional. If username is provided, a password must also be present. Ifsupplied, these credentials are used to log into a Dgraph cluster through the ACL mechanism.
Valid connection string args:
| Arg | Value | Description |
|---|---|---|
| apikey | <key> | a Dgraph Cloud API Key |
| bearertoken | <token> | an access token |
| sslmode | disable | require | verify-ca | TLS option, the default isdisable. Ifverify-ca is set, the TLS certificate configured in the Dgraph cluster must be from a valid certificate authority. |
Note that usingsslmode=require disables certificate validation and significantly reduces thesecurity of TLS. This mode should only be used in non-production (e.g., testing or development)environments.
Some example connection strings:
| Value | Explanation |
|---|---|
| dgraph://localhost:9080 | Connect to localhost, no ACL, no TLS |
| dgraph://sally:supersecret@dg.example.com:443?sslmode=verify-ca | Connect to remote server, use ACL and require TLS and a valid certificate from a CA |
| dgraph://foo-bar.grpc.us-west-2.aws.cloud.dgraph.io:443?sslmode=verify-ca&apikey=<your-api-connection-key> | Connect to a Dgraph Cloud cluster |
| dgraph://foo-bar.grpc.hypermode.com?sslmode=verify-ca&bearertoken=<some access token> | Connect to a Dgraph cluster protected by a secure gateway |
Using theDgraphClient.open function with a connection string:
// open a connection to an ACL-enabled, non-TLS cluster and login as grootDgraphClientclient =DgraphClient.open("dgraph://groot:password@localhost:8090");// some time later...client.shutdown();
The following code snippet shows how to create a synchronous client using three connections.
ManagedChannelchannel1 =ManagedChannelBuilder .forAddress("localhost",9080) .usePlaintext().build();DgraphStubstub1 =DgraphGrpc.newStub(channel1);ManagedChannelchannel2 =ManagedChannelBuilder .forAddress("localhost",9082) .usePlaintext().build();DgraphStubstub2 =DgraphGrpc.newStub(channel2);ManagedChannelchannel3 =ManagedChannelBuilder .forAddress("localhost",9083) .usePlaintext().build();DgraphStubstub3 =DgraphGrpc.newStub(channel3);DgraphClientdgraphClient =newDgraphClient(stub1,stub2,stub3);
If you want to connect to Dgraph running on aDgraph Cloud instance, thenall you need is the URL of your Dgraph Cloud instance and the API key. You can get a client withthem as follows :
DgraphStubstub =DgraphClient.clientStubFromCloudEndpoint("https://your-instance.cloud.dgraph.io/graphql","your-api-key");DgraphClientdgraphClient =newDgraphClient(stub);
Note theDgraphClient.open method can be used if you have a Dgraph connection string (see above).
To setup a client using TLS, you could use the following code snippet. The server needs to be setupusing the instructions providedhere.
If you are doing client verification, you need to convert the client key from PKCS#1 format toPKCS#8 format. By default, grpc doesn't support reading PKCS#1 format keys. To convert the format,you could use theopenssl tool.
First, let's install theopenssl tool:
apt install openssl
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:
SslContextBuilderbuilder =GrpcSslContexts.forClient();builder.trustManager(newFile("<path to ca.crt>"));// Skip the next line if you are not performing client verification.builder.keyManager(newFile("<path to client.name.crt>"),newFile("<path to client.name.java.key>"));SslContextsslContext =builder.build();ManagedChannelchannel =NettyChannelBuilder.forAddress("localhost",9080) .sslContext(sslContext) .build();DgraphGrpc.DgraphStubstub =DgraphGrpc.newStub(channel);DgraphClientdgraphClient =newDgraphClient(stub);
Checking the version of the Dgraph server this client is interacting with is as easy as:
Versionv =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 isable to communicate with the Dgraph server. This will also help reduce the latency of the firstquery/mutation which results from some dynamic library loading and linking that happens in JVM (seethis issue for more details).
If ACL is enabled then you can log-in to the default namespace (0) with following:
dgraphClient.login(USER_ID,USER_PASSWORD);
For logging-in to some other namespace, use theloginIntoNamespace method on the client:
dgraphClient.loginIntoNamespace(USER_ID,USER_PASSWORD,NAMESPACE);
Once logged-in, thedgraphClient object can be used to do any further operations.
To set the schema, create anOperation object, set the schema and pass it toDgraphClient#altermethod.
Stringschema ="name: string @index(exact) .";Operationoperation =Operation.newBuilder().setSchema(schema).build();dgraphClient.alter(operation);
Starting Dgraph version 20.03.0, indexes can be computed in the background. You can call thefunctionsetRunInBackground(true) as shown below before callingalter. You can find more detailshere.
Stringschema ="name: string @index(exact) .";Operationoperation =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 usefulif you wish to discard all the data, and start from a clean slate, without bringing the instancedown.
// 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());
There are two types of transactions in dgraph, i.e. the read-only transactions that only includequeries and the transactions that change data in dgraph with mutate operations. Both the synchronousclientDgraphClient and the async clientDgraphAsyncClient support the two types of transactionsby providing thenewTransaction and thenewReadOnlyTransaction APIs. Creating a transaction is alocal operation and incurs no network overhead.
In most of the cases, the normal read-write transactions is used, which can have any number of queryor mutate operations. However, if a transaction only has queries, you might benefit from a read-onlytransaction, which can share the same read timestamp across multiple such read-only transactions andcan result in lower latencies.
For normal read-write transactions, it is a good practise to callTransaction#discard() in afinally block after running the transaction. CallingTransaction#discard() afterTransaction#commit() is a no-op and you can calldiscard() multiple times with no additionalside-effects.
Transactiontxn =dgraphClient.newTransaction();try {// Do something here// ...}finally {txn.discard();}
For read-only transactions, there is no need to callTransaction.discard, which is equivalent to ano-op.
TransactionreadOnlyTxn =dgraphClient.newReadOnlyTransaction();
Read-only transactions can be set as best-effort. Best-effort queries relax the requirement oflinearizable reads. This is useful when running queries that do not require a result from the latesttimestamp.
TransactionbestEffortTxn =dgraphClient.newReadOnlyTransaction() .setBestEffort(true);
Transaction#mutate runs a mutation. It takes in aMutation object, which provides two main waysto set data: JSON and RDF N-Quad. You can choose whichever way is convenient.
We're going to use JSON. First we define aPerson class to represent a person. This data will beserialized into JSON.
classPerson {StringnamePerson() {}}
Next, we initialise aPerson object, serialize it and use it inMutation object.
// Create dataPersonperson =newPerson();person.name ="Alice";// Serialize itGsongson =newGson();Stringjson =gson.toJson(person);// Run mutationMutationmu =Mutation.newBuilder() .setSetJson(ByteString.copyFromUtf8(json.toString())) .build();// mutationResponse stores a Response protocol buffer objectResponsemutationResponse =txn.mutate(mu);// eg: to get the UIDs created in this mutationSystem.out.println(mutationResponse.getUidsMap())
Sometimes, you only want to commit mutation, without querying anything further. In such cases, youcan use aCommitNow field inMutation object to indicate that the mutation must be immediatelycommitted.
Mutation can be run using thedoRequest function as well.
Requestrequest =Request.newBuilder() .addMutations(mu) .build();txn.doRequest(request);
A transaction can be committed using theTransaction#commit() method. If your transactionconsisted solely of calls toTransaction#query(), and no calls toTransaction#mutate(), thencallingTransaction#commit() is not necessary.
An error will be returned if other transactions running concurrently modify the same data that wasmodified in this transaction. It is up to the user to retry transactions when they fail.
Transactiontxn =dgraphClient.newTransaction();try {// …// Perform any number of queries and mutations// …// and finally …txn.commit()}catch (TxnConflictExceptionex) {// Retry or handle exception.}finally {// Clean up. Calling this after txn.commit() is a no-op// and hence safe.txn.discard();}
You can run a query by callingTransaction#query(). You will need to pass in a GraphQL+- querystring, and a map (optional, could be empty) of any variables that you might want to set in thequery.
The response would contain aJSON field, which has the JSON encoded result. You will need todecode it before you can do anything useful with it.
Let’s run the following query:
queryall($a:string) {all(func:eq(name,$a)) {name }}
First we must create aPeople class that will help us deserialize the JSON result:
classPeople {List<Person>all;People() {}}
Then we run the query, deserialize the result and print it out:
// QueryStringquery ="query all($a: string){\n" +" all(func: eq(name, $a)) {\n" +" name\n" +" }\n" +"}\n";Map<String,String>vars =Collections.singletonMap("$a","Alice");Responseresponse =dgraphClient.newReadOnlyTransaction().queryWithVars(query,vars);// DeserializePeopleppl =gson.fromJson(response.getJson().toStringUtf8(),People.class);// Print resultsSystem.out.printf("people found: %d\n",ppl.all.size());ppl.all.forEach(person ->System.out.println(person.name));
This should print:
people found: 1Alice
You can also usedoRequest function to run the query.
Requestrequest =Request.newBuilder() .setQuery(query) .build();txn.doRequest(request);
You can get query results as an RDF response by calling eitherqueryRDF() orqueryRDFWithVars().The response contains thegetRdf() method, which will provide the RDF encoded output.
Note: If you are querying foruid values only, use a JSON format response
// QueryStringquery ="query me($a: string) { me(func: eq(name, $a)) { name }}";Map<String,String>vars =Collections.singletonMap("$a","Alice");Responseresponse =dgraphAsyncClient.newReadOnlyTransaction().queryRDFWithVars(query,vars).join();// Print resultsSystem.out.println(response.getRdf().toStringUtf8());
This should print (assuming Alice'suid is0x2):
<0x2><name>"Alice".
Thetxn.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 usetxn.doRequestto perform a query followed by a mutation.
To know more about upsert, we highly recommend going through the docs athttps://docs.dgraph.io/mutations/#upsert-block.
Stringquery ="query {\n" +"user as var(func: eq(email,\"wrong_email@dgraph.io\"))\n" +"}\n";Mutationmu =Mutation.newBuilder() .setSetNquads(ByteString.copyFromUtf8("uid(user) <email>\"correct_email@dgraph.io\" .")) .build();Requestrequest =Request.newBuilder() .setQuery(query) .addMutations(mu) .setCommitNow(true) .build();txn.doRequest(request);
The upsert block also allows specifying a conditional mutation block using an@if directive. Themutation is executed only when the specified condition is true. If the condition is false, themutation is silently ignored.
See more about Conditional UpsertHere.
Stringquery ="query {\n" +"user as var(func: eq(email,\"wrong_email@dgraph.io\"))\n" +"}\n";Mutationmu =Mutation.newBuilder() .setSetNquads(ByteString.copyFromUtf8("uid(user) <email>\"correct_email@dgraph.io\" .")) .setCond("@if(eq(len(user), 1))") .build();Requestrequest =Request.newBuilder() .setQuery(query) .addMutations(mu) .setCommitNow(true) .build();txn.doRequest(request);
It is recommended that you always set a deadline for each client call, after which the clientterminates. This is in line with the recommendation for any gRPC client. Readthis forumpost for more details.
channel =ManagedChannelBuilder.forAddress("localhost",9080).usePlaintext(true).build();DgraphGrpc.DgraphStubstub =DgraphGrpc.newStub(channel);ClientInterceptortimeoutInterceptor =newClientInterceptor(){@Overridepublic <ReqT,RespT>ClientCall<ReqT,RespT>interceptCall(MethodDescriptor<ReqT,RespT>method,CallOptionscallOptions,Channelnext) {returnnext.newCall(method,callOptions.withDeadlineAfter(500,TimeUnit.MILLISECONDS)); }};stub =stub.withInterceptors(timeoutInterceptor);DgraphClientdgraphClient =newDgraphClient(stub);
dgraphClient.newTransaction().query(query,500,TimeUnit.MILLISECONDS);
Certain 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 firstManagedChannelchannel =ManagedChannelBuilder.forAddress(TEST_HOSTNAME,TEST_PORT).usePlaintext(true).build();DgraphStubstub =DgraphGrpc.newStub(channel);// use MetadataUtils to augment the stub with headersMetadatametadata =newMetadata();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 stubDgraphClientdgraphClient =newDgraphClient(stub);// trigger a RPC call using the DgraphClientdgraphClient.alter(Operation.newBuilder().setDropAll(true).build());
The example below uses the helper methodHelpers#deleteEdges to delete multiple edgescorresponding to predicates on a node with the given uid. The helper method takes an existingmutation, and returns a new mutation with the deletions applied.
Mutationmu =Mutation.newBuilder().build()mu =Helpers.deleteEdges(mu,uid,"friends","loc");dgraphClient.newTransaction().mutate(mu);
To disconnect from Dgraph, callManagedChannel#shutdown on the gRPC channel object created whencreating a Dgraph client.
channel.shutdown();
You can also close all channels in from the client object:
dgraphClient.shutdown();
Dgraph Client for Java also bundles an asynchronous API, which can be used by instantiating theDgraphAsyncClient class. The usage is almost exactly the same as theDgraphClient (show inprevious section) class. The main differences is that theDgraphAsyncClient#newTransacation()returns anAsyncTransaction class. The API forAsyncTransaction is exactlyTransaction. Theonly difference is that instead of returning the results directly, it returns immediately with acorrespondingCompletableFuture<T> object. This object represents the computation which runsasynchronously to yield the result in the future. Read more aboutCompletableFuture<T> in theJava 8 documentation.
Here is the asynchronous version of the code above, which runs a query.
// QueryStringquery ="query all($a: string){\n" +" all(func: eq(name, $a)) {\n" +" name\n" +"}\n" +"}\n";Map<String,String>vars =Collections.singletonMap("$a","Alice");AsyncTransactiontxn =dgraphAsyncClient.newTransaction();txn.query(query).thenAccept(response -> {// DeserializePeopleppl =gson.fromJson(res.getJson().toStringUtf8(),People.class);// Print resultsSystem.out.printf("people found: %d\n",ppl.all.size());ppl.all.forEach(person ->System.out.println(person.name));});
If you would like to see the latency for either a mutation or query request, the latency field inthe returned result can be helpful. Here is an example to log the latency of a query request:
Responseresp =txn.query(query);Latencylatency =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:
AssignedassignedIds =dgraphClient.newTransaction().mutate(mu);Latencylatency =assignedIds.getLatency();
Warning: The gradle build runs integration tests on a locally running Dgraph server. The testswill remove all data from your Dgraph instance. So make sure that you don't have any important dataon your Dgraph instance.
./gradlew build
If you have made changes to thetask.proto file, this step will also regenerate the source filesgenerated by Protocol Buffer tools.
We usegoogle-java-format to format the source code. If you run./gradlew build, you will bewarned if there is code that is not conformant. You can run./gradlew goJF to format the sourcecode, before committing it.
Warning: This command will runs integration tests on a locally running Dgraph server. The testswill remove all data from your Dgraph instance. So make sure that you don't have any important dataon your Dgraph instance.
Make sure you have a Dgraph server running on localhost before you run this task.
./gradlewtestAbout
Official Dgraph Java client
Resources
License
Code of conduct
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.