MicroStream is one of the most interesting projects in the Java space currently. It takes a fresh approach to Java persistence, acting as a kind of seamless object persistence data layer. MicroStream eliminates the friction of figuring out how to persist an application's runtime graph. I've introduced MicroStream in a previous article, so this time we'll get right into a demonstration. You'll learn how to use MicroStream with a default filesystem first, then we'll update our demo program to use the MariaDB RDBMS.
Set up a Java project
Let's start by creating a new Java project. We’ll use a simple Maven project, but any approach works. Open your command line and enter the code in Listing 1 to create a new project with a Maven archetype.
Listing 1. Create a new project
$ mvn archetype:generate -DgroupId=com.infoworld -DartifactId=microstream -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4 -Dpackage=com.infoworld -DinteractiveMode=false
Note that I've set the group ID as com.infoworld
, the project ID as microstream
, and the package as com.infoworld
.
Next, open the /myproject/pom.xml
file and add the dependency shown in Listing 2. We’ll use MicroStream's embedded storage library, which is the simplest approach. Also, make sure the Maven compiler version is set to 1.8.
Listing 2. Pom.xml setup
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<!-- ... -->
<dependency>
<groupId>one.microstream</groupId>
<artifactId>microstream-storage-embedded</artifactId>
<version>07.01.00-MS-GA</version>
</dependency>
Now we can run the MicroStream starter project and get a response from the command line, as shown in Listing 3.
Listing 3. Run the MicroStream starter
$ mvn clean package
$ java -cp target/microstream-1.0-SNAPSHOT.jar com.infoworld.App
Hello World!
Add a model to be persisted
Next, let's incorporate a simple model that we can persist. We’ll change the starter project's main class to take three String
command-line arguments and hold that in an ArrayList
.
Listing 4. App.java with ArrayList model
package com.infoworld;
import java.util.ArrayList;
public class App {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
for (int i = 0; i < args.length; i++) {
list.add(args[i]);
}
System.out.println(list);
}
}
We can test the array list version as shown in Listing 5. It takes the passed-in arguments and puts them in a List
, then outputs it.
Listing 5. Run App.java with the ArrayList model
$ mvn clean package
$ java -cp target/microstream-1.0-SNAPSHOT.jar com.infoworld.App test test2 test3
[test, test2, test3]
Store the list with MicroStream
Now, modify this code to store the list with MicroStream. Listing 6 shows how it's done.
Listing 6. Save the list
package com.infoworld;
import java.util.ArrayList;
public class App {
public static void main(String[] args) {
ArrayList<String> list;
if (args.length > 0) {
list = new ArrayList<String>();
for (int i = 0; i < args.length; i++) {
list.add(args[i]);
}
EmbeddedStorageManager manager = EmbeddedStorage.start();
manager.setRoot(list);
manager.storeRoot();
System.out.println("Saved: " + list);
}
}
Notice how easy it is to save the state with defaults. When we call EmbeddedStorage.start()
without arguments, it creates a new MicroStream engine with defaults, including a default file storage location and persistence strategy.
We then set the root object to be our list with manager.setRoot()
. Every MicroStream application has a root object that is the root of the entire object graph to be persisted.
The manager.storeRoot()
tells the engine to persist the state.
Listing 7 shows a complete version of the application. Note that it persists the list when it's passed in; if not, it retrieves and displays it.
Listing 7. Save and recover objects with defaults
import one.microstream.storage.embedded.types.EmbeddedStorage;
import one.microstream.storage.embedded.types.EmbeddedStorageManager;
import java.nio.file.Paths;
import java.util.ArrayList;
public class App {
public static void main(String[] args) {
ArrayList<String> list;
if (args.length > 0) {
list = new ArrayList<String>();
for (int i = 0; i < args.length; i++) {
list.add(args[i]);
}
EmbeddedStorageManager manager = EmbeddedStorage.start();
manager.setRoot(list);
manager.storeRoot();
System.out.println("Saved: " + list);
} else {
EmbeddedStorageManager storageEngine = EmbeddedStorage.start();
list = (ArrayList<String>) storageEngine.root();
System.out.println("Loaded: " + list);
}
System.exit(0);
}
}
You’ll notice that if you run the program with arguments, it’ll save the list of strings, and if you run it without the args, it’ll pull them from storage and display them. Listing 8 has both options.
Listing 8. Save and load the program
$ mvn clean package exec:java -Dexec.mainClass="com.infoworld.App" -Dexec.args="test test2 test3
Saved: [test, test2, test3]
$ mvn clean package exec:java -Dexec.mainClass="com.infoworld.App"
Loaded: [test, test2, test3]
You may be wondering where MicroStream is putting the storage files. By default, it uses the Java project's working directory. In our demo, it puts it at /microstream/microstream
, the root of the project. If you look there, you’ll see a /my-storage-folder
.
If you need to change the location of the storage files, you can configure it with an argument to start()
, like what's shown in Listing 9. Here, we set a file location on both start calls. Notice we import the standard Paths Java library to handle the [what?].
Listing 9. Set an alternative directory
import java.nio.file.Paths;
//...
EmbeddedStorageManager manager = EmbeddedStorage.start(Paths.get(System.getProperty("user.home"), "data"));
MicroStream with MariaDB
Now let’s see how to use an alternative storage system. We’ll look at using a local MariaDB community instance. In my case (on Ubuntu) I installed MariaDB as a service locally (see instructions). Once you have MariaDB running, we can modify our application to use the database to hold the state, as shown in Listing 10.
Listing 10. MariaDB version of the app
package com.infoworld;
import org.mariadb.jdbc.MariaDbDataSource;
import one.microstream.afs.sql.types.SqlFileSystem;
import one.microstream.afs.sql.types.SqlConnector;
import one.microstream.afs.sql.types.SqlProviderMariaDb;
import one.microstream.storage.embedded.types.EmbeddedStorage;
import one.microstream.storage.embedded.types.EmbeddedStorageManager;
import java.util.ArrayList;
public class App {
public static void main(String[] args) {
try {
MariaDbDataSource dataSource = new MariaDbDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/mydb");
dataSource.setUser("admin");
dataSource.setPassword("password");
SqlFileSystem fileSystem = SqlFileSystem.New(SqlConnector.Caching(SqlProviderMariaDb.New(dataSource)));
EmbeddedStorageManager manager = EmbeddedStorage.start(fileSystem.ensureDirectoryPath("microstream_storage"));
ArrayList<String> list;
if (args.length > 0) {
list = new ArrayList<String>();
for (int i = 0; i < args.length; i++) {
list.add(args[i]);
}
manager.setRoot(list);
manager.storeRoot();
System.out.println("Saved: " + list);
} else {
list = (ArrayList<String>) manager.root();
System.out.println("Loaded: " + list);
}
} catch (Exception e){
System.err.println("error: "+ e);
throw new RuntimeException(e);
}
}
}
Listing 10 has the full version of the application using a local instance of MariaDB instead of the default filesystem. This version assumes MariaDB is listening on localhost port 3306. The admin user password is "password".
Notice the line where EmbeddedStorage.start()
is called; it is passed the argument fileSystem.ensureDirectoryPath("microstream_storage")
. The effect is to add the microstream_storage table to the mydb database if it's not there already.
Incidentally, the error handling here is just a token. In a real application, we would need to be more conscientious about error handling.
The "afs" in the one.microstream.afs.sql
package stands for Abstract File System. The purpose is to let the application use MicroStream to save the object graph without worrying too much about how it's implemented. So a database, filesystem, or cloud object store like AWS S3 can all be used in the same way we're using MariaDB, except for how the storage is configured. NoSQL, like MongoDB, is also supported, although it is an enterprise feature.
To run this example, we need to add the MariaDB driver and the one.microstream.afs
JAR file to the pom.xm
l dependencies:
Listing 11. Add the MariaDB dependency
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>2.7.1</version>
</dependency>
<dependency>
<groupId>one.microstream</groupId>
<artifactId>microstream-afs-sql</artifactId>
<version>07.01.00-MS-GA</version>
</dependency>
Now the application can be run with the same commands seen back in Listing 8, with the state being persisted to the local instance of MariaDB.
Conclusion
MicroStream approaches Java persistence in a different way than most Java developers are used to architecturally. It eliminates the requirement of having a separate standalone component that is installed and maintained to deal with data storage. However, you've seen, you can still use an RDBMS if you wish.
MicroStream is something like an ORM (like Hibernate) but even more seamless. It opens up the possibility of thinking about data persistence within the mental realm of objects. You still need to consider what and when to save or load, but you don’t have to set aside the business model and go into the data layer as a distinct stage.
MicroStream takes a fresh approach to Java persistence, which could be a key feature in Java’s ongoing renaissance.