Build a Java App
Learn how to build a Java app using Stardog.
Introduction
Stardog makes the Enterprise Knowledge Graph fast and easy. We’re going to build a Java app that talks to Stardog’s RDF graph database in 5 easy steps.
- Download and install Stardog
- Create a Java project with Gradle
- Build a database
- Import data into the database
- Query and update the database
All of the code for this tutorial is available in the stardog-examples repo on Github. This guide was written using Stardog version 7.7.2 and Gradle version 7.2.
1. Download and Install Stardog
Install Stardog by following the instructions for your system on the Install Stardog page.
2. Set up a Gradle Project
Now let’s setup Gradle. In our project’s directory, run gradle init
. You will receive a series of prompts like the following:
Select type of project to generate:
1: basic
2: application
3: library
4: Gradle plugin
Enter selection (default: basic) [1..4] 2
Select implementation language:
1: C++
2: Groovy
3: Java
4: Kotlin
5: Scala
6: Swift
Enter selection (default: Java) [1..6] 3
Split functionality across multiple subprojects?:
1: no - only one application project
2: yes - application and library projects
Enter selection (default: no - only one application project) [1..2] 1
Select build script DSL:
1: Groovy
2: Kotlin
Enter selection (default: Groovy) [1..2] 1
Select test framework:
1: JUnit 4
2: TestNG
3: Spock
4: JUnit Jupiter
Enter selection (default: JUnit Jupiter) [1..4] 1
Project name (default: JavaStardogClient):
Source package (default: JavaStardogClient):
Input the appropriate selections for your project when prompted. You can see the selections made for this example above. This will generate the necessary Gradle files for our project, which we will add to as we go.
The only dependency we need is com.complexible.stardog:client-http:VERSION
where VERSION
is your Stardog server version, which we will add to the dependencies
section of our build.gradle
, found in the app
directory. The build.gradle
is as follows:
apply plugin: 'java'
apply plugin: 'application'
group 'org.example'
version '1.0-SNAPSHOT'
repositories {
maven { url "https://maven.stardog.com" }
mavenCentral()
}
dependencies {
implementation('com.complexible.stardog:client-http:7.7.2')
}
test {
useJUnitPlatform()
}
application {
mainClass = 'JavaStardogClient.App'
}
We should now have a directory structure that looks like the following:
.
├── app
│ ├── build.gradle
│ └── src
│ ├── main
│ │ ├── java
│ │ │ └── JavaStardogClient
│ │ │ └── App.java
│ │ └── resources
│ └── test
│ ├── java
│ │ └── JavaStardogClient
│ │ └── AppTest.java
│ └── resources
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
I have deleted app/src/test/java/JavaStardogClient/AppTest.java
since tests are beyond the scope of this tutorial, but this is where tests should be located.
3. Build a Database
We have Stardog installed and Gradle configured. Let’s start building our database. The first thing is to create a connection to Stardog. We are going to start by creating an admin connection which will be used to make sure our demo database is in the same state each time the program is run. This is done by dropping then re-creating the database at runtime and is simply done for demonstration purposes. In production, you most likely do not want to do this.
You will also need to adjust the url
, username
, password
, and to
(the database you are connecting to) based on your Stardog instance. Example values for a default local installation are provided below.
private static final String url = "http://localhost:5820";
private static final String username = "admin";
private static final String password = "admin";
private static final String to = "testDB";
/**
* Creates a connection to the DBMS itself so we can perform some administrative actions.
*/
public static void createAdminConnection() {
try (final AdminConnection aConn = AdminConnectionConfiguration.toServer(url)
.credentials(username, password)
.connect()) {
// A look at what databases are currently in Stardog
aConn.list().forEach(item -> System.out.println(item));
// Checks to see if the 'testDB' is in Stardog. If it is, we are
// going to drop it so we are starting fresh
if (aConn.list().contains(to)) {aConn.drop(to);}
// Convenience function for creating a persistent
// database with all the default settings.
aConn.disk(to).create();
}
}
4. Import Data
We will now add methods to create a ConnectionPool
, acquire a connection from that pool with getConnection()
, and release a connection with releaseConnection()
. The ConnectionConfiguration
will tell the pool how to create the new connections. We then pass the ConnectionConfiguration
to the ConnectionPoolConfig
as it is the basis of the pool.
We can also provide additional detail about the pool such as min/max pool size, expiration, and block wait time. Define these variables according to your use case. Once again, example values are provided below. We then create the pool and return so we can start using it.
private static int maxPool = 200;
private static int minPool = 10;
private static boolean reasoningType = false;
private static long blockCapacityTime = 900;
private static TimeUnit blockCapacityTimeUnit = TimeUnit.SECONDS;
private static long expirationTime = 300;
private static TimeUnit expirationTimeUnit = TimeUnit.SECONDS;
/**
* Now we want to create the configuration for our pool.
* @param connectionConfig the configuration for the connection pool
* @return the newly created pool which we will use to get our Connections
*/
private static ConnectionPool createConnectionPool(ConnectionConfiguration connectionConfig) {
ConnectionPoolConfig poolConfig = ConnectionPoolConfig
.using(connectionConfig)
.minPool(minPool)
.maxPool(maxPool)
.expiration(expirationTime, expirationTimeUnit)
.blockAtCapacity(blockCapacityTime, blockCapacityTimeUnit);
return poolConfig.create();
}
/**
* Obtains the Stardog connection from the connection pool
* @param connectionPool the connection pool to get our connection
* @return Stardog Connection
*/
public static Connection getConnection(ConnectionPool connectionPool) {
return connectionPool.obtain();
}
/**
* Releases the Stardog connection from the connection pool
* @param connection Stardog Connection
*/
public static void releaseConnection(ConnectionPool connectionPool, Connection connection) {
try {
connectionPool.release(connection);
} catch (StardogException e) {
e.printStackTrace();
}
}
After we get a Stardog connection, we will use it to populate our database. Changes to a database must occur within a transaction; i.e., begin followed by commit or rollback. Changes are not visible to others until the transaction is committed or until you perform a query operation to inspect the state of the database within the transaction.
We will use the rdf data below as an example in this tutorial. In this example, we are going to store it in the app/src/main/resources
folder.
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
:incredibleHulk rdf:type foaf:Person ;
foaf:name "Robert Bruce Banner"^^xsd:string ;
foaf:title "Incredible Hulk"^^xsd:string ;
foaf:givenname "Robert"^^xsd:string ;
foaf:family_name "Banner"^^xsd:string ;
foaf:knows :captainAmerica, :blackWidow, :thor, :ironMan .
:captainAmerica rdf:type foaf:Person ;
foaf:name "Steven Rogers"^^xsd:string ;
foaf:title "Captian America"^^xsd:string ;
foaf:givenname "Steven"^^xsd:string ;
foaf:family_name "Rogers"^^xsd:string ;
foaf:nick "Steve"^^xsd:string ;
foaf:knows :incredibleHulk, :blackWidow, :thor, :ironMan .
:thor rdf:type foaf:Person ;
foaf:name "Thor Odinson"^^xsd:string ;
foaf:title "Thor"^^xsd:string ;
foaf:givenname "Thor"^^xsd:string ;
foaf:family_name "Odinson"^^xsd:string ;
foaf:knows :incredibleHulk, :blackWidow, :captainAmerica, :ironMan .
:blackWidow rdf:type foaf:Person ;
foaf:name "Natalia Alianovna Romanova"^^xsd:string ;
foaf:title "Black Widow"^^xsd:string ;
foaf:givenname "Natalia"^^xsd:string ;
foaf:family_name "Romanova"^^xsd:string ;
foaf:nick "Natasha"^^xsd:string ;
foaf:knows :incredibleHulk, :thor, :captainAmerica, :ironMan .
5. Query the Database
Now that everything is set up, we can begin interacting with Stardog. In our main()
method, we will start by calling createAdminConnection()
, defining our ConnectionConfiguration
, and using the configuration to create our ConnectionPool
.
createAdminConnection();
ConnectionConfiguration connectionConfig = ConnectionConfiguration
.to(to)
.server(url)
.reasoning(reasoningType)
.credentials(username, password);
// creates the Stardog connection pool
ConnectionPool connectionPool = createConnectionPool(connectionConfig);
Next we will create our connection, add our data, then commit the change. After that, we can begin querying the data. Once we are done, we shutdown our connection pool, and the connection is automatically closed by the try/catch block.
try (Connection connection = getConnection(connectionPool)) {
// first start a transaction. This will generate the contents of
// the database from the N3 file.
connection.begin();
// declare the transaction
connection.add().io().format(RDFFormats.N3).stream(new FileInputStream("src/main/resources/marvel.rdf"));
// and commit the change
connection.commit();
// Query the database to get our list of Marvel superheroes and print the results to the console
SelectQuery query = connection.select("PREFIX foaf:<http://xmlns.com/foaf/0.1/> " +
"select * { ?s rdf:type foaf:Person }");
SelectQueryResult tupleQueryResult = query.execute();
QueryResultWriters.write(tupleQueryResult, System.out, TextTableQueryResultWriter.FORMAT);
} catch (StardogException | IOException e) {
e.printStackTrace();
} finally {
connectionPool.shutdown();
}
Within the try
block, we can do additional operations on our database such as adding additional data. First we will create IRI
variables for the other people in our database and then define our new person using these variables.
private static final String NS = "http://api.stardog.com/";
private static final IRI IronMan = Values.iri(NS, "ironMan");
private static final IRI BlackWidow = Values.iri(NS, "blackWidow");
private static final IRI CaptainAmerica = Values.iri(NS, "captainAmerica");
private static final IRI Thor = Values.iri(NS, "thor");
private static final IRI IncredibleHulk = Values.iri(NS, "incredibleHulk");
// first start a transaction - This will add Tony Stark A.K.A Iron Man to the database
connection.begin();
// declare the transaction
connection.add()
.statement(IronMan, RDF.TYPE, FOAF.Person)
.statement(IronMan, FOAF.name, Values.literal("Anthony Edward Stark"))
.statement(IronMan, FOAF.title, Values.literal("Iron Man"))
.statement(IronMan, FOAF.givenName, Values.literal("Anthony"))
.statement(IronMan, FOAF.familyName, Values.literal("Stark"))
.statement(IronMan, FOAF.knows, BlackWidow)
.statement(IronMan, FOAF.knows, CaptainAmerica)
.statement(IronMan, FOAF.knows, Thor)
.statement(IronMan, FOAF.knows, IncredibleHulk);
// and commit the change
connection.commit();
We can run the same query as before to now see Iron Man in the results.
tupleQueryResult = query.execute();
QueryResultWriters.write(tupleQueryResult, System.out, TextTableQueryResultWriter.FORMAT);
We can remove data as well. In this example we will remove every instance where Captain America is the subject or object.
// first start a transaction - this will remove Captain America from the list where he is eithe the
// subject or the object
connection.begin();
// declare the transaction
connection.remove()
.statements(CaptainAmerica, null, null)
.statements(null, null, CaptainAmerica);
// and commit the change
connection.commit();
If we re-run our query, we will now see that Captain America has been removed from the database.
Conclusion
I showed you how to install Stardog on a Linux environment, create an administration connection in order to perform administrative actions, create a connection pool to the database, and use a connection from the pool to perform transactions and queries.
The full code for this example can be found on our Github.