This question is likely already asked, but I am not satisfied with the answers. I add the link for reference.
Now, coming to the point, I have six different abstract classes which could be individually extended by a Hibernate entity.
Note: Some classes extend one of these abstract classes, while some don't.
BaseEntity.java
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@MappedSuperclass
public abstract class BaseEntity {
@Column(name = "created_at", updatable = false, nullable = false)
protected Instant createdAt;
@Column(name = "updated_at", nullable = false)
protected Instant updatedAt;
@PrePersist
protected void onCreate() {
Instant now = Instant.now();
if (createdAt == null)
this.createdAt = now;
this.updatedAt = now;
}
@PreUpdate
protected void onUpdate() {
this.updatedAt = Instant.now();
}
}
BaseEntityImplUUID.java
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@MappedSuperclass
public abstract class BaseEntityImplUUID extends BaseEntity {
@Id
@Column(length = 36, nullable = false, unique = true, updatable = false)
protected UUID id;
@Override
protected void onCreate() {
super.onCreate();
if (this.id == null)
this.id = UUIDFactory.generate();
}
}
BaseEntityImplLong.java
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@MappedSuperclass
public abstract class BaseEntityImplLong extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long id;
}
BaseEntitySoftDeletable.java
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@MappedSuperclass
public class BaseEntitySoftDeletable extends BaseEntity {
@Column(name = "deleted_at")
private Instant deletedAt;
@Column(name = "is_deleted")
private Boolean isDeleted;
}
As same as the BaseEntitySoftDeletable
class, the BaseEntitySoftDeletableImplUUID
abstract class extends the BaseEntityImplUUID
abstract class, and the BaseEntitySoftDeletableImplLong
abstract class extends the BaseEntityImplLong
abstract class.
I found two answers from the previous question (I mentioned above) and the same from ChatGPT, Claude, and GeminiAI.
Rather than using multiple class explosion or composition, I need a way that uses inheritance effectively. I expect answers to the following two questions.
Note: I tried with custom annotation, but it is a bit difficult to handle with Hibernate filters and listeners for me. But still, I am ready to accept a better answer, though.
Rather than using multiple class explosion or composition, I need a way that uses inheritance effectively. I expect answers to the following two questions.
- What is another effective way that uses inheritance or something?
You have these alternatives for obtaining entity properties and / or behavior other than by implementing them directly in the entity class:
JPA entity classes can inherit persistent properties, callback methods, and associated annotations from mapped superclasses. Java does not have multiple inheritance of classes, and interfaces cannot have instance variables, and JPA in any case does not, generally, pay attention to annotations from implemented interfaces, so in practice, there is no multiple inheritance vector that is useful for your purposes.
JPA entity classes can embed embeddable clases, such that instances are a direct part of the persistent state of the containing entity. Embedded classes can of course have methods, but they cannot provide lifecycle callbacks to their parent entity, and in general, relatively few JPA annotations apply to embeddable classes or their members.
JPA entity classes can implement arbitrary interfaces, and thereby obtain method implementations from default methods of those interfaces. But you cannot provide or access persistent properties that way, nor can you expect that JPA will recognize or honor annotations applied to such default methods, so this doesn't have much relevance to persistence.
- If there is still no other way, what's the best practice?
Generally, it's not the issue you're making it. Projects tend to be fairly consistent across their persistence units, such that they don't have a need for the plethora of choices of entity base classes that you are trying to provide. Many don't rely on inheritance at all to provide features to their entities. Few rely on much broadly-scoped polymorphic behavior, and it's not usually much needed because most uses of entities are naturally specific to particular entity types.
In your place, I'd probably use at most two mapped entity superclasses: your BaseEntity
and maybe BaseEntityImplUUID
. It is not a coincidence that these two alone are sufficient to provide for all the entity lifecycle callbacks in the example code. If I was allowing for entities with different Id
types, then I would expect entities to provide for those themselves, except maybe UUIDs, though adding BaseEntityImplLong
wouldn't be egregious.
I would consider supporting the soft-deletable feature with an @Embeddable
class providing the isDeleted
and deletedAt
properties (though I note that isDeleted
is probably redundant). That does not get you a polymorphic interface to soft deletion, but, again, you probably wouldn't benefit much from such anyway.