Rajesh has posted 2 posts at DZone. View Full User Profile

Manage Hierarchical Data using Spring, JPA and Aspects

10.21.2010
| 6544 views |
  • submit to reddit

Managing hierarchical data using two dimentional tables is a pain. There are some patterns to reduce this pain. One such solution is described here. This article is about implementing the same using Spring, JPA, Annotations and Aspects. Please go through follow the link to better understand this solution described. The purpose is to come up with a component that will remove the boiler-plate code in the business layer to handle hierarchical data.
Summary

  • Create a base class for Entities used to represent Hierarchical data
  • Create annotation classes
  • Code the Aspect that will execute addional steps for managing Hierarchical data. (Heart of the solution)
  • Now the Aspect can be used everywhere Hierarchical data is used.

Detail
  • Create base class for Entities used to represent Hierarchical data.
    The purpose of the super class is to encapsulate all the common attrubutes and operations required for managing hierarchical data in a table. Please note that the class is annotated as @MappedSuperclass.

    The methods are meant to generate queries required to perform CRUD operations on the Table. Their use will be more clear later in the article when we will revisit HierarchicalEntity.

    Now any Entity that extends this class will have all the attributes required to manage hierarchical data.
    import com.es.clms.aspect.HierarchicalEntity;
    import javax.persistence.EntityListeners;
    import javax.persistence.MappedSuperclass;

    @MappedSuperclass
    @EntityListeners({HierarchicalEntity.class})
    public abstract class AbstractHierarchyEntity
    implements Serializable {

    protected Long parentId;
    protected Long lft;
    protected Long rgt;

    public String getMaxRightQuery() {
    return "Select max(e.rgt) from " + this.getClass().getName() + " e";
    }

    public String getQueryForParentRight() {
    return "Select e.rgt from " + this.getClass().getName()
    + " e where e.id = ?1";
    }

    public String getDeleteStmt() {
    return "Delete from " + this.getClass().getName()
    + " e Where e.lft between ?1 and ?2";
    }

    public String getUpdateStmtForFirst() {
    return "Update " + this.getClass().getName()
    + " e set e.lft = e.lft + ?2 Where e.lft >= ?1";
    }

    public String getUpdateStmtForRight() {
    return "Update " + this.getClass().getName()
    + " e set e.rgt = e.rgt + ?2 Where e.rgt >= ?1";
    }
    .
    .
    .//Getter and setters for all the attributes.
    }
  • Create annotation classes
    The following is an annotation class that will be used to annotate the methods that perform CRUD operations on hierarchical data. It is followed by an enum that will decide the type of CRUD operation to be performed.

    These classes will make more sense after the next section.


    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface HierarchicalOperation {
    HierarchicalOperationType operationType();
    }

    /**
    * Enum - Type of CRUD operation.
    */

    public enum HierarchicalOperationType {
    SAVE, DELETE;
    }


  • Code the Aspect that will execute addional steps for managing Hierarchical data. HierarchicalEntity is an aspect that performs the additional logic required to manage the hierarchical data as descriped in the article here.

    This is the first time I have used an Aspect, therefore I am sure that there are better ways to do this. Those of you, who are good at it, please improve this part of code.

    This class is annotated as @Aspect. The pointcut will intercept any method anotated with HierarchicalOperation and has a input of type AbstractHierarchyEntity. A sample its usage is in next section.

    operation method is annotated to be executed before the pointcut. Based on the HierarchicalOperationType passed, this method will either execute the additional tasks required to save or delete the hierarchical record. This is where the methods defined in AbstractHierarchyEntity for generating JPA Queries are used.

    GenericDAOHelper is a utility class for using JPA.


    import com.es.clms.annotation.HierarchicalOperation;
    import com.es.clms.annotation.HierarchicalOperationType;
    import com.es.clms.common.GenericDAOHelper;
    import com.es.clms.model.AbstractHierarchyEntity;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Service;
    import org.springframework.beans.factory.annotation.Autowired;

    @Aspect
    @Service("hierarchicalEntity")
    public class HierarchicalEntity {

    @Autowired
    private GenericDAOHelper genericDAOHelper;

    @Pointcut(value = "execution(@com.es.clms.annotation.HierarchicalOperation * *(..)) "
    + "&& args(AbstractHierarchyEntity)")
    private void hierarchicalOps() {
    }

    /**
    *
    * @param jp
    * @param hierarchicalOperation
    */
    @Before("hierarchicalOps() && @annotation(hierarchicalOperation) ")
    public void operation(final JoinPoint jp,
    final HierarchicalOperation hierarchicalOperation) {
    if (jp.getArgs().length != 1) {
    throw new IllegalArgumentException(
    "Expecting only one parameter of type AbstractHierarchyEntity in "
    + jp.getSignature());
    }
    if (HierarchicalOperationType.SAVE.equals(
    hierarchicalOperation.operationType())) {
    save(jp);
    } else if (HierarchicalOperationType.DELETE.equals(
    hierarchicalOperation.operationType())) {
    delete(jp);
    }
    }

    /**
    *
    * @param jp
    */
    private void save(JoinPoint jp) {

    AbstractHierarchyEntity entity =
    (AbstractHierarchyEntity) jp.getArgs()[0];
    if (entity == null)
    return;
    if (entity.getParentId() == null) {
    Long maxRight = (Long) genericDAOHelper.executeSingleResultQuery(
    entity.getMaxRightQuery());

    if (maxRight == null) {
    maxRight = 0L;
    }
    entity.setLft(maxRight + 1);
    entity.setRgt(maxRight + 2);
    } else {
    Long parentRight = (Long) genericDAOHelper.executeSingleResultQuery(
    entity.getQueryForParentRight(), entity.getParentId());

    entity.setLft(parentRight);
    entity.setRgt(parentRight + 1);

    genericDAOHelper.executeUpdate(
    entity.getUpdateStmtForFirst(), parentRight, 2L);
    genericDAOHelper.executeUpdate(
    entity.getUpdateStmtForRight(), parentRight, 2L);
    }
    }

    /**
    *
    * @param jp
    */
    private void delete(JoinPoint jp) {

    AbstractHierarchyEntity entity =
    (AbstractHierarchyEntity) jp.getArgs()[0];

    genericDAOHelper.executeUpdate(
    entity.getDeleteStmt(), entity.getLft(), entity.getRgt());

    Long width = (entity.getRgt() - entity.getLft()) + 1;

    genericDAOHelper.executeUpdate(
    entity.getUpdateStmtForFirst(), entity.getRgt(), width * (-1));
    genericDAOHelper.executeUpdate(
    entity.getUpdateStmtForRight(), entity.getRgt(), width * (-1));
    }
    }

  • Sample Usage
    From this point on you don't have to worry about the additional tasks required for managing the data. Just use the HierarchicalOperation anotation with appropriate HierarchicalOperationType.

    Below is a sample use of the code developed so far.


    @HierarchicalOperation(operationType = HierarchicalOperationType.SAVE)
    public long save(VariableGroup group) {

    entityManager.persist(group);
    return group.getId();
    }


    @HierarchicalOperation(operationType = HierarchicalOperationType.DELETE)
    public void delete(VariableGroup group) {
    entityManager.remove(entityManager.merge(group));
    }



    http://rajeshkilango.blogspot.com/2010/10/manage-hierarchical-data-using-spring.html
Published at DZone with permission of its author, Rajesh Ilango.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags: