javaspring-bootspring-data-jpaentityabstract-class

How to manage multiple JPA base entities (UUID, Long ID, Soft Delete) without redundant classes?


This question is likely already asked, but I am not satisfied with the answers. I add the link for reference.

Previous Question

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.

  1. Create six separate abstract classes with different behaviors and fields, like in my code.
  2. Use composition with @Embeddable and @Embedded annotations.

Rather than using multiple class explosion or composition, I need a way that uses inheritance effectively. I expect answers to the following two questions.

  1. What is another effective way that uses inheritance or something?
  2. If there is still no other way, what's the best practice?

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.


Solution

  • Rather than using multiple class explosion or composition, I need a way that uses inheritance effectively. I expect answers to the following two questions.

    1. 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:

    1. 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.