DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Solving Unique Search Requirements Using TreeMap Data Structure
  • Unlocking the Potential of Binary Search Trees with C# Programming
  • Mule 4 DataWeave(1.x) Script To Resolve Wildcard Dynamically
  • Mule 3 DataWeave(1.x) Script To Resolve Wildcard Dynamically

Trending

  • Beyond Simple Responses: Building Truly Conversational LLM Chatbots
  • Agentic AI for Automated Application Security and Vulnerability Management
  • A Deep Dive Into Firmware Over the Air for IoT Devices
  • Start Coding With Google Cloud Workstations
  1. DZone
  2. Data Engineering
  3. Data
  4. EclipseStore: Storing More Complex Data Structures

EclipseStore: Storing More Complex Data Structures

How to store complex data structures using EclipseStore? Are there any restrictions? We will explore the possibilities now!

By 
Sven Ruppert user avatar
Sven Ruppert
DZone Core CORE ·
Oct. 09, 23 · Tutorial
Likes (1)
Comment
Save
Tweet
Share
3.4K Views

Join the DZone community and get the full member experience.

Join For Free

In 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.

Java
 
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.

Java
 
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.

Java
 
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.

Java
 
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.

Java
 
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.

Java
 
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.

StorageManager

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

Data structure Data (computing) Strings Tree (data structure)

Published at DZone with permission of Sven Ruppert. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Solving Unique Search Requirements Using TreeMap Data Structure
  • Unlocking the Potential of Binary Search Trees with C# Programming
  • Mule 4 DataWeave(1.x) Script To Resolve Wildcard Dynamically
  • Mule 3 DataWeave(1.x) Script To Resolve Wildcard Dynamically

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends: