Don’t be shy, visualize your JPA model!
JPA 2 (2.0 actually) introduced Metamodel class. Even if still generally hidden in libraries this is a very nice addition allowing end users to code against the JPA graph without having to parse (again since JPA does it) the model.
It has a lot of usages for libraries but one nice feature you can create leveraging this is the visualization of your entity graph. This is something easy to add to your application and which could be generic if considered util (a bit like the h2 servlet we add to see the database in dev).
But wait, why would I need that? I can browse the object tree in my IDE, no? Yes and no. First you need to assume your IDE does and therefore in a team assume everyone has the right plugin or is able to quickly browse the model in the IDE. Can be true but still assume a lot and not that friendly.
Ok, but I can reverse the code or database to see that, no? Yes you can...assuming what is running is exactly what is there. For the database the model can have changed but we rarely completly the model for legacy applications for instance or just cause it would be too costly in term of SLA. Reversing the code has the same pitfall: do you assume all entities of the project/libraries are in the same persistence unit? Can be true...or not. So generally being able to check against the runtime is not a bad idea.
Ok so how do we start?
Build your own graph
First thing to do is to get an EntityManager. This is generally as simple as an injection in a class:
public class AEEService {
@PersistenceContext
private EntityManager em;
}
Or if you use CDI/Spring:
public class ANotReallyEEService {
@Inject
private EntityManager em;
}
Side note: if in the last case you don't manage to get an entity manager this way or with a qualifier, I would recommand you to ensure you can later on cause it is generally a criteria showing the application is not well set up.
Now we have the entity manager we need to get the metamodel. JPA did it the simple way:
Metamodel model = em.getMetamodel();
Ok so we have the model so we just need to convert it to something we can draw. The model is a graph (not a tree in general) so we need to represent a graph. There are multiple ways to do so but one easy is to represent a graph as a set of nodes and edges.
So let's create our domain:
// @Data is the lombok one to avoid boilerplate in the snippets
@Data
public class Node {
private final String name;
}
@Data
public class Edge {
private final String from;
private final String to;
private final String name;
}
@Data
public class Graph {
private final Collection<Node> nodes;
private final Collection<Edge> edges;
}
And finally we need to populate the model (Graph) from the metamodel. To do so we just browse the entities, add them all as node and finally browse the relationships (associations or collections depending if you are on the "1" or "n" side) and add these ones as edges:
public Graph from(final Metamodel jpa) {
return new Graph(
jpa.getEntities().stream()
.map(EntityType::getName)
.map(Node::new)
.collect(toList()),
jpa.getEntities().stream()
.map(this::visitEntity)
.flatMap(Collection::stream)
.collect(toSet()));
}
Don't be abused by the stream syntax, this is really just an iteration to extract entity names and relationships (in visitEntity). This last part is the hardest probably (but keep calm it is not a big headache). It needs to browse all attributes and extract the associations and collections. Then depending the type of relationship it needs to extract the right entity name (or basic if not an entity-entity relationship like a Map<String, String>):
private Set<Edge> visitEntity(final Type<?> type) {
if (!EntityType.class.isInstance(type)) {
return Collections.emptySet();
}
final EntityType<?> e = EntityType.class.cast(type);
return e.getDeclaredAttributes().stream()
.filter(a -> a.isCollection() || a.isAssociation())
.map(a -> {
// extract the relationship type
final Type aType = a.isCollection() ?
PluralAttribute.class.cast(a).getElementType() :
SingularAttribute.class.cast(a).getType();
// extract target type (alias of the entity if possible or java type)
final String to = EntityType.class.isInstance(aType) ?
EntityType.class.cast(aType).getName() :
aType.getJavaType().getName();
// build the edge finally
return new Edge(e.getName(), to, a.getName());
})
.collect(toSet());
}
With that code you have your own graph representation of the JPA model.
Visualize your graph
I'll skip the step to build a JAX-RS resource exposing this Graph object (was one of the reason why we didn't return directly the JPA metamodel, the behavior would be highly not portable, desired or expected).
Now I assume you exposed Graph as JSON, then you just need to import any javascript library able to build a graph. Personnally I like Dracula cause it is simple. All you need to do is to import raphael, dracula and map your Graph to Dracula graph;
<html>
<body>
<div id="canvas"></div>
<script src="https://code.jquery.com/jquery-3.1.1.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.2.1/raphael.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/graphdracula/1.0.3/dracula.min.js"></script>
<script>
(function () {
$.getJSON("api/model/jpa", function(graph) {
var g = new Dracula.Graph();
graph.nodes.forEach(function (node) {
g.addNode(node.name);
});
graph.edges.forEach(function (edge) {
g.addEdge(edge.from, edge.to, {directed: true});
});
var layouter = new Dracula.Layout.Spring(g);
layouter.layout();
var renderer = new Dracula.Renderer.Raphael(document.getElementById('canvas'), g, 400, 300);
renderer.draw();
});
})();
</script>
</body>
</html>
In previous snippet I imported jQuery too to do the ajax call but generally you will use your js framework (like Angular $http for instance). Then, once we have the graph, we just convert it to a Dracula.Graph and render it in a div of our choice (in real project we would also not select the div by id but it is highly dependent on the framework so for the snippet by id was fine).
For a blog, where a post has an author and some comments, it will show something like:
Of course this is just a start but I hope you will be tempted to investigate Metamodel API, it really does worth it.
On the solution presented in this post, we can add the following features which would make it a complete solution:
- on click on an entity you can show the list of attributes (not only relationships) with their type and highlight the identifier of each entity, everything is in the metamodel
- The graph can represent more nicely the direction of the relationships (this is the case in this sample but not really visible due to the default style)
- The graph could be a complete UML (or Merise) model
- An awesome feature you can do with JPA is to link this graph to queries to browse the model is a highly user friendly manner!
Today is the big data time and one challenge is to browse its data. If you think about it, JPA offers an awesome way to be able to browse your data and discover an unknown application and can maybe offers what our future API will be. Don't forget that whatever data you handle, you just need to ensure you can "see" it ;)!
From the same author:
In the same category: