Skip to main content

Command Palette

Search for a command to run...

Techniques to Map Subclasses in JPA Using @Inheritance(strategy = JOINED)

When we talk about mapping inheritance in JPA, we’re not just discussing a coding technique — we’re exploring how object-oriented design meets the relational world. The @Inheritance(strategy = JOINED) annotation is a bridge between a clean class ...

Published
5 min read
Techniques to Map Subclasses in JPA Using @Inheritance(strategy = JOINED)
T

I am Tuanh.net. As of 2024, I have accumulated 8 years of experience in backend programming. I am delighted to connect and share my knowledge with everyone.

1. Understanding the Concept of JOINED Inheritance

In Java, inheritance is straightforward: subclasses extend a parent class and automatically get its fields and methods. However, relational databases don’t understand inheritance. They deal with tables, rows, and columns. JPA, through the @Inheritance(strategy = JOINED) strategy, allows developers to express inheritance at the database level by splitting data across multiple tables — one for the parent and one for each subclass.

In this strategy, each subclass has its own table that contains only the specific fields for that subclass, while shared attributes live in the parent table. When querying a subclass, JPA automatically joins the parent and subclass tables using the primary key.

1.1 How It Works Conceptually

Let’s say you have an abstract User entity and two concrete subclasses: Admin and Customer. The User table holds common fields such as id, name, and email. The Admin table might store adminLevel, while Customer holds loyaltyPoints. When you query all users, JPA joins User with both subclass tables.

This structure preserves data normalization, avoids redundant columns, and keeps the database schema clean.

2. Example: Mapping Subclasses with @Inheritance(strategy = JOINED)

Let’s explore an example that models a real-world scenario — a system that manages different types of users in an application.

2.1 The Parent Entity: User

import jakarta.persistence.*;

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@Table(name = "users")
public abstract class User {

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

private String name;
private String email;

// Constructors, Getters, Setters
public User() {}
public User(String name, String email) {
this.name = name;
this.email = email;
}

// toString for debug purposes
@Override
public String toString() {
return "User{id=" + id + ", name='" + name + "', email='" + email + "'}";
}
}

Here’s what’s happening:

  • The @Inheritance(strategy = InheritanceType.JOINED) tells JPA that subclasses will have separate tables but linked through their primary keys.
  • The base table users stores shared attributes.
  • Each subclass table will reference users.id as its foreign key.

2.2 The Subclass: Admin

@Entity
@Table(name = "admins")
public class Admin extends User {

private String adminLevel;

public Admin() {}
public Admin(String name, String email, String adminLevel) {
super(name, email);
this.adminLevel = adminLevel;
}

public String getAdminLevel() {
return adminLevel;
}

@Override
public String toString() {
return super.toString() + ", adminLevel='" + adminLevel + "'";
}
}

2.3 Another Subclass: Customer

@Entity
@Table(name = "customers")
public class Customer extends User {

private int loyaltyPoints;

public Customer() {}
public Customer(String name, String email, int loyaltyPoints) {
super(name, email);
this.loyaltyPoints = loyaltyPoints;
}

public int getLoyaltyPoints() {
return loyaltyPoints;
}

@Override
public String toString() {
return super.toString() + ", loyaltyPoints=" + loyaltyPoints;
}
}

3. Running the Example: Repository and Data Flow

3.1 Repository Interfaces

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<user, Long> {}
public interface AdminRepository extends JpaRepository<admin, Long> {}
public interface CustomerRepository extends JpaRepository<customer, Long> {}

Each subclass and parent can have its own repository, but the UserRepository can be used to retrieve all types thanks to polymorphism.

3.2 Example Test Runner

import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class DataLoader implements CommandLineRunner {

private final AdminRepository adminRepo;
private final CustomerRepository customerRepo;
private final UserRepository userRepo;

public DataLoader(AdminRepository adminRepo, CustomerRepository customerRepo, UserRepository userRepo) {
this.adminRepo = adminRepo;
this.customerRepo = customerRepo;
this.userRepo = userRepo;
}

@Override
public void run(String... args) {
adminRepo.save(new Admin("Alice", "alice@corp.com", "SUPER"));
customerRepo.save(new Customer("Bob", "bob@shop.com", 120));

System.out.println("All Users:");
userRepo.findAll().forEach(System.out::println);
}
}

4. Behind the Scenes: Database Structure

The id column in each subclass table is both a primary key and a foreign key referencing users.id.When you query Admin, JPA executes something like:

SELECT u.id, u.name, u.email, a.admin_level
FROM users u
JOIN admins a ON u.id = a.id;

5. Why Use JOINED Strategy?

5.1 Advantages

  • Normalized data: No redundant fields across tables.
  • Type safety: Each subclass has its own logical boundaries.
  • Clear relationships: Database mirrors object hierarchy cleanly.

5.2 Disadvantages

  • Query performance: Requires joins for subclass queries.
  • Complex migrations: Adding/removing fields means updating multiple tables.

5.3 When to Choose JOINED

Use this strategy when:

  • Your class hierarchy is stable and normalized.
  • You want to avoid column duplication.
  • You expect to query specific subclass types occasionally, not constantly.

If your hierarchy is small or performance-critical, consider SINGLE_TABLE. For full flexibility, TABLE_PER_CLASS might also fit certain analytical workloads.

6. Expanding Beyond the Basics

In more advanced systems, you can combine inheritance with:

  • @DiscriminatorColumn to mark entity type.
  • DTO projections to flatten joined queries.
  • Polymorphic queries: e.g., SELECT u FROM User u WHERE TYPE(u) = Admin.
  • MappedSuperclass: when you need shared fields but no actual table for the parent.

These patterns let you design robust, domain-driven systems that keep both your ORM and DBA happy — a rare and beautiful peace treaty in enterprise projects.

Using @Inheritance(strategy = JOINED) in JPA allows developers to map class hierarchies in a normalized, elegant way while maintaining strong type consistency in Java. It’s ideal for systems that model real-world hierarchies like employees, vehicles, or users — where shared attributes matter, but subclass-specific details need their own home.

If you’ve got questions about how to optimize or extend JOINED inheritance in your project, drop them in the comments below — I’ll be happy to help you dive deeper into this beautiful balance between OOP and relational design.

Read more at : Techniques to Map Subclasses in JPA Using @Inheritance(strategy = JOINED)

More from this blog

T

tuanh.net

540 posts

Are you ready to elevate your Java, OOP, Spring, and DevOps skills? Look no further!