DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Understanding Java Signals
  • A Guide to Constructor Chaining in Java
  • Java Bean Validation: Applying Constraints Programmatically
  • Achieving Inheritance in NoSQL Databases With Java Using Eclipse JNoSQL

Trending

  • Microsoft Azure Synapse Analytics: Scaling Hurdles and Limitations
  • Beyond ChatGPT, AI Reasoning 2.0: Engineering AI Models With Human-Like Reasoning
  • Unlocking the Potential of Apache Iceberg: A Comprehensive Analysis
  • Create Your Own AI-Powered Virtual Tutor: An Easy Tutorial
  1. DZone
  2. Coding
  3. Java
  4. Inheritance vs Composition in JPA

Inheritance vs Composition in JPA

@MappedSuperclass is used almost in each #JPA application to declare common fields. It could also be achieved using @Embedded entities. Read this article to learn more.

By 
Georgii Vlasov user avatar
Georgii Vlasov
·
Aleksey Stukalov user avatar
Aleksey Stukalov
·
Updated Sep. 20, 22 · Tutorial
Likes (11)
Comment
Save
Tweet
Share
9.4K Views

Join the DZone community and get the full member experience.

Join For Free

Introduction

«Don't repeat yourself» or «DRY». Developers try to adhere to this principle during software development. It helps to avoid redundant code writing and, as a result, simplifies its maintainability in the future. But how to achieve this principle in the JPA world?

There are two approaches: Inheritance and Composition. Both have their pros and cons. Let's figure out what they are on the not quite "real-world" but representative example.

Subject Domain

Our model has three entities: Article, Author, and Spectator. Each entity has fields for audit (createdDate, createdBy, modifiedDate, and modifedBy). Author and Spectator also have fields for address (country, city, street, building).

Subject Domain Model

Inheritance: MappedSuperclass

To comply with the DRY principle, let's take duplicate fields to separate Mapped Superclasses. We will inherit our entities from them. Since all entities must have fields for auditing, let's start with the BaseEntityAudit class. We will create a "BaseEntityAuditAddress" class for entities with address fields and inherit it from the BaseEntityAudit class.

BaseEntityAudit Class

NOTE: All the approaches presented in this article are implemented and available in this repository on GitHub.

Java
 
@MappedSuperclass 
public class BaseEntityAuditAddress extends BaseEntityAudit { 
  @Column(name = "country") 
  private String country; 

  @Column(name = "city") 
  private String city; 

  @Column(name = "street") 
  private String street; 

  @Column(name = "building") 
  private String building;
  //...
}

@Entity 
@Table(name = "spectator") 
public class Spectator extends BaseEntityAuditAddress {
  //...
}


The hierarchy of entities is implemented so that we no longer repeat ourselves. Mission complete. But what if...?

Breaking the Hierarchy

But what if the initial requirements for our model change a little? For example, consider that Article needs just audit fields, Spectator needs address fields only, and Author needs both. In this case, following the Inheritance strategy, we will have to neglect the DRY principle anyway because there are no multiple inheritances for classes in Java. In other words, our hierarchy will look like a diagram below, which is impossible to implement in Java.

Breaking the Hierarchy

We will have to leave two Superclasses created earlier and one with address fields only for Spectator. So, the address fields will be repeated in two entities. If we want to comply with the DRY principle, let's use the composition instead.

Composition Strategy

Composition: @Embeddable and Interfaces

Let’s implement the composition via interfaces with only one method: getBaseEntityAudit() or getBaseEntityAddress(). As you can guess, they will return embeddable entities containing the corresponding fields. Implementing these methods in entities will replace getters for @Embedded fields.

Implementing Composition Via Interfaces

Java
 
@Embeddable 
public class BaseEntityAudit { 
  @Column(name = "created_date", nullable = false, updatable = false) 
  @CreatedDate 
  private long createdDate; 

  @Column(name = "created_by") 
  @CreatedBy 
  private String createdBy; 

  @Column(name = "modified_date") 
  @LastModifiedDate 
  private long modifiedDate; 

  @Column(name = "modified_by") 
  @LastModifiedBy 
  private String modifiedBy;
  // ...
}

public interface EntityWithAuditFields { 
  BaseEntityAudit getBaseEntityAudit(); 
}

Now we are free to use those interfaces in any entity. To implement interface methods, you need to add an @Embedded attribute and a getter for it.

Java
 
@Entity 
@Table(name = "author") 
public class Author implements EntityWithAuditFields, EntityWithAddressFields { 
  //...

  @Embedded 
  private BaseEntityAudit baseEntityAudit; 

  @Embedded 
  private BaseEntityAddress baseEntityAddress; 
  
  public BaseEntityAddress getBaseEntityAddress() {
    return baseEntityAddress; 
  } 
  
  public BaseEntityAudit getBaseEntityAudit() { 
    return baseEntityAudit; 
  }
  //... 
}

Polymorphism: Upcast to the Parent Class

We have achieved DRY in the entity code, but what about the business code that works with these entities? Let's imagine that we need a method that will return a list of countries from a list of entities. In our example with inheritance, we will need to pass a list with the BaseEntityAuditAddress type as a parameter. And we will be able to use this method for both Authors and Spectators.

Java
 
public class Business { 
  public List<String> getCountries(List<BaseEntityAuditAddress> entitiesList) { 
    if (entitiesList == null || entitiesList.isEmpty()) { 
      return Collections.emptyList(); 
    } 
    return entitiesList.stream() 
      .map(BaseEntityAuditAddress::getCountry) 
      .distinct() 
      .collect(Collectors.toList()); 
    } 
}

The usage will be the following:

Java
 
List<BaseEntityAuditAddress> authors = new ArrayList<>();
//add authors to the list
List<String> countries = new Business().getCountries(authors);

However, changing the approach will not change anything. All that needs to be changed is to replace BaseEntityAuditAddress with EntityWithAddressFields.

Java
 
public class Business { 
  public List<String> getCountries(List<EntityWithAddressFields> entitiesList) { 
    if (entitiesList == null || entitiesList.isEmpty()) { 
      return Collections.emptyList(); 
    } 
    return entitiesList.stream() 
      .map(EntityWithAddressFields::getBaseEntityAddress) 
      .map(BaseEntityAddress::getCountry) 
      .distinct() 
      .collect(Collectors.toList());
    }
}

Perhaps the method has become easier to read since we explicitly refer to an entity with only the address and not both the address and the audit fields.

Conclusion

In the end, the composition seems to have more flexible use cases. But even if you decide to use inheritance (one of the possible reasons: is to limit such flexibility intentionally), JPA Buddy will help you regardless of the chosen approach. Check it out in a short video version of this article.

Inheritance (object-oriented programming) entity Java (programming language)

Published at DZone with permission of Georgii Vlasov. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Understanding Java Signals
  • A Guide to Constructor Chaining in Java
  • Java Bean Validation: Applying Constraints Programmatically
  • Achieving Inheritance in NoSQL Databases With Java Using Eclipse JNoSQL

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!