Introduction

As a developer you get quite often confronted with the setup of a new Java project and some tasks repeat themselves. One of them is thinking about how you would like to design your entities - if you work with JPA of course 🙂 - and if you would like to create a BaseEntity.

The advantage of a BaseEntity-class is, that it provides all extending classes with - like the name says - base functionality and you don’t need to create the id on each class for itself.

BaseEntity

Let’s take a look at some of the keywords in this class:

  • @MappedSuperclass - By the use of @MappedSuperclass the BaseEntity class will not have a separate representation as table in the database. All fields in this class will be stored in the table of the extending class.
  • public abstract class BaseEntity - Making this class abstract prevents the developers from instantiating an instance of this class. Only extending classes can instantiate an instance.
  • id - The id is annotated with @Id, which marks it as the primary key to identify this entity.
  • @GeneratedValue(strategy = GenerationType.AUTO) - You can specify the generation strategy. GenerationType.AUTO will automatically choose a strategy for you, depending on the underlying database.
  • version - This field is not necessary, but with the @Version annotation you will get Optimistic Locking as addition. When you first instantiate an instance of a extending class, the value of version will be 0. Saving/Updating this object in the database will increment this field by one. Did a change happen while you are trying to persist it into the database, an exception will get thrown.

Possible bottleneck @GeneratedValue(strategy = GenerationType.AUTO)
Using @GeneratedValue(strategy = GenerationType.AUTO) can be also a disadvantage, while using it with Postgres or Oracle. This databases do not have autoincremental fields as you know it from MySQL and they will use a sequence. Depending on how you created your database or sequences, it can happen that all your entities will share the same sequence: a possible bottleneck when performing a lot of insert operations.

import javax.persistence.*;
import java.io.Serializable;
import java.util.Objects;

@MappedSuperclass
public abstract class BaseEntity implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Version
    private Long version;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Long getVersion() {
        return version;
    }

    public void setVersion(Long version) {
        this.version = version;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        BaseEntity that = (BaseEntity) o;
        return Objects.equals(id, that.id) &&
                Objects.equals(version, that.version);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, version);
    }

    @Override
    public String toString() {
        return "BaseEntity{" +
                "id=" + id +
                ", version=" + version +
                '}';
    }
}

Base entity with temporal and changed by fields

While we have seen the BaseEntity class, it is also possible to go further and use additional keywords provided by JPA for our advantage. The following entity contains additional fields like createdBy, updatedBy, createdAt and updatedAt. The last two are TemporalType, which contain a timestamp.

  • @PrePersist - This annotation is a marker, which tells the underlying JPA implementation to execute this marked method. In our case it fills the createdAt field with the current time. This method will be executed on the first insert/creation/persist of the entity.
  • @PreUpdate- This annotation is a marker, which tells the underlying JPA implementation to execute this marked method on each update of the entity. In our case it fills the updatedAt field with the current time.
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
import java.util.Objects;

@MappedSuperclass
public abstract class BaseEntityAudit extends BaseEntity implements Serializable {

    private String createdBy;
    private String updatedBy;

    @Temporal(TemporalType.TIMESTAMP)
    private Date createdAt;

    @Temporal(TemporalType.TIMESTAMP)
    private Date updatedAt;

    /**
     * Sets createdAt before insert
     */
    @PrePersist
    public void setCreationDate() {
        this.createdAt = new Date();
    }

    /**
     * Sets updatedAt before update
     */
    @PreUpdate
    public void setChangeDate() {
        this.updatedAt = new Date();
    }

    public Date getCreatedAt() {
        return createdAt;
    }

    public void setCreatedAt(Date createdAt) {
        this.createdAt = createdAt;
    }

    public Date getUpdatedAt() {
        return updatedAt;
    }

    public void setUpdatedAt(Date updatedAt) {
        this.updatedAt = updatedAt;
    }

    public String getCreatedBy() {
        return createdBy;
    }

    public void setCreatedBy(String createdBy) {
        this.createdBy = createdBy;
    }

    public String getUpdatedBy() {
        return updatedBy;
    }

    public void setUpdatedBy(String updatedBy) {
        this.updatedBy = updatedBy;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        if (!super.equals(o)) return false;
        BaseEntityAudit that = (BaseEntityAudit) o;
        return Objects.equals(createdBy, that.createdBy) &&
                Objects.equals(updatedBy, that.updatedBy) &&
                Objects.equals(createdAt, that.createdAt) &&
                Objects.equals(updatedAt, that.updatedAt);
    }

    @Override
    public int hashCode() {
        return Objects.hash(super.hashCode(), createdBy, updatedBy, createdAt, updatedAt);
    }

    @Override
    public String toString() {
        return "BaseEntityAudit{" +
                "createdBy='" + createdBy + '\'' +
                ", updatedBy='" + updatedBy + '\'' +
                ", createdAt=" + createdAt +
                ", updatedAt=" + updatedAt +
                "} " + super.toString();
    }
}