EclipseStore: Storing More Complex Data Structures
How to store complex data structures using EclipseStore? Are there any restrictions? We will explore the possibilities now!
Join the DZone community and get the full member experience.
Join For FreeIn the first part of my series, I showed how to prepare EclipseStore for use in a project. We also initialized the StorageManager
and saved, modified, and deleted the first data. But what about more complex structures? Can you use inheritance? To do this, we will now create a small class model.
First, let's look at what inheritance looks like. To do this, we take an interface called BaseInterfaceA
, an implementation BaseClassA
and a derivative LevelOneA
. We will now try to save this and see how it behaves depending on the input when saving.
Here is the listing of the classes and interfaces involved. Since we already saw in the first part that the implementation variant has no influence, I will use a standard implementation regarding getters and setters or the constructors.
public interface BaseInterfaceA {
public default String allUpperCase(String value){
return value.toUpperCase();
}
}
public class BaseClassA implements BaseInterfaceA {
private String valueBaseA;
@Override
public String toString() {
return "BaseClassA{" +
"valueBaseA='" + valueBaseA + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BaseClassA that = (BaseClassA) o;
return Objects.equals(valueBaseA, that.valueBaseA);
}
@Override
public int hashCode() {
return Objects.hash(valueBaseA);
}
public String getValueBaseA() {
return valueBaseA;
}
public void setValueBaseA(String valueBaseA) {
this.valueBaseA = valueBaseA;
}
}
public class LevelOneA extends BaseClassA {
private String valueOneA;
public LevelOneA(String valueOneA) {
this.valueOneA = valueOneA;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
LevelOneA levelOneA = (LevelOneA) o;
return Objects.equals(valueOneA, levelOneA.valueOneA);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), valueOneA);
}
public String getValueOneA() {
return valueOneA;
}
public void setValueOneA(String valueOneA) {
this.valueOneA = valueOneA;
}
@Override
public String toString() {
return "LevelOneA{" +
"valueOneA='" + valueOneA + '\'' +
'}';
}
}
Case I: Direct Saving of the Respective Classes
The entities are packed directly into a list and saved in the first case. As we can see, there are no special features here. This is the same case as the first article. The output of the printElements
method is exactly what we expected.
EmbeddedStorageManager storageManager = EmbeddedStorage.start();
storageManager.setRoot(new ArrayList<LevelOneA>());
storageManager.storeRoot();
List<LevelOneA> root = (List<LevelOneA>) storageManager.root();
root.add(new LevelOneA("levelOneA - 01"));
root.add(new LevelOneA("levelOneA - 02"));
root.add(new LevelOneA("levelOneA - 03"));
storageManager.storeRoot();
storageManager.shutdown();
storageManager = EmbeddedStorage.start();
List<LevelOneA> rootLoaded = (List<LevelOneA>) storageManager.root();
rootLoaded.forEach(System.out::println);
System.out.println(" ========== ");
storageManager.shutdown();
Console output:
LevelOneA{valueOneA='levelOneA - 01'}
LevelOneA{valueOneA='levelOneA - 02'}
LevelOneA{valueOneA='levelOneA - 03'}
==========
Case II: Save as a Base Class
Now, let's consider the case where a class is passed to the StorageManager
for storage as one of its base classes. In this case, the LevelOneA
class is passed as the base class BaseClassA
. It should be noted that this primary type was also used when defining the root list.
EmbeddedStorageManager storageManager = EmbeddedStorage.start();
storageManager.setRoot(new ArrayList<BaseClassA>());
storageManager.storeRoot();
List<BaseClassA> root = (List<BaseClassA>) storageManager.root();
root.add(new LevelOneA("levelOneA - 01"));
root.add(new LevelOneA("levelOneA - 02"));
root.add(new LevelOneA("levelOneA - 03"));
storageManager.storeRoot();
storageManager.shutdown();
storageManager = EmbeddedStorage.start();
List<BaseClassA> rootLoaded = (List<BaseClassA>) storageManager.root();
rootLoaded.forEach(System.out::println);
System.out.println(" ========== ");
storageManager.shutdown();
The output again meets our expectations.
LevelOneA{valueOneA='levelOneA - 01'}
LevelOneA{valueOneA='levelOneA - 02'}
LevelOneA{valueOneA='levelOneA - 03'}
==========
Case II: Saving an Implementation as an Interface
Now, let's move on to the case in which we proceed via the interface. There are no problems here, either. Everything behaves as expected.
EmbeddedStorageManager storageManager = EmbeddedStorage.start();
storageManager.setRoot(new ArrayList<BaseInterfaceA>());
storageManager.storeRoot();
List<BaseInterfaceA> root = (List<BaseInterfaceA>) storageManager.root();
root.add(new LevelOneA("levelOneA - 01"));
root.add(new LevelOneA("levelOneA - 02"));
root.add(new LevelOneA("levelOneA - 03"));
storageManager.storeRoot();
storageManager.shutdown();
storageManager = EmbeddedStorage.start();
List<BaseInterfaceA> rootLoaded = (List<BaseInterfaceA>) storageManager.root();
rootLoaded.forEach(System.out::println);
System.out.println(" ========== ");
storageManager.shutdown();
The output on the console is still unchanged.
LevelOneA{valueOneA='levelOneA - 01'}
LevelOneA{valueOneA='levelOneA - 02'}
LevelOneA{valueOneA='levelOneA - 03'}
==========
Case IV: Saving a Mixed List via a Common Interface
Now, if we define a list of type BaseInterfaceA
as root and then add instances of different implementations. How does EclipseStore
behave during the save process? To make a long story short, it works as intended. All instances are stored neatly after deployment. So we have no loss of information. The console output will then look like this.
LevelOneA{valueOneA='levelOneA - 01'}
BaseClassA{valueBaseA='BaseClassA - 02'}
LevelOneA{valueOneA='levelOneA - 03'}
==========
Storing Lists, Trees and Graphs
Now that we've looked at whether inheritance is supported as much as we hoped, we're getting to the point where we can start thinking about the data structures themselves. We have seen that simple entities and lists of entities are relatively easy for EclipseStore
. Now, let's go one step further and look at trees and graphs.
Storing of Trees
In computer science, we understand a tree to be a data structure that can have branches but does not contain any cycles. This makes them a special form of graphs, just as lists are a special form of trees. So, let's come to a tree implementation in which a node can have two child nodes. Of course, you can also build this structure with n child nodes, but this will not bring any added value in our case. If we create such a tree and give the root node to the StorageManager
, we can save this tree without any further action. Modifying individual elements also works as usual. You can also change the desired segment and save on this or any higher-level piece.
EmbeddedStorageManager storageManager = EmbeddedStorage.start();
Node rootNode = new Node("rootNode");
storageManager.setRoot(rootNode);
storageManager.storeRoot();
Node leftChildLev01 = new Node("Root-L");
Node rightChildLev01 = new Node("Root-R");
leftChildLev01.addLeft(new Node("Root-L-R"));
leftChildLev01.addLeft(new Node("Root-L-L"));
rightChildLev01.addLeft(new Node("Root-R-L"));
rightChildLev01.addRight(new Node("Root-R-R"));
rootNode.addLeft(leftChildLev01);
rootNode.addRight(rightChildLev01);
storageManager.storeRoot();
storageManager.shutdown();
storageManager = EmbeddedStorage.start();
Node rootLoaded = (Node) storageManager.root();
System.out.println(rootLoaded.toString());
System.out.println(" ========== ");
storageManager.shutdown();
Output to the console :
Node{id='rootNode',
leftNode=Node{id='Root-L',
leftNode=Node{id='Root-L-L',
leftNode=null,
rightNode=null
},
rightNode=null
},
rightNode=Node{id='Root-R',
leftNode=Node{id='Root-R-L',
leftNode=null,
rightNode=null
},
rightNode=Node{id='Root-R-R',
leftNode=null,
rightNode=null
}
}
}
Storing Graphs
Unfortunately, you don't just have to deal with lists and trees. Complex data models often have cycles. This can come about, for example, through bidirectional relationships. Unwanted cycles can also arise in a model if it grows over time and is repeatedly expanded. Whether these cycles are tightly or loosely coupled plays a minor role. The following example creates a chart that contains multiple cycles. Can the diagram then be saved, modified, and loaded? Does EclipseStore recognize these structures and can resolve or break the loops?
In this example, the graph consists of nodes of the GraphNode
class. This class contains an attribute of type String to store an ID. There is also a reference to the parent node and a list of child nodes. This means you can now create any nested chart you want.
In this case, the graphic looks like this.
GraphNode rootNode = new GraphNode("rootNode");
GraphNode child01Lev01 = new GraphNode("child01Lev01");
GraphNode child02Lev01 = new GraphNode("child02Lev01");
rootNode.addChildGraphNode(child01Lev01);
rootNode.addChildGraphNode(child02Lev01);
child01Lev01.setParent(rootNode);
child02Lev01.setParent(rootNode);
GraphNode child01Lev02 = new GraphNode("child01Lev02");
GraphNode child02Lev02 = new GraphNode("child02Lev02");
child01Lev01.addChildGraphNode(child01Lev02);
child01Lev01.addChildGraphNode(child02Lev02);
child01Lev02.setParent(child01Lev01);
child01Lev02.setParent(child01Lev01);
GraphNode child01Lev03 = new GraphNode("child01Lev03");
GraphNode child02Lev03 = new GraphNode("child02Lev03");
child01Lev03.setParent(child02Lev01);
child02Lev03.setParent(child02Lev01);
child02Lev01.addChildGraphNode(child01Lev03);
child02Lev01.addChildGraphNode(child02Lev03);
//creating cycles
rootNode.addChildGraphNode(child01Lev03);
rootNode.setParent(child02Lev03);
EmbeddedStorageManager storageManager = EmbeddedStorage.start();
storageManager.setRoot(rootNode);
storageManager.storeRoot();
The node rootNode
is passed to the StorageManager
as root and saved — subsequent loading of the root results in the previously created graph.
As we can see from this example, EclipseStore can store graphs without any problems. Cycles are acceptable here.
Conclusion
We have now seen that EclipseStore can store any data structure in its entirety; lists, trees and graphs are not a limitation. This allows us to create the data model independently of the persistence layer. This is a possibility that I have always been looking for in the last 20 years of my Java activities.
We will now deal with the intricacies of EclipseStore in the following parts. It remains exciting.
Happy coding
Sven
Published at DZone with permission of Sven Ruppert. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments