Google

Sep 13, 2013

Defining Hibernate entities with auditable fields, soft delete field, optimistic locking field, etc

There are certain generic columns that database tables have like
  1. Auto generated identity columns.
  2. Auditing columns like creadedDtTm, createdBy, modifiedDtTm, and modifiedBy.
  3. Soft delete or logical delete flags like inactive 'Y' or 'N'.
  4. Optimistic locking detection based on columns like Timestamp or version number.
These logic can be mapped in a generic way using hibernate so that all the other entities can reuse. Let's look at some sample code using Hibernate annotations.

1. Identity columns.

package com.myapp.common;

import static javax.persistence.GenerationType.IDENTITY;

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;

@SuppressWarnings("serial")
@MappedSuperclass
public class StandardIdLongBaseEntity  extends AuditableBaseEntity<long>
{
    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "ID", unique = true, nullable = false, precision = 18, scale = 0) // name is almost always overridden
    @Override
    public Long getId() {
        return super.getId();
    }
}


2. Audit fields and optimistic locking detection using timestamp. Firstly the "MappedSuperclass"

package com.myapp.common;

import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.MappedSuperclass;
import javax.persistence.Version;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.hibernate.annotations.Generated;
import org.hibernate.annotations.GenerationTime;
import org.hibernate.annotations.TypeDef;
import org.hibernate.annotations.TypeDefs;

@SuppressWarnings("serial")
@MappedSuperclass
public abstract class AuditableBaseEntity<pk> extends BaseEntity<pk>
{
    private byte[] timestamp;
 private StandardAuditFields auditFields = new StandardAuditFields();

    @Version    
    @Column(name = "Timestamp", insertable = false, updatable = false, unique = true, nullable = false)
    @Generated(GenerationTime.ALWAYS)
 public byte[] getTimestamp() {
  return timestamp;
 }


 public void setTimestamp(byte[] timestamp) {
  this.timestamp = timestamp;
 }
 
 @Embedded
 public StandardAuditFields getAuditFields() {
     if (auditFields == null)
     {
         // TODO: Not thread safe. 
         auditFields = new StandardAuditFields();
     }
  return auditFields;
 }

 public void setAuditFields(StandardAuditFields auditFields) {
  this.auditFields = auditFields;
 }

    @Override
    public String toString()
    {
        return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE, true);
    }
}


Next, the "Embeddable" class with fields for auditing.


package com.myapp.common;

import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.hibernate.annotations.Generated;
import org.hibernate.annotations.GenerationTime;

@Embeddable
public class StandardAuditFields {
 private String createdBy;
 private Date createdDtTm;
    private Date modifiedDtTm;
    private String modifiedBy;

   
 @Temporal(TemporalType.TIMESTAMP)
 @Generated(GenerationTime.INSERT)
    @Column(name="CreatedDtTm", length=23, insertable=true, updatable=false)
 public Date getCreatedDtTm() {
  return createdDtTm;
 }

 public void setCreatedDtTm(Date createdDtTm) {
  this.createdDtTm = createdDtTm;
 }

 @Temporal(TemporalType.TIMESTAMP)
 @Generated(GenerationTime.ALWAYS)
    @Column(name="ModifiedDtTm", length=23, insertable=false, updatable=false)
 public Date getModifiedDtTm() {
  return modifiedDtTm;
 }

 public void setModifiedDtTm(Date modifiedDtTm) {
  this.modifiedDtTm = modifiedDtTm;
 }

 @Column(name="ModifiedBy", length=30, insertable=false, updatable=false)
 public String getModifiedBy() {
  return modifiedBy;
 }

 public void setModifiedBy(String modifiedBy) {
  this.modifiedBy = modifiedBy;
 }

 @Override
 public String toString()
 {
     return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
 }
}

3. Mapping soft delete.

package com.myapp.common;

import javax.persistence.Column;
import javax.persistence.MappedSuperclass;

import org.hibernate.annotations.Type;
import org.hibernate.annotations.Where;

@SuppressWarnings("serial")
@MappedSuperclass
@Where(clause = "inactiveFlag = 'N'")
public class BaseSoftDeleteableLongEntity extends StandardIdLongBaseEntity
{
    private boolean isSoftDeleted;
    
    @Column(name = "InactiveFlag", nullable = false, length = 1)
    @Type(type = "yes_no")
    public boolean isSoftDeleted()
    {
        return isSoftDeleted;
    }
    
    public void setSoftDeleted(boolean isSoftDeleted)
    {
        this.isSoftDeleted = isSoftDeleted;
    }
   
}

4 Finally, defining your entity.

package com.myapp.batch;

import com.google.common.base.Objects;
import java.util.Date;

import javax.persistence.AttributeOverride;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Table;
import javax.persistence.Transient;

import org.apache.commons.lang.StringUtils;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.hibernate.annotations.TypeDefs;
import org.hibernate.annotations.Where;

@Entity
@Table(name = "Tbl_Batch_Run", schema = "dbo", catalog = "sne_kath")
@AttributeOverride(name = "id", column = @Column(name = "Tbl_Batch_RunId"))
@Where(clause = "inactiveFlag = 'N'")
@SQLDelete(sql = "UPDATE Tbl_Batch_Run set inactiveFlag = 'Y' WHERE Tbl_Batch_RunId = ? and timestamp = ?") // optimistic locking
@JsonIgnoreProperties(
{
    "auditFields", "id", "softDeleted"})
@TypeDefs(
{@TypeDef(name = "BatchTypeCd", typeClass = BatchTypeCdType.class)}) // user defined types
public class BatchRun extends BaseSoftDeleteableLongEntity
{
    
    private String entity;
    private long batchId;
    private BatchTypeCd batchTypeCd;
    private Status status;
    
  
    /**
     * Required constructor for JSON Marshal
     */
    public BatchRun()
    {
    }
    
    public BatchRun(BatchRun aBatchRun)
    {
        this(aBatchRun.getEntity(), aBatchRun.getBatchId(), aBatchRun.getBatchTypeCd(), aBatchRun.getStatus());
    }
    

    public BatchRun(String entity, long batchId, BatchTypeCd batchTypeCd, Status status,)
    {
        this.entity = entity;
        this.batchId = batchId;
        this.batchTypeCd = batchTypeCd;
        this.status = status;
    }
    
    @Column(name = "Entity", nullable = false)
    public String getEntity()
    {
        return this.entity;
    }
    
    public void setEntity(String entity)
    {
        this.entity = entity;
    }
    
    @Column(name = "BatchId", nullable = false)
    public long getBatchId()
    {
        return this.batchId;
    }
    
    public void setBatchId(long batchId)
    {
        this.batchId = batchId;
    }
    
    @Enumerated(EnumType.STRING)
    @Column(name = "Status", nullable = false, length = 30)
    public Status getStatus()
    {
        return status;
    }
    
    public void setStatus(Status status)
    {
        this.status = status;
    }
    
    
    @Type(type = "BatchTypeCd")
    @Column(name = "BatchType", nullable = false, length = 30)
    public BatchTypeCd getBatchTypeCd()
    {
        return batchTypeCd;
    }
    
    public void setBatchTypeCd(BatchTypeCd batchTypeCd)
    {
        this.batchTypeCd = batchTypeCd;
    }
    
    /**
     * Business key for this object is the batchid, entity, valuationDate and batchTypeCd
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj)
    {
        if (obj == null)
        {
            return false;
        }
        if (getClass() != obj.getClass())
        {
            return false;
        }
        
        final BatchRun other = (BatchRun) obj;
        
        return Objects.equal(batchId, other.batchId) 
                && Objects.equal(entity, other.entity)
                && Objects.equal(batchTypeCd, other.batchTypeCd);
    }
    
    @Override
    public int hashCode()
    {
        return Objects.hashCode(batchId, entity, valuationDate, batchTypeCd);
    }
    
    @Override
    public String toString()
    {
        return Objects.toStringHelper(this)
                .addValue(batchId)
                .addValue(entity)
                .addValue(batchTypeCd)
                .toString();
    }
}

Labels:

0 Comments:

Post a Comment

Subscribe to Post Comments [Atom]

<< Home