JPA Inheritance: Strategies and Best Practices

JPA Inheritance: Strategies and Best Practices

In Java Persistence API (JPA), object inheritance can be mapped to a relational database using three different strategies: the single table strategy, the joined table strategy, and the table per class strategy. In this article, we’ll explore how to use JPA associations with each of these strategies, along with some best practices.

Strategies

  1. Single Table Strategy

    In the single-table strategy, all the entities in the hierarchy are stored in a single table, with each row representing an instance of a specific entity. For example, consider a Person entity that is the base class for Employee and Customer entities. Each Employee has a list of Task entities.

     @Entity
     @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
     @DiscriminatorColumn(name = "TYPE")
     public abstract class Person {
         @Id
         private long id;
         private String name;
    
         // getters and setters
     }
    
     @Entity
     @DiscriminatorValue("EMPLOYEE")
     public class Employee extends Person {
         @OneToMany(mappedBy = "employee")
         private List<Task> tasks;
    
         // getters and setters
     }
    
     @Entity
     @DiscriminatorValue("CUSTOMER")
     public class Customer extends Person {
         private String email;
    
         // getters and setters
     }
    
     @Entity
     public class Task {
         @Id
         private long id;
         private String description;
    
         @ManyToOne
         private Employee employee;
    
         // getters and setters
     }
    

    In this example, the Person entity is the base class for Employee and Customer entities, using the @Inheritance and @DiscriminatorColumn annotations to indicate the single table inheritance strategy. The Employee entity has a list of Task entities, which is annotated with @OneToMany and mappedBy="employee". The Task entity has a @ManyToOne association to the Employee entity.

  2. Joined Table Strategy

    In the joined table strategy, each entity in the hierarchy has its own table, with a separate table to store the common properties in the hierarchy. For example, consider a Person entity that is the base class for Employee and Customer entities. Each Employee has a list of Task entities.

     @Entity
     @Inheritance(strategy = InheritanceType.JOINED)
     public abstract class Person {
         @Id
         private long id;
         private String name;
    
         // getters and setters
     }
    
     @Entity
     public class Employee extends Person {
         @OneToMany(mappedBy = "employee")
         private List<Task> tasks;
    
         // getters and setters
     }
    
     @Entity
     public class Customer extends Person {
         private String email;
    
         // getters and setters
     }
    
     @Entity
     public class Task {
         @Id
         private long id;
         private String description;
    
         @ManyToOne
         private Employee employee;
    
         // getters and setters
     }
    

    In this example, the Person entity is the base class for Employee and Customer entities, using the @Inheritance annotation to indicate the joined table inheritance strategy. The Employee entity has a list of Task entities, which is annotated with @OneToMany and mappedBy="employee". The Task entity has a @ManyToOne association to the Employee entity.

  3. Table Per Class Strategy

    In the table per class strategy, each entity in the hierarchy has its own table, with all the properties of the entity and its superclass(es) included in the table. The tables are not related to foreign keys, and queries involving the inheritance hierarchy may require joins across multiple tables.

     @Entity
     @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
     public abstract class Person {
         @Id
         private long id;
         private String name;
    
         // getters and setters
     }
    
     @Entity
     public class Employee extends Person {
         @OneToMany(mappedBy = "employee")
         private List<Task> tasks;
    
         // getters and setters
     }
    
     @Entity
     public class Customer extends Person {
         private String email;
    
         // getters and setters
     }
    
     @Entity
     public class Task {
         @Id
         private long id;
         private String description;
    
         @ManyToOne
         private Employee employee;
    
         // getters and setters
     }
    

    In this example, the Person entity is the base class for Employee and Customer entities, using the @Inheritance annotation to indicate the table per class inheritance strategy. The Employee entity has a list of Task entities, which is annotated with @OneToMany and mappedBy="employee". The Task entity has a @ManyToOne association to the Employee entity.

Best Practices

  • Choose the Right Inheritance Strategy

    When mapping object inheritance to a relational database with JPA, it’s important to choose the right inheritance strategy for your use case. Each strategy has its advantages and disadvantages, and the choice depends on factors such as the size of the object hierarchy, the complexity of the queries, and the performance requirements.

  • Use Appropriate Association Types

    When using JPA associations with inheritance, it’s important to choose the appropriate association types for your use case. For example, a one-to-many association is appropriate for a list of child entities, while a many-to-one association is appropriate for a parent entity.

  • Use Appropriate Fetching Strategies

    When querying for entities with associations, JPA provides several fetching strategies to optimize performance. For example, the FetchType.LAZY strategy can be used to fetch associated entities only when they are needed, while the FetchType.EAGER strategy can be used to fetch associated entities along with the main entity.

  • Be Careful with Cascade Types

    When using JPA associations with inheritance, it’s important to be careful with cascade types. Cascade types specify the operations that should be cascaded to associated entities when the main entity is persisted, updated, or deleted. In some cases, cascading operations can cause unintended side effects, such as deleting associated entities that should not be deleted.

Conclusion

Overall, by choosing the right inheritance strategy and using appropriate association types, fetching strategies, and cascade types, you can optimize the performance and maintainability of your JPA applications.