|

Composition vs. Inheritance with JPA and Hibernate


Take your skills to the next level!

The Persistence Hub is the place to be for every Java developer. It gives you access to all my premium video courses, monthly Java Persistence News, monthly coding problems, and regular expert sessions.


Like all object-oriented programing languages, Java supports the fundamental concepts of inheritance and composition. There is an important difference between both concepts. Inheritance enables you to model an is-a association between two classes by extending a superclass. Composition models a has-a association by referencing another class in an instance variable.

You can use both concepts with JPA and Hibernate. But when should you use which one and what is the best way to do that?

For plain Java classes, there have been hundreds of discussions about this question. And there is a clear winner.

Inheritance vs. Composition for Plain Java Classes

In most cases, you should prefer composition when you design plain Java classes. In his book Effective Java 3rd Edition Joshua Bloch describes 2 circumstances in which it’s OK to use inheritance:

“It is safe to use inheritance within a package, where the subclass and the superclass implementations are under the control of the same programmers. It is also safe to use inheritance when extending classes specifically designed and documented for extension.”
Joshua Bloch – Effective Java 3rd Edition (Book Review)

In my experience, most classes in most projects were created to solve a specific task but nobody thought about extending them in the future. So, they are not designed for it and you shouldn’t extend them. If you do it anyways, you will most likely experience unexpected side-effects and implementation lock-ins in future releases.

But all these discussions and recommendations are for plain Java classes. Is this also valid for entity classes annotated with JPA annotations and mapped to tables in a relational database? And how should you implement the mapping

Inheritance vs. Composition for Entity Classes

The general recommendation to use composition over inheritance is also valid for entity classes. You can apply the same arguments that Joshua Bloch used for plain Java classes to your entities. But these are not the only reasons. When you are working with entities, you always need to keep in mind that your classes will be mapped to database tables.

Relational databases don’t support the inheritance concept. You have to use one of JPA’s inheritance strategies to map your inheritance hierarchy to one or more database tables. When you choose one of these strategies, you need to decide:

  • if you want to forgo constraints that ensure data consistency so that you get the best performance, or
  • if you accept inferior performance so that you can use the constraints.

As you can see, both approaches have their disadvantages. You don’t run into these problems if you use composition.

Using Composition with JPA and Hibernate

JPA and Hibernate support 2 basic ways to implement composition:

  1. You can reference another entity in your composition by modeling an association to it. Each entity will be mapped to its database table and can be loaded independently. A typical example is a Person entity which has a one-to-many association to an Address entity.
  2. You can also use an embeddable to include its attributes and their mapping information into your entity mapping. The attributes are mapped to the same table as the other attributes of the entity. The embeddable can’t exist on its own and has no persistent identity. You can use it to define a composition of address attributes which become part of the Person entity.

Composition via association mapping

For a lot of developers, this is the most natural way to use composition in entity mappings. It uses concepts that are well-established and easy to use in both worlds: the database, and the Java application.

Associations between database records are very common and easy to model. You can:

  • store the primary key of the associated record in one of the fields of your database record to model a one-to-many association. A typical example is a record in the Book table which contains the primary key of a record in the Publisher table. This enables you to store a reference to the publisher who published the book.
  • introduce an association table to model a many-to-many relationship. Each record in this table stores the primary key of the associated record. E.g., a BookAuthor table stores the primary keys of records in the Author and Book tables to persist which authors wrote a specific book.

One of the main benefits of JPA and Hibernate is that you can easily map these associations in your entities. You just need an attribute of the type of the associated entity or a collection of the associated entities and a few annotation. I explained these mappings in great details in one of my previous posts: Ultimate Guide – Association Mappings with JPA and Hibernate. So, I will just show you a quick example, and you can take a look at that post if you want to dive deeper.

The following code snippets show the mapping of a many-to-many association between the Book and the Author entity.
In this example, the Book entity owns the association and specifies the join table with its foreign key columns. As you can see, the mapping is relatively simple. You just need an attribute of type Set<Author>, a @ManyToMany annotation which specifies the type of relationship and a @JoinTable annotation to define the association table.

@Entity
public class Book {

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

	@ManyToMany
	@JoinTable(name = "book_author", 
		   joinColumns = { @JoinColumn(name = "book_id") }, 
		   inverseJoinColumns = { @JoinColumn(name = "author_id") })
	private Set authors = new HashSet();
	
	...
}

Modelling the other end of the association is even simpler. The books attribute with its @ManyToMany annotation just references the association mapping defined on the Book entity.

@Entity
public class Author {

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

	@ManyToMany(mappedBy = "authors")
	private Set<Book> books = new HashSet<Book>();
	
	...
}

You can use a similar approach to model one-to-one, one-to-many, and many-to-one associations. I explained this in more details in a previous post.

Composition with embeddables

Embeddables are another option to use composition when implementing your entities. They enable you to define a reusable set of attributes with mapping annotations. In contrast to the previously discussed association mappings, the embeddable becomes part of the entity and has no persistent identity on its own.

Let’s take a look at an example.

The 3 attributes of the Address class store simple address information. The @Embeddable annotation tells Hibernate and any other JPA implementation that this class and its mapping annotations can be embedded into an entity. In this example, I rely on JPA’s default mappings and don’t provide any mapping information.

@Embeddable
public class Address {

	private String street;
	private String city;
	private String postalCode;
	
	...
}

After you have defined your embeddable, you can use it as the type of an entity attribute. You just need to annotate it with @Embedded, and your persistence provider will include the attributes and mapping information of your embeddable in the entity.

So, in this example, the attributes street, city and postalCode of the embeddable Address will be mapped to columns of the Author table.

@Entity
public class Author implements Serializable {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Long id;
	
	@Embedded
	private Address address;
	
	...
}

If you want to use multiple attributes of the same embeddable type, you need to override the column mappings of the attributes of the embeddable. You can do that with a collection of @AttributeOverride annotations. Since JPA 2.2, the @AttributeOverride annotation is repeatable, and you no longer need to wrap it in an @AttributeOverrides annotation.

@Entity
public class Author implements Serializable {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Long id;
	
	@Embedded
	private Address privateAddress;
	
	@Embedded
	@AttributeOverride(
		name = "street",
		column = @Column( name = "business_street" )
	)
	@AttributeOverride(
		name = "city",
		column = @Column( name = "business_city" )
	)
	@AttributeOverride(
		name = "postalCode",
		column = @Column( name = "business_postcalcode" )
	)
	private Address businessAddress;
	
	...
}

Summary

Relational table models don’t support inheritance. JPA offers different strategies to map your inheritance hierarchy to one or more database tables. If you use one of these strategies, you need to decide if you want to ensure data consistency or if you want to get the best performance. Unfortunately, you can’t have both if you use inheritance.

You don’t need any of these mappings strategies and you don’t need to choose between consistency and performance if you use composition. You should, therefore, prefer composition over inheritance, when you design your entity model.

JPA and Hibernate offer 2 options to map your composed entities to database tables.

You can use an embeddable to define a reusable set of attributes and mapping information, which become part of your entity. The embeddable can’t exist on its own and Hibernate maps its attributes to the same database table as it maps the entity.

You can also use other entities in your composition. You then need to model an association between the two entities and Hibernate will persist each of them to its own database table.

One Comment

  1. Hi

    The tables have common columns like created_by, update_by, created_on, updatd_on, revision, etc

    And we have common class with these attributes

    How to better to use it in entity:
    1. Use @MappedSuperclass and inheritance
    2. Use composition

    Logically this is composition, but many people prefer to use inheritance

    What are pros and cons?

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.