JPA DAO Guidelines

Managed Object Design

Objects persisted via JPA are generally referred to as Managed Objects. In uPortal all domain objects are first defined by a public interface and then a concrete implementation with the JPA annotations is created. An example interface and implementation are included at the bottom of this section.

These guidelines must be used when creating Managed Objects in uPortal

  1. Define the domain object methods in an Interface
  2. The domain object should be data focused, it must not also encapsulate buisness logic.
  3. The implementation should be package private and exist in the same package as the DAO implementation and the DAO implementation should be the only class ever creating new instances of the managed object.
  4. A surrogate key must be defined as the primary @Id for the object, business keys must never be used as the primary @Id.
  5. An immutable natural key shouldbe defined. This is one or more fields that make up a unique business identity for the object.
    • These fields should be annotated with @NaturalId to gain significant performance improvements when querying using the natural id fields.
    • These fields should be marked as final and passed in via the constructor
  6. All nullable=false fields must be passed in via the constructor to prevent invalid object creation and should be marked as final
  7. A private no-arg constructor is required to allow Hibernate to create the object using reflection. This constructor is private to protect against invalid object creation.
  8. Both @TableGenerator and @SequenceGenerator annotations should be used to define the generator for the @GeneratedValue annotation. This allows for the maximum flexibility and consistency across databases.
  9. equals and hashCode must be implemented but must only ever include the fields that make up the unique business identity of the object.
    • For objects defined by an interface all efforts should be made to allow two different implementations of the same interface to be equal to each other
  10. toString should be implemented and should as much information as is needed to easily identify the object but not too much that it clutters log files.
  11. Objects should define a version field annotated with @Version to support optimistic locking and data-integrity checks
Managed Object Examples
 IPortletType.java
public interface IPortletType {
    /**
     * Get the unique ID of this channel type.
     * 
     * @return unique id
     */
    public int getId();

    /**
     * Get the name of this channel type
     * 
     * @return channel type name
     */
    public String getName();

    /**
     * Get a description of this channel type
     * 
     * @return channel type description
     */
    public String getDescription();

    /**
     * Get the URI of the ChannelPublishingDocument associated with this
     * channel type.  This CPD will be used to determine configuration options 
     * for channels of this type.
     * 
     * @return ChannelPublishingDocument URI
     */
    public String getCpdUri();

    
    // Setter methods
    
    /**
     * Set the description for this channel type
     * 
     * @param description
     */
    public void setDescription(String descr);

    /**
     * Set the URI of the ChannelPublishingDocument associated with this channel
     * type.  This CPD will be used to determine configuration options for 
     * channels of this type.
     * 
     * @param ChannelPublishingDocument URI
     */
    public void setCpdUri(String cpdUri);
}
 PortletTypeImpl.java
@Entity
@Table(name = "UP_PORTLET_TYPE")
@SequenceGenerator(
        name="UP_PORTLET_TYPE_GEN",
        sequenceName="UP_PORTLET_TYPE_SEQ",
        allocationSize=1
    )
@TableGenerator(
        name="UP_PORTLET_TYPE_GEN",
        pkColumnValue="UP_PORTLET_TYPE",
        allocationSize=1
    )
@NaturalIdCache
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class PortletTypeImpl implements Serializable, IPortletType {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(generator = "UP_PORTLET_TYPE_GEN")
    @Column(name = "TYPE_ID")
    private final int internalId;
    
    @Version
    @Column(name = "ENTITY_VERSION")
    private final long entityVersion;
    
    //Hidden reference to the child portlet definitions, used to allow cascading deletes where when a portlet type is deleted all associated definitions are also deleted
    //MUST BE LAZY FETCH, this set should never actually be populated at runtime or performance will be TERRIBLE
    @OneToMany(mappedBy = "portletType", targetEntity = PortletDefinitionImpl.class, cascade = { CascadeType.ALL }, fetch = FetchType.LAZY, orphanRemoval = true)
    private transient Set<IPortletDefinition> portletDefinitions = null;

    @NaturalId
    @Column(name = "TYPE_NAME", length = 70)
    private final String name;
    @Column(name = "TYPE_DESCR", length = 2000)
    private String descr;
    @Column(name = "TYPE_DEF_URI", length = 255, nullable = false)
    private String cpdUri;
    
    /**
     * Default constructor used by hibernate
     */
    @SuppressWarnings("unused")
    private PortletTypeImpl() {
        this.internalId = -1;
        this.entityVersion = -1;
        this.name = null;
    }
    
    public PortletTypeImpl(String name, String cpdUri) {
        this.internalId = -1;
        this.entityVersion = -1;
        this.name = name;
        this.cpdUri = cpdUri;
    }

    @Override
    public int getId() {
        return this.internalId;
    }
    @Override
    public String getName() {
        return name;
    }
    @Override
    public String getDescription() {
        return descr;
    }
    @Override
    public String getCpdUri() {
        return cpdUri;
    }
    @Override
    public void setDescription(String descr) {
        this.descr = descr;
    }
    @Override
    public void setCpdUri(String cpdUri) {
        this.cpdUri = cpdUri;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (!(obj instanceof IPortletType))
            return false;
        IPortletType other = (IPortletType) obj;
        if (this.name == null) {
            if (other.getName() != null)
                return false;
        }
        else if (!this.name.equals(other.getName()))
            return false;
        return true;
    }
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((this.name == null) ? 0 : this.name.hashCode());
        return result;
    }
    @Override
    public String toString() {
        return "PortletTypeImpl [internalId=" + this.internalId + ", name=" + this.name + ", descr=" + this.descr
                + ", cpdUri=" + this.cpdUri + "]";
    }

    @Override
    public String getDataId() {
        return Integer.toString(getId());
    }

    @Override
    public String getDataTitle() {
        return name;
    }

    @Override
    public String getDataDescription() {
        return descr;
    }
}

JPA DAO Design

JPA DAOs are generally not that complex CRUD style data access objects. There a few design patterns that must be followed for all uPortal JPA DAOs.

  1. All JPA DAO must extend from BasePortalJpaDao. This class provides common functionality for building and executing queries that should be used by all JPA DAOs.
  2. The DAO must have a create method that takes in as parameters all the data required to create an persist the entity. This method will call the package-private constructor of the managed object and then must call entityManager.persist() before returning the managed object. Unattached JPA Managed Objects should never exist in uPortal.
  3. Wherever possible the DAO should use the CriteriaBuilder API and the JPA MetaModel. This ensures that later refactoring doesn't break queries.
JPA DAO Examples
 IPortletTypeDao.java
public interface IPortletTypeDao {
    /**
     * Creates, initializes and persists a new {@link IPortletType} based on the specified parameters
     * 
     * @param name The name of the channel type
     * @param cpdUri The URI to the CPD file used when publishing channels of this type
     * 
     * @return A newly created, initialized and persisted {@link IPortletType}
     * @throws org.springframework.dao.DataIntegrityViolationException If a IChannelType already exists for the provide arguments
     * @throws IllegalArgumentException If any of the parameters are null
     */
    public IPortletType createPortletType(String name, String cpdUri);
    
    /**
     * Persists changes to a {@link IPortletType}.
     * 
     * @param type The channel type to store the changes for
     * @throws IllegalArgumentException if type is null.
     */
    public IPortletType updatePortletType(IPortletType type);
    
    /**
     * Removes the specified {@link IPortletType} from the persistent store.
     * 
     * @param type The type to remove.
     * @throws IllegalArgumentException if type is null.
     */
    public void deletePortletType(IPortletType type);
    
    /**
     * Get a {@link IPortletType} for the specified id.
     * 
     * @param id The id to get the type for.
     * @return The channel type for the id, null if no type exists for the id.
     */
    public IPortletType getPortletType(int id);
    
    /**
     * Get a {@link IPortletType} for the specified name
     * 
     * @param name The name to get the type for.
     * @return The channel type for the name, null if no type exists for the fname.
     * @throws IllegalArgumentException if name is null.
     */
    public IPortletType getPortletType(String name);
    
    /**
     * @return A {@link List} of all persisted {@link IPortletType}s
     */
    public List<IPortletType> getPortletTypes();

}
 JpaPortletTypeDao.java
@Repository
public class JpaPortletTypeDao extends BasePortalJpaDao implements IPortletTypeDao {
    private CriteriaQuery<PortletTypeImpl> findAllTypesQuery;
    @Override
    public void afterPropertiesSet() throws Exception {
        this.findAllTypesQuery = this.createCriteriaQuery(new Function<CriteriaBuilder, CriteriaQuery<PortletTypeImpl>>() {
            @Override
            public CriteriaQuery<PortletTypeImpl> apply(CriteriaBuilder cb) {
                final CriteriaQuery<PortletTypeImpl> criteriaQuery = cb.createQuery(PortletTypeImpl.class);
                criteriaQuery.from(PortletTypeImpl.class);
                return criteriaQuery;
            }
        });
    }
    
    
    @Override
    @PortalTransactional
    public void deletePortletType(IPortletType type) {
        Validate.notNull(type, "definition can not be null");
        
        final IPortletType persistentChanneltype;
        final EntityManager entityManager = this.getEntityManager();
        if (entityManager.contains(type)) {
            persistentChanneltype = type;
        }
        else {
            persistentChanneltype = entityManager.merge(type);
        }
        entityManager.remove(persistentChanneltype);
    }
    @Override
    @PortalTransactional
    public IPortletType createPortletType(String name, String cpdUri) {
        Validate.notEmpty(name, "name can not be null");
        Validate.notEmpty(cpdUri, "cpdUri can not be null");
        
        final PortletTypeImpl channelType = new PortletTypeImpl(name, cpdUri);
        
        this.getEntityManager().persist(channelType);
        
        return channelType;
    }
    @Override
    public IPortletType getPortletType(int id) {
        return this.getEntityManager().find(PortletTypeImpl.class, id);
    }
    @OpenEntityManager(unitName = PERSISTENCE_UNIT_NAME)
    @Override
    public IPortletType getPortletType(String name) {
        final NaturalIdQuery<PortletTypeImpl> query = this.createNaturalIdQuery(PortletTypeImpl.class);
        query.using(PortletTypeImpl_.name, name);
        return query.load();
    }
    @Override
    public List<IPortletType> getPortletTypes() {
        final TypedQuery<PortletTypeImpl> query = this.createCachedQuery(this.findAllTypesQuery);
        final List<PortletTypeImpl> portletTypes = query.getResultList();
        return new ArrayList<IPortletType>(portletTypes);
    }
    @Override
    @PortalTransactional
    public IPortletType updatePortletType(IPortletType type) {
        Validate.notNull(type, "type can not be null");
        
        this.getEntityManager().persist(type);
        
        return type;
    }
}