We define a JPA entity Division in accordance with the adjacency list model:
@Entity(name = "Division") @Table(name = "DIVISION") @EntityListeners( { HierarchyListener.class } ) public class Division implements IHierarchyElement { ... @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "global_id_gen") @SequenceGenerator(name = "global_id_gen", sequenceName = "GLOBAL_ID_GEN") @Column(name = "ID") public Long getId() { return id; } @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "PARENT_ID") public Division getParent() { return parent; } @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "ORG_ID", nullable = false) public Organization getOrganization() { return organization; } @Basic(optional = false) @Column(name = "LEVEL", nullable = false) public Short getLevel() { return level; } @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "TOP_ID") public Division getTop() { return top; } public void setLevel(Short theLevel) { level = theLevel; } public void setTop(IHierarchyElement theTop) { top = (Division) theTop; } ... }
for the following table (PostgreSQL flavor):
CREATE TABLE division ( id INTEGER DEFAULT nextval('global_id_gen') PRIMARY KEY, name VARCHAR(128) NOT NULL, org_id INTEGER NOT NULL, parent_id INTEGER, top_id INTEGER NOT NULL, level SMALLINT NOT NULL DEFAULT 0, UNIQUE (name, org_id), CONSTRAINT div_org_fk FOREIGN KEY(org_id) REFERENCES organization(id), CONSTRAINT div_parent_fk FOREIGN KEY(parent_id) REFERENCES division(id), CONSTRAINT div_top_fk FOREIGN KEY(top_id) REFERENCES division(id), CHECK ( (parent_id IS NULL AND level = 0 AND top_id = id) OR (parent_id IS NOT NULL AND level > 0 AND top_id <> id) ) )The hierarchical data consists of divisions per organization. Organization may have multiple top divisions. That's why there is an ORG_ID that points to organization this division belongs to. Thus, organization entity serves as a top of the tree of divisions. Table DIVISION uses PARENT_ID column to reference its parent division - so PARENT_ID is nullable (in case of top division whose parent is an organization).
Last piece to the puzzle is the purpose of columns LEVEL and TOP_ID. They are our optimization additions to adjacency list model. LEVEL is an integer that stores level of division in hierarchy: top division (with no parent division and parent organization) has level 0; its children have level 1 and so on. TOP_ID always references top-most division in the hierarchy for its division. If division is top-most (its parent is an organization) then it references itself.
What kind of optimization do we get with LEVEL and TOP_ID? They are not necessary for adjacency list model as PARENT_ID is sufficient. But having more information about tree stored with the data we have more flexibility and easier job in manipulating or reporting on hierarchical data. Thus, I can extract whole sub-tree for any top-most division or answer questions about hierarchy depth using LEVEL with no expensive cursors. It could be that you come up with optimization of your own depending on specific needs.
Back to entity definition in Java. The interface IHierarchyElement is all any hierarchical entity needs to implement and support hierarchical data:
public interface IHierarchyElement { IHierarchyElement getParent(); Short getLevel(); void setLevel(Short level); IHierarchyElement getTop(); void setTop(IHierarchyElement top); }
And this is all to it to have an entity support hierarchical model in the relational database. Now we can use one of standard JPA features - callback lifecycle listener class (lookup its usage in the annotations to Division entity above) to maintain data for adjacency list model (for illustration purpose code below is stripped of all error handling):
public class HierarchyListener { @PrePersist @PreUpdate public void setLevelAndTop(IHierarchyElement entity) { final IHierarchyElement parent = entity.getParent(); // set level if (parent == null) { entity.setLevel((short) 0); } else { entity.setLevel((short) (parent.getLevel() + 1)); } // set top if (parent == null) { entity.setTop(entity); } else { entity.setTop(parent.getTop()); } } }
This pattern is a combination of specialized interface IHierarchyElement and a callback HierarchyListener class. Using them our goal is to implicitly maintain hierarchical data for JPA entities. When a hierarchical data is shared between many entities in domain model we can claim that this pattern factors out hierarchical aspect of data to focus on business-related semantic in the domain model.
2 comments:
Can you please share the code in detail?
Thanks in advance
This waas a lovely blog post
Post a Comment