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
theBaseEntity
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 classabstract
prevents the developers from instantiating an instance of this class. Only extending classes can instantiate an instance.id
- Theid
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 getOptimistic Locking
as addition. When you first instantiate an instance of a extending class, the value ofversion
will be0
.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 thecreatedAt
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 theupdatedAt
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();
}
}