Persistent entity model

Model your business domain using a standard persistent entity model with out-of-the-box default persistent entity implementations.

External resources

Overview

  • Project: daofusion-core
  • Package reference: com.anasoft.os.daofusion.entity

Introduction

Development of a DAO layer for a specific project usually starts out with the task of domain model definition. You can think of a domain model as a description of the business problem in terms of entities and their relations. Domain model definition phase usually comes after analyzing collected business requirements, depicted by notations such as UML.

In general, a domain model isn't something that gets done at the beginning and remains unchanged as the project progresses. This statement would be true if you're able to get a fully understood picture of your business requirements and ensure that these requirements will not change (in other words, an ideal project analysis and a customer who is willing to sacrifice the possibility to change his requirements). In reality, each and every project comes with an inherent unpredictability of requirements. One solution of dealing with ever changing requirements is to apply an agile methodology which adapts to changes as they come, developing your domain model in predictable iterations that are short enough to get a valuable feedback on the progress of your project.

Having the domain model defined and understood, the next step is to implement it in Java via persistent entities. Persistent entities are classes that implement entities of the business problem which are intended to be stored in the underlying database.

Each persistent entity follows a standard lifecycle as declared by the JPA specification:

  • After its creation, the entity starts out in the transient state. Being transient means that it is not managed within the persistence context yet. Transient instances have usually some properties set by the user before their persistence.
  • Persisting a transient entity results in associating the entity with the persistence context and storing it into the database, according to transaction boundaries and associated flush mode. From now on, the entity is being managed within the current persistence session - it's in the attached state.
  • When the associated persistence session ends or an entity gets evicted from the current session, it is treated as detached. A detached entity needs to be reattached to the current persistence context in order to use persistence services again.

Persistent entity model overview

DAO Fusion provides a standard persistent entity model with out-of-the-box default implementations for you to build on.

Persistable interface represents a generic persistent entity contract using a single primary key column type as the class parameter. This parameter is used by the PersistentEntityDao when querying for entities by their id's and is therefore mandatory. However, this doesn't mean that users cannot define custom primary keys in their entities - see the Persistable Javadoc for more information about simple and composite primary key approaches as well as other useful hints. As for the inheritance mapping strategy, it is up to the user to mark specific root nodes within the persistent entity hierarchy using the @Inheritance annotation as appropriate (keep in mind that the default JPA inheritance strategy is set to single table per class along with its implications on the underlying database schema).

PersistentEntity is an abstract implementation of the Persistable interface for an entity managed within the JPA persistence context. It defines an automatically-generated id field of the given type using the @Id and @GeneratedValue annotations. Note that the PersistentEntity does NOT provide any special hashCode / equals method implementation - this is a task left to be done by the user, depending on the chosen method implementation pattern. See the Persistable Javadoc for more information about the two basic implementation patterns and their comparison (business key versus synthetic generated value approach).

There are two specialized abstract classes derived from PersistentEntity which use Long as the primary key column type: MutablePersistentEntity and ImmutablePersistentEntity.

MutablePersistentEntity features the version-based optimistic locking strategy support via a version field and is generally recommended for standard mutable domain-specific objects.

ImmutablePersistentEntity is marked as Immutable so that any updates performed by Hibernate on this entity are silently ignored. You can use this entity class for domain-specific objects which are not allowed to be changed after their persistence.

Last but not least, PersistentEnumeration as an extension of the PersistentEntity using Long as the primary key column type is suitable for scenarios when you need to store dynamic enumerations within the database. PersistentEnumeration contains a name field with unique, not-updatable and not-null constraints which can be used by the PersistentEnumerationDao for string-based enumeration lookup. Unlike PersistentEntity, PersistentEnumeration provides the hashCode / equals method implementation based on the name field. Note that the PersistentEnumeration does not contain a version field - users can add this field in enumeration subclasses if they require version-based optimistic locking strategy support for their persistent enumerations.

PersistentEntity as well as its abstract subclasses support the clone operation implicitly - they contain a custom clone implementation that nullifies certain fields (id and version), without implementing the Cloneable interface on their own. PersistentEntity subclasses can therefore implement the Cloneable interface to indicate explicit clone support.

Implementing hashCode and equals via synthetic object id

Implementing hashCode / equals methods correctly can be very tricky and can lead to strange errors regarding the application behavior. As mentioned above, there are basically two approaches to their implementation. The following code demonstrates the synthetic generated value approach based on the java.util.UUID class:

@MappedSuperclass
public abstract class OidBasedMutablePersistentEntity extends MutablePersistentEntity {

    public static final int OID_COLUMN_LENGTH = 36;
    public static final String OID_COLUMN_NAME = "object_id";

    @NaturalId
    @Column(length = OID_COLUMN_LENGTH, name = OID_COLUMN_NAME,
        unique = true, updatable = false, nullable = false)
    private String oid;

    public OidBasedMutablePersistentEntity() {
        oid = generateOid();
    }

    public String getOid() {
        return oid;
    }

    protected void setOid(String oid) {
        this.oid = oid;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((oid == null) ? 0 : oid.hashCode());
        return result;
    }

    @Override
    public final boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (!(obj instanceof OidBasedMutablePersistentEntity)) {
            return false;
        }

        OidBasedMutablePersistentEntity other = (OidBasedMutablePersistentEntity) obj;

        return (oid == null) ? false : oid.equals(other.oid);
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        OidBasedMutablePersistentEntity clone = OidBasedMutablePersistentEntity.class.cast(super.clone());
        clone.oid = generateOid();
        return clone;
    }

    protected String generateOid() {
        return UUID.randomUUID().toString();
    }

}

Note that the oid field setter is marked as protected to avoid direct object identity manipulation.

Sample persistent entity

The following code shows a sample persistent entity, extending the OidBasedMutablePersistentEntity class as shown above:

@Entity
@Table(name = "orders")
public class Order extends OidBasedMutablePersistentEntity {

    @Column(nullable = false, updatable = false)
    @Temporal(TemporalType.TIMESTAMP)
    private Date creationDate;

    @Embedded
    private Address shippingAddress;

    @OneToMany(mappedBy = "order")
    @Cascade(value = { CascadeType.SAVE_UPDATE })
    private List<OrderItem> orderItems = new ArrayList<OrderItem>();

    @Column(nullable = false)
    private Boolean complete;

    @ManyToOne
    @JoinColumn(nullable = false, updatable = false)
    private Customer customer;

    protected List<OrderItem> getOrderItems() {
        return orderItems;
    }

    protected void setOrderItems(List<OrderItem> orderItems) {
        this.orderItems = orderItems;
    }

    public void addOrderItem(OrderItem orderItem) {
        orderItems.add(orderItem);
        orderItem.setOrder(this);
    }

    public void removeOrderItem(OrderItem orderItem) {
        orderItems.remove(orderItem);
        orderItem.setOrder(null);
    }

    public List<OrderItem> getUnmodifiableOrderItemList() {
        return Collections.unmodifiableList(orderItems);
    }

    // rest of the getters and setters go here

    public int getTotalPrice() {
        int result = 0;

        for (OrderItem orderItem : orderItems) {
            result += orderItem.getTotalPrice();
        }

        return result;
    }

}

As you can see, there is nothing special happening here except that we are extending the OidBasedMutablePersistentEntity class which implements the synthetic generated value approach.

Note that the getter and setter for the orderItems field are marked as protected to avoid direct list manipulation - @OneToMany annotation defines a 1:N bidirectional relationship between Order and OrderItem classes. Instead, there are two public methods which handle the bidirectional relationship properly and are intended to be used for the orderItems list manipulation (addOrderItem and removeOrderItem). The getUnmodifiableOrderItemList method returns an unmodifiable list of OrderItem instances for inspecting orderItems list values in a read-only fashion.

Sample persistent enumeration

The following code shows a sample persistent enumeration:

@Entity
@Table(name = "payment_types")
public class PaymentType extends PersistentEnumeration {

    // place some custom code here

}

You could add an extra description field to the enumeration class but basically that's all about it. Don't forget to call the setName method on your persistent enumerations before saving them into the database.

Implementing clone

The following code shows how to implement the clone operation on a persistent entity:

@Entity
@Table(name = "orders")
public class Order extends OidBasedMutablePersistentEntity implements Cloneable {

    @Column
    private String description;

    @Column(nullable = false)
    private Boolean complete;

    @Column(nullable = false, updatable = false)
    @Temporal(TemporalType.TIMESTAMP)
    private Date creationDate;

    @Embedded
    private Address shippingAddress;

    @ManyToOne
    @JoinColumn(nullable = false, updatable = false)
    private Customer customer;

    // getters and setters go here

    @Override
    protected Object clone() {
        try {
            // performs a shallow (bit-by-bit) copy of this object,
            // taking care of primitive fields and immutable objects
            Order clone = Order.class.cast(super.clone());

            // mutable objects need to be deep-copied (cloned)
            clone.creationDate = Date.class.cast(this.creationDate.clone());
            clone.shippingAddress = Address.class.cast(this.shippingAddress.clone());
            clone.customer = Customer.class.cast(this.customer.clone());

            return clone;
        }

        catch (CloneNotSupportedException e) {
            // shouldn't happen if all mutable objects are cloneable
            throw new Error(e);
        }
    }

}

When a user calls the clone method on our sample entity, following actions are taken:

  • PersistentEntity's clone method ensures that the id field gets nullified after the shallow copy
  • MutablePersistentEntity's clone method ensures that the version field gets nullified after the shallow copy
  • OidBasedMutablePersistentEntity's clone method ensures that the oid field is re-generated after the shallow copy
  • Order's clone method ensures that all references to mutable objects are cloned after the shallow copy

The bottom line is that all persistent entity copies (clones) are inherently treated as transient. Cloning a persistent entity therefore represents an alternative to calling its constructor and creating a deep structure copy of this entity. This also means that all mutable objects referenced by the entity must implement the Cloneable interface as well.

Using composite primary keys

The following code shows how to use composite primary keys in persistent entities (hashCode / equals methods are implemented via the business key approach):

@MappedSuperclass
@Entity(optimisticLock = OptimisticLockType.VERSION)
public abstract class CompositeKeyBasedMutablePersistentEntity<ID extends Serializable> implements Persistable<ID> {

    @EmbeddedId
    private ID id;

    @Version
    private Integer version;

    // hashCode / equals method implementation is based on the composite
    // primary key (ID), which should be set by the entity constructor
    // (default no-argument constructor must NOT be public)

    public CompositeKeyBasedMutablePersistentEntity(ID id) {
        this.id = id;
    }

    public ID getId() {
        return id;
    }

    protected void setId(ID id) {
        this.id = id;
    }

    public Integer getVersion() {
        return version;
    }

    protected void setVersion(Integer version) {
        this.version = version;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((id == null) ? 0 : id.hashCode());
        return result;
    }

    @Override
    public final boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (!(obj instanceof CompositeKeyBasedMutablePersistentEntity)) {
            return false;
        }

        final CompositeKeyBasedMutablePersistentEntity<?> other = (CompositeKeyBasedMutablePersistentEntity<?>) obj;

        return (id == null) ? false : id.equals(other.id);
    }

}

@Embeddable
public class CustomerPk implements Serializable {

    @Column(length = 64)
    private String name;

    @Basic
    @Temporal(TemporalType.TIMESTAMP)
    private Date dateOfBirth;

    // default no-argument constructor required by Hibernate
    protected CustomerPk() {
        this(null, null);
    }

    public CustomerPk(String name, Date dateOfBirth) {
        this.name = name;
        this.dateOfBirth = dateOfBirth;
    }

    // getters and setters go here

    // hashCode / equals method implementation based
    // on "name" and "dateOfBirth" goes here

}

@Entity
@Table(name = "customers")
public class Customer extends CompositeKeyBasedMutablePersistentEntity<CustomerPk> {

    @Column(nullable = false)
    private String contactEmail;

    // default no-argument constructor required by Hibernate
    protected Customer() {
        this(null);
    }

    public Customer(CustomerPk id) {
        super(id);
    }

    // getters and setters go here

}

Implementing hashCode / equals methods via the business key approach imposes certain limitations on "semi"-unique attributes of the business key. The reason behind these limitations lies in the fact that the business key essentially combines two orthogonal concepts: object identity and object state. Uniqueness of the business key might be broken if any of the "semi"-unique attributes change. Additionally, a persistent entity instance has to have all "semi"-unique attributes set in advance (e.g. within its constructor) for hashCode / equals methods to work properly. Synthetic generated value approach, on the other hand, tries to separate object identity and object value concepts from each other by adding an auto-generated object identity field.