JPA Inheritance vs Composition Spring Boot Example
1. Introduction
Inheritance is an “IS-A” type of relationship in object-oriented programming (OOP). Inheritance is tightly coupled since child classes extend from the parent class. JPA @MappedSuperclass can be annotated to the super/parent class. Composition is a “HAS-A” type of relationship in OOP, in which an object is composed of smaller associated objects. JPA @Embeddable and @Embedded can be annotated to these associating objects. In this example, I will create a spring boot project that explains JPA inheritance vs composition with the following annotations.
- @MappedSuperclass: marks a class whose mapping information is applied to the entities that inherit from it. A mapped superclass has no separate table defined for it.
- @Embeddable: specifies a class whose instances are stored as a part of an owning entity. Each of the persistent properties or fields of the embedded object is mapped to the database table for the owning entity.
- @Embedded: specifies a persistent field or property of an entity whose value is an instance of an embeddable class. The embeddable class must be annotated as @Embeddable.
- @OneToMany: specifies a many-valued association with one-to-many multiplicity.
- @ManyToOne: specifies a single-valued association to another entity class that has many-to-one multiplicity.
2. Setup
In this step, I will create a gradle project for a Spring boot data JPA.
build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '3.3.2'
id 'io.spring.dependency-management' version '1.1.6'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
tasks.named('test') {
useJUnitPlatform()
}
Also configure application.properties as the following:
application.properties
spring.application.name=jpa-inheritance-comp-demo spring.jpa.show-sql=true spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect spring.jpa.hibernate.ddl-auto= update spring.h2.console.enabled=true
3. JPA Inheritance vs Composition Diagram
As Figure 1 shows, The common fields “CREATED_BY and CREATED_DATE” are mapped by a base class annotated with @MappedSuperclass. This is a JPA inheritance example. The home address and work address are mapped by the address objects annotated with @Embedded annotation for a JPA composition example.

Figure 2 Java class diagram shows the inheritance hierarchy among the classes.

4. JPA Inheritance vs Composition Data Models
In this step, I will create six data model classes.
4.1 SuperBaseClass
In this step, I will create a super base class – SuperBaseClass that annotates with @MappedSuperclass.
SuperBaseClass.java
package com.zheng.demo.jpa.model;
import java.io.Serializable;
import java.time.ZonedDateTime;
import jakarta.persistence.Column;
import jakarta.persistence.MappedSuperclass;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@MappedSuperclass
public class SuperBaseClass implements Serializable {
private static final long serialVersionUID = -8418746719009088954L;
@Column(name = "CREATED_BY")
@EqualsAndHashCode.Include
private String createdBy;
@Column(name = "CREATED_DATE")
@EqualsAndHashCode.Include
private ZonedDateTime createdDate;
}
- Line 12:
@MappedSuperclassmaps its properties to child entities’ columns.
4.2 Employee
In this step, I will create an Employee class extending from the SuperBaseClass defined in step 4.1.
Employee.java
package com.zheng.demo.jpa.model;
import jakarta.persistence.AttributeOverride;
import jakarta.persistence.AttributeOverrides;
import jakarta.persistence.Column;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@Entity(name = "T_EMPLOYEE")
@EqualsAndHashCode(callSuper = false)
public class Employee extends SuperBaseClass {
private static final long serialVersionUID = -198345917492306405L;
@Column(name = "FIRST_NAME")
private String firstName;
@Embedded
@AttributeOverrides({ @AttributeOverride(name = "line1", column = @Column(name = "HOME_ADDR_LINE1")),
@AttributeOverride(name = "line2", column = @Column(name = "HOME_ADDR_LINE2")),
@AttributeOverride(name = "city", column = @Column(name = "HOME_ADDR_CITY")),
@AttributeOverride(name = "state", column = @Column(name = "HOME_ADDR_STATE")) })
private Address homeAddress;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "EMPLOYEE_ID")
private Integer id;
@Column(name = "LAST_NAME")
private String lastName;
@Embedded
@AttributeOverrides({ @AttributeOverride(name = "line1", column = @Column(name = "WORK_ADDR_LINE1")),
@AttributeOverride(name = "line2", column = @Column(name = "WORK_ADDR_LINE2")),
@AttributeOverride(name = "city", column = @Column(name = "WORK_ADDR_CITY")),
@AttributeOverride(name = "state", column = @Column(name = "WORK_ADDR_STATE")) })
private Address workAddress;
}
- Line 26,41:
@Embeddedannotation maps the home and work addresses to its columns.
4.3 Item
In this step, I will create an Item class extending from the SuperBaseClass defined in step 4.1.
Item.java
package com.zheng.demo.jpa.model;
import com.fasterxml.jackson.annotation.JsonBackReference;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@Entity
@Table(name = "T_ITEM")
@EqualsAndHashCode(callSuper = false)
public class Item extends SuperBaseClass {
private static final long serialVersionUID = 2328545108774264879L;
private String description;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "ITEM_ID")
private Integer id;
@JsonBackReference
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "ORDER_ID")
private Order order;
public Item(Order order, String description) {
this.setDescription(description);
this.setOrder(order);
}
}
- Line 34:
@ManyToOneannotation marks the many-to-one relationship betweenItemandOrderentities.
4.4 Order
In this step, I will create an Order class extending from the SuperBaseClass defined in step 4.1.
Order.java
package com.zheng.demo.jpa.model;
import java.time.ZonedDateTime;
import java.util.HashSet;
import java.util.Set;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@Entity
@EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true)
@Table(name = "T_ORDER")
public class Order extends SuperBaseClass {
private static final long serialVersionUID = -520933370803886172L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "ORDER_ID")
@EqualsAndHashCode.Include
private Integer id;
@JsonManagedReference
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "order")
private Set items = new HashSet<>();
@EqualsAndHashCode.Include
private String name;
public void addItem(String detail) {
Item m = new Item();
m.setCreatedBy("system");
m.setCreatedDate(ZonedDateTime.now());
m.setDescription(detail);
m.setOrder(this);
items.add(m);
}
}
- Line 38:
@OneToManymarks one-to-many relationship betweenOrderandItementities.
4.5 Task
In this step, I will create a Task class extending from the SuperBaseClass defined in step 4.1.
Task.java
package com.zheng.demo.jpa.model;
import com.fasterxml.jackson.annotation.JsonBackReference;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@Entity
@Table(name = "T_TASK")
@EqualsAndHashCode(callSuper = false)
public class Task extends SuperBaseClass {
private static final long serialVersionUID = 7871984492489240292L;
private String description;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "EMPLOYEE_ID")
@JsonBackReference
private Employee employee;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "TASK_ID")
private Integer id;
}
- Line 27:
@ManyToOneannotation marks the M-1 relationship betweenTaskandEmployeeentities.
4.6 Address
In this step, I will create an embeddable Address class.
Address.java
package com.zheng.demo.jpa.model;
import jakarta.persistence.Embeddable;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@Embeddable
public class Address {
private String city;
private String line1;
private String line2;
private String state;
}
- Line 9:
@Embeddablemarks this class which can be used as an embedded component for other entities.
5. JPA Repositories
5.1 Employee Repository
In this step, I will create an EmployeeRepo interface which extends from the JpaRepository.
EmployeeRepo.java
package com.zheng.demo.jpa.repo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.zheng.demo.jpa.model.Employee;
@Repository
public interface EmployeeRepo extends JpaRepository<Employee, Integer> {
}
5.2 Item Repository
In this step, I will create an ItemRepo interface which extends from the JpaRepository.
ItemRepo.java
package com.zheng.demo.jpa.repo;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.zheng.demo.jpa.model.Item;
@Repository
public interface ItemRepo extends JpaRepository<Item, Integer> {
List<Item> findItemsByOrderId(Integer orderId);
}
5.3 Order Repository
In this step, I will create an OrderRepo interface which extends from the JpaRepository.
OrderRepo.java
package com.zheng.demo.jpa.repo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.zheng.demo.jpa.model.Order;
@Repository
public interface OrderRepo extends JpaRepository<Order, Integer> {
}
5.4 Task Repository
In this step, I will create a TaskRepo interface which extends from the JpaRepository.
TaskRepo.java
package com.zheng.demo.jpa.repo;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.zheng.demo.jpa.model.Task;
@Repository
public interface TaskRepo extends JpaRepository<Task, Integer> {
List<Task> findTasksByEmployeeId(Integer employeeId);
}
6. Services
6.1 Employee Service
In this step, I will create an EmployeeService class that can save, read, and add tasks to an employee.
EmployeeService.java
package com.zheng.demo.jpa.service;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.zheng.demo.jpa.model.Address;
import com.zheng.demo.jpa.model.Employee;
import com.zheng.demo.jpa.model.Task;
import com.zheng.demo.jpa.repo.EmployeeRepo;
import com.zheng.demo.jpa.repo.TaskRepo;
@Service
public class EmployeeService {
@Autowired
private EmployeeRepo empRepo;
@Autowired
private TaskRepo taskRepo;
@Transactional
public void addTask(Integer empId, String taskDetail) {
Optional<Employee> foundP = get(empId);
if (foundP.isPresent()) {
Task note = new Task();
note.setCreatedBy("system");
note.setCreatedDate(ZonedDateTime.now());
note.setDescription(taskDetail);
note.setEmployee(foundP.get());
taskRepo.save(note);
}
}
@Transactional(readOnly = true)
public Optional<Employee> get(Integer empId) {
return empRepo.findById(empId);
}
@Transactional
public List<Task> getTasks(Integer empId) {
return taskRepo.findTasksByEmployeeId(empId);
}
@Transactional
public Integer save(final String firstName, final String lastName, final String state, final String city,
final String line1) {
Address address = new Address();
address.setCity(city);
address.setLine1(line1);
address.setState(state);
Employee user = new Employee();
user.setFirstName(firstName);
user.setLastName(lastName);
user.setHomeAddress(address);
user.setCreatedBy("System");
user.setCreatedDate(ZonedDateTime.now());
user = empRepo.save(user);
return user.getId();
}
}
6.2 Order Service
In this step, I will create an OrderServce class that can create an order, read orders, and add items to an order.
OrderService.java
package com.zheng.demo.jpa.service;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.zheng.demo.jpa.model.Item;
import com.zheng.demo.jpa.model.Order;
import com.zheng.demo.jpa.repo.ItemRepo;
import com.zheng.demo.jpa.repo.OrderRepo;
@Service
public class OrderService {
@Autowired
private ItemRepo itemRepo;
@Autowired
private OrderRepo orderRepo;
@Transactional
public Order addItem(Integer orderId, String itemDesc) {
Order itemOrder = null;
Optional<Order> foundOrd = getOrder(orderId);
if (foundOrd.isPresent()) {
itemOrder = foundOrd.get();
} else {
itemOrder = new Order();
itemOrder.setName("NA");
}
Item newItem = new Item();
newItem.setCreatedBy("system");
newItem.setCreatedDate(ZonedDateTime.now());
newItem.setDescription(itemDesc);
newItem.setOrder(itemOrder);
itemRepo.save(newItem);
return itemOrder;
}
@Transactional
public List<Item> getItems(Integer orderId) {
return itemRepo.findItemsByOrderId(orderId);
}
@Transactional(readOnly = true)
public Optional<Order> getOrder(Integer orderId) {
return orderRepo.findById(orderId);
}
@Transactional
public Order save(final String name, final String orderItemDe) {
Order order = new Order();
order.setCreatedBy("System");
order.setCreatedDate(ZonedDateTime.now());
order.setName(name);
order.addItem(orderItemDe);
order = orderRepo.save(order);
return order;
}
}
6.3 Test Employee Service
In this step, I will create an EmployeeServiceTest to test the methods.
EmployeeServiceTest.java
package com.zheng.demo.jpa.service;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.zheng.demo.jpa.model.Employee;
import com.zheng.demo.jpa.model.Task;
@SpringBootTest
class EmployeeServiceTest {
@Autowired
private EmployeeService testService;
@Test
void test_save_get() {
Integer maryId = testService.save("Mary", "Zheng", "MO", "St. Louis", "100 street");
Optional<Employee> foundMary = testService.get(maryId);
assertFalse(foundMary.isEmpty());
assertEquals("Mary", foundMary.get().getFirstName());
}
@Test
void test_addTask_getTasks() {
Integer alexId = testService.save("Alex", "Zheng", "MO", "St. Louis", "200 street");
testService.addTask(alexId, "Alex note 1");
testService.addTask(alexId, "Alex note 2");
List<Task> notes = testService.getTasks(alexId);
assertEquals(2, notes.size());
assertEquals("Alex note", notes.get(0).getDescription().substring(0, 9));
}
}
Run the EmployeeServiceTest and capture the output here.
EmployeeServiceTest Output
11:01:33.337 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [com.zheng.demo.jpa.service.EmployeeServiceTest]: EmployeeServiceTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration. 11:01:33.514 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper -- Found @SpringBootConfiguration com.zheng.demo.jpa.JpaInheritanceCompDemoApplication for test class com.zheng.demo.jpa.service.EmployeeServiceTest . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v3.3.2) 2024-09-07T11:01:34.055-05:00 INFO 38824 --- [jpa-inheritance-comp-demo] [ main] c.z.d.jpa.service.EmployeeServiceTest : Starting EmployeeServiceTest using Java 17.0.11 with PID 38824 (started by azpm0 in C:\MaryTools\workspace\jpa-inheritance-comp-demo) 2024-09-07T11:01:34.057-05:00 INFO 38824 --- [jpa-inheritance-comp-demo] [ main] c.z.d.jpa.service.EmployeeServiceTest : No active profile set, falling back to 1 default profile: "default" 2024-09-07T11:01:34.822-05:00 INFO 38824 --- [jpa-inheritance-comp-demo] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode. 2024-09-07T11:01:34.906-05:00 INFO 38824 --- [jpa-inheritance-comp-demo] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 69 ms. Found 4 JPA repository interfaces. 2024-09-07T11:01:35.439-05:00 INFO 38824 --- [jpa-inheritance-comp-demo] [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default] 2024-09-07T11:01:35.567-05:00 INFO 38824 --- [jpa-inheritance-comp-demo] [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 6.5.2.Final 2024-09-07T11:01:35.628-05:00 INFO 38824 --- [jpa-inheritance-comp-demo] [ main] o.h.c.internal.RegionFactoryInitiator : HHH000026: Second-level cache disabled 2024-09-07T11:01:36.077-05:00 INFO 38824 --- [jpa-inheritance-comp-demo] [ main] o.s.o.j.p.SpringPersistenceUnitInfo : No LoadTimeWeaver setup: ignoring JPA class transformer 2024-09-07T11:01:36.113-05:00 INFO 38824 --- [jpa-inheritance-comp-demo] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting... 2024-09-07T11:01:36.398-05:00 INFO 38824 --- [jpa-inheritance-comp-demo] [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:d4de5281-478a-40bd-9176-10f9736ad702 user=SA 2024-09-07T11:01:36.399-05:00 INFO 38824 --- [jpa-inheritance-comp-demo] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed. 2024-09-07T11:01:36.451-05:00 WARN 38824 --- [jpa-inheritance-comp-demo] [ main] org.hibernate.orm.deprecation : HHH90000025: H2Dialect does not need to be specified explicitly using 'hibernate.dialect' (remove the property setting and it will be selected by default) 2024-09-07T11:01:37.521-05:00 INFO 38824 --- [jpa-inheritance-comp-demo] [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) Hibernate: create table t_employee (employee_id integer not null, created_by varchar(255), created_date timestamp(6) with time zone, first_name varchar(255), home_addr_city varchar(255), home_addr_line1 varchar(255), home_addr_line2 varchar(255), home_addr_state varchar(255), last_name varchar(255), work_addr_city varchar(255), work_addr_line1 varchar(255), work_addr_line2 varchar(255), work_addr_state varchar(255), primary key (employee_id)) Hibernate: create table t_item (item_id integer not null, created_by varchar(255), created_date timestamp(6) with time zone, description varchar(255), order_id integer, primary key (item_id)) Hibernate: create table t_order (order_id integer not null, created_by varchar(255), created_date timestamp(6) with time zone, name varchar(255), primary key (order_id)) Hibernate: create table t_task (task_id integer not null, created_by varchar(255), created_date timestamp(6) with time zone, description varchar(255), employee_id integer, primary key (task_id)) Hibernate: create sequence t_employee_seq start with 1 increment by 50 Hibernate: create sequence t_item_seq start with 1 increment by 50 Hibernate: create sequence t_order_seq start with 1 increment by 50 Hibernate: create sequence t_task_seq start with 1 increment by 50 Hibernate: alter table if exists t_item add constraint FKtesk72ntb0eubn30cxidbymp4 foreign key (order_id) references t_order Hibernate: alter table if exists t_task add constraint FKc7gwb3d0o8quad9ueqxe6kc7y foreign key (employee_id) references t_employee 2024-09-07T11:01:37.576-05:00 INFO 38824 --- [jpa-inheritance-comp-demo] [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default' 2024-09-07T11:01:38.164-05:00 WARN 38824 --- [jpa-inheritance-comp-demo] [ main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning 2024-09-07T11:01:38.559-05:00 INFO 38824 --- [jpa-inheritance-comp-demo] [ main] o.s.b.a.h2.H2ConsoleAutoConfiguration : H2 console available at '/h2-console'. Database available at 'jdbc:h2:mem:d4de5281-478a-40bd-9176-10f9736ad702' 2024-09-07T11:01:38.634-05:00 INFO 38824 --- [jpa-inheritance-comp-demo] [ main] c.z.d.jpa.service.EmployeeServiceTest : Started EmployeeServiceTest in 4.909 seconds (process running for 10.605) Hibernate: select next value for t_employee_seq Hibernate: insert into t_employee (created_by,created_date,first_name,home_addr_city,home_addr_line1,home_addr_line2,home_addr_state,last_name,work_addr_city,work_addr_line1,work_addr_line2,work_addr_state,employee_id) values (?,?,?,?,?,?,?,?,?,?,?,?,?) Hibernate: select e1_0.employee_id,e1_0.created_by,e1_0.created_date,e1_0.first_name,e1_0.home_addr_city,e1_0.home_addr_line1,e1_0.home_addr_line2,e1_0.home_addr_state,e1_0.last_name,e1_0.work_addr_city,e1_0.work_addr_line1,e1_0.work_addr_line2,e1_0.work_addr_state from t_employee e1_0 where e1_0.employee_id=? Hibernate: select next value for t_task_seq Hibernate: insert into t_task (created_by,created_date,description,employee_id,task_id) values (?,?,?,?,?) Hibernate: select e1_0.employee_id,e1_0.created_by,e1_0.created_date,e1_0.first_name,e1_0.home_addr_city,e1_0.home_addr_line1,e1_0.home_addr_line2,e1_0.home_addr_state,e1_0.last_name,e1_0.work_addr_city,e1_0.work_addr_line1,e1_0.work_addr_line2,e1_0.work_addr_state from t_employee e1_0 where e1_0.employee_id=? Hibernate: select next value for t_task_seq Hibernate: insert into t_task (created_by,created_date,description,employee_id,task_id) values (?,?,?,?,?) Hibernate: select t1_0.task_id,t1_0.created_by,t1_0.created_date,t1_0.description,t1_0.employee_id from t_task t1_0 where t1_0.employee_id=? Hibernate: select next value for t_employee_seq Hibernate: insert into t_employee (created_by,created_date,first_name,home_addr_city,home_addr_line1,home_addr_line2,home_addr_state,last_name,work_addr_city,work_addr_line1,work_addr_line2,work_addr_state,employee_id) values (?,?,?,?,?,?,?,?,?,?,?,?,?) Hibernate: select e1_0.employee_id,e1_0.created_by,e1_0.created_date,e1_0.first_name,e1_0.home_addr_city,e1_0.home_addr_line1,e1_0.home_addr_line2,e1_0.home_addr_state,e1_0.last_name,e1_0.work_addr_city,e1_0.work_addr_line1,e1_0.work_addr_line2,e1_0.work_addr_state from t_employee e1_0 where e1_0.employee_id=? 2024-09-07T11:01:39.447-05:00 INFO 38824 --- [jpa-inheritance-comp-demo] [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default' 2024-09-07T11:01:39.451-05:00 INFO 38824 --- [jpa-inheritance-comp-demo] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated... 2024-09-07T11:01:39.453-05:00 INFO 38824 --- [jpa-inheritance-comp-demo] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
- Line 26 -29: verify tables created with both inheritance and composition columns.
- created_by varchar(255), created_date timestamp(6) with time zone are in all 4 tables because of the @MappedSuperclass annotation
- Line 26:
Employeeentity maps toT_EMPLOYEEtable. It shows DDL ascreate table t_employee (employee_id integer not null, created_by varchar(255), created_date timestamp(6) with time zone, first_name varchar(255), home_addr_city varchar(255), home_addr_line1 varchar(255), home_addr_line2 varchar(255), home_addr_state varchar(255), last_name varchar(255), work_addr_city varchar(255), work_addr_line1 varchar(255), work_addr_line2 varchar(255), work_addr_state varchar(255), primary key (employee_id)). - Line 27:
Itementity maps toT_ITEMtable - Line 28:
Orderentity maps toT_ORDERtable. - Line 29:
Taskentity maps toT_TASKtable
6.4 Test Order Service
In this step, I will create an OrderServiceTest to test the methods.
OrderServiceTest.java
package com.zheng.demo.jpa.service;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.zheng.demo.jpa.model.Item;
import com.zheng.demo.jpa.model.Order;
@SpringBootTest
class OrderServiceTest {
@Autowired
private OrderService testService;
@Test
void test_save_get() {
Order orderId = testService.save("Order1", "Item 1");
Optional<Order> foundOrder = testService.getOrder(orderId.getId());
assertTrue(foundOrder.isPresent());
assertEquals("Order1", foundOrder.get().getName());
List<Item> orderItems = testService.getItems(orderId.getId());
assertFalse(orderItems.isEmpty());
assertEquals("Item 1", orderItems.get(0).getDescription());
}
@Test
void test_addItem_getItems() {
Order alexId = testService.save("Alex Zheng", "Alex note info");
testService.addItem(alexId.getId(), "Alex note 1");
testService.addItem(alexId.getId(), "Alex note 2");
List<Item> notes = testService.getItems(alexId.getId());
assertEquals(3, notes.size());
assertEquals("Alex note", notes.get(0).getDescription().substring(0, 9));
}
}
Run OrderServiceTest and capture the output here.
OrderServiceTest Output
11:06:33.894 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [com.zheng.demo.jpa.service.OrderServiceTest]: OrderServiceTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration. 11:06:33.996 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper -- Found @SpringBootConfiguration com.zheng.demo.jpa.JpaInheritanceCompDemoApplication for test class com.zheng.demo.jpa.service.OrderServiceTest . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v3.3.2) 2024-09-07T11:06:34.440-05:00 INFO 28860 --- [jpa-inheritance-comp-demo] [ main] c.z.demo.jpa.service.OrderServiceTest : Starting OrderServiceTest using Java 17.0.11 with PID 28860 (started by azpm0 in C:\MaryTools\workspace\jpa-inheritance-comp-demo) 2024-09-07T11:06:34.447-05:00 INFO 28860 --- [jpa-inheritance-comp-demo] [ main] c.z.demo.jpa.service.OrderServiceTest : No active profile set, falling back to 1 default profile: "default" 2024-09-07T11:06:35.033-05:00 INFO 28860 --- [jpa-inheritance-comp-demo] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode. 2024-09-07T11:06:35.117-05:00 INFO 28860 --- [jpa-inheritance-comp-demo] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 72 ms. Found 4 JPA repository interfaces. 2024-09-07T11:06:35.607-05:00 INFO 28860 --- [jpa-inheritance-comp-demo] [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default] 2024-09-07T11:06:35.666-05:00 INFO 28860 --- [jpa-inheritance-comp-demo] [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 6.5.2.Final 2024-09-07T11:06:35.701-05:00 INFO 28860 --- [jpa-inheritance-comp-demo] [ main] o.h.c.internal.RegionFactoryInitiator : HHH000026: Second-level cache disabled 2024-09-07T11:06:36.000-05:00 INFO 28860 --- [jpa-inheritance-comp-demo] [ main] o.s.o.j.p.SpringPersistenceUnitInfo : No LoadTimeWeaver setup: ignoring JPA class transformer 2024-09-07T11:06:36.033-05:00 INFO 28860 --- [jpa-inheritance-comp-demo] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting... 2024-09-07T11:06:36.209-05:00 INFO 28860 --- [jpa-inheritance-comp-demo] [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:f94d384e-ed4b-4c4f-a585-dbb775dc16de user=SA 2024-09-07T11:06:36.211-05:00 INFO 28860 --- [jpa-inheritance-comp-demo] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed. 2024-09-07T11:06:36.245-05:00 WARN 28860 --- [jpa-inheritance-comp-demo] [ main] org.hibernate.orm.deprecation : HHH90000025: H2Dialect does not need to be specified explicitly using 'hibernate.dialect' (remove the property setting and it will be selected by default) 2024-09-07T11:06:37.168-05:00 INFO 28860 --- [jpa-inheritance-comp-demo] [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) Hibernate: create table t_employee (employee_id integer not null, created_by varchar(255), created_date timestamp(6) with time zone, first_name varchar(255), home_addr_city varchar(255), home_addr_line1 varchar(255), home_addr_line2 varchar(255), home_addr_state varchar(255), last_name varchar(255), work_addr_city varchar(255), work_addr_line1 varchar(255), work_addr_line2 varchar(255), work_addr_state varchar(255), primary key (employee_id)) Hibernate: create table t_item (item_id integer not null, created_by varchar(255), created_date timestamp(6) with time zone, description varchar(255), order_id integer, primary key (item_id)) Hibernate: create table t_order (order_id integer not null, created_by varchar(255), created_date timestamp(6) with time zone, name varchar(255), primary key (order_id)) Hibernate: create table t_task (task_id integer not null, created_by varchar(255), created_date timestamp(6) with time zone, description varchar(255), employee_id integer, primary key (task_id)) Hibernate: create sequence t_employee_seq start with 1 increment by 50 Hibernate: create sequence t_item_seq start with 1 increment by 50 Hibernate: create sequence t_order_seq start with 1 increment by 50 Hibernate: create sequence t_task_seq start with 1 increment by 50 Hibernate: alter table if exists t_item add constraint FKtesk72ntb0eubn30cxidbymp4 foreign key (order_id) references t_order Hibernate: alter table if exists t_task add constraint FKc7gwb3d0o8quad9ueqxe6kc7y foreign key (employee_id) references t_employee 2024-09-07T11:06:37.212-05:00 INFO 28860 --- [jpa-inheritance-comp-demo] [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default' 2024-09-07T11:06:37.769-05:00 WARN 28860 --- [jpa-inheritance-comp-demo] [ main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning 2024-09-07T11:06:38.112-05:00 INFO 28860 --- [jpa-inheritance-comp-demo] [ main] o.s.b.a.h2.H2ConsoleAutoConfiguration : H2 console available at '/h2-console'. Database available at 'jdbc:h2:mem:f94d384e-ed4b-4c4f-a585-dbb775dc16de' 2024-09-07T11:06:38.181-05:00 INFO 28860 --- [jpa-inheritance-comp-demo] [ main] c.z.demo.jpa.service.OrderServiceTest : Started OrderServiceTest in 3.994 seconds (process running for 4.973) Hibernate: select next value for t_order_seq Hibernate: select next value for t_item_seq Hibernate: insert into t_order (created_by,created_date,name,order_id) values (?,?,?,?) Hibernate: insert into t_item (created_by,created_date,description,order_id,item_id) values (?,?,?,?,?) Hibernate: select o1_0.order_id,o1_0.created_by,o1_0.created_date,o1_0.name from t_order o1_0 where o1_0.order_id=? Hibernate: select next value for t_item_seq Hibernate: insert into t_item (created_by,created_date,description,order_id,item_id) values (?,?,?,?,?) Hibernate: select o1_0.order_id,o1_0.created_by,o1_0.created_date,o1_0.name from t_order o1_0 where o1_0.order_id=? Hibernate: insert into t_item (created_by,created_date,description,order_id,item_id) values (?,?,?,?,?) Hibernate: select i1_0.item_id,i1_0.created_by,i1_0.created_date,i1_0.description,i1_0.order_id from t_item i1_0 where i1_0.order_id=? Hibernate: select next value for t_order_seq Hibernate: insert into t_order (created_by,created_date,name,order_id) values (?,?,?,?) Hibernate: insert into t_item (created_by,created_date,description,order_id,item_id) values (?,?,?,?,?) Hibernate: select o1_0.order_id,o1_0.created_by,o1_0.created_date,o1_0.name from t_order o1_0 where o1_0.order_id=? Hibernate: select i1_0.item_id,i1_0.created_by,i1_0.created_date,i1_0.description,i1_0.order_id from t_item i1_0 where i1_0.order_id=? 2024-09-07T11:06:38.923-05:00 INFO 28860 --- [jpa-inheritance-comp-demo] [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default' 2024-09-07T11:06:38.926-05:00 INFO 28860 --- [jpa-inheritance-comp-demo] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated... 2024-09-07T11:06:38.928-05:00 INFO 28860 --- [jpa-inheritance-comp-demo] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
- Table DDL is printed out as step 6.3.
7. Conclusion
In this example, I demonstrated the following common JPA inheritance and composition annotations in a spring boot project:
@MappedSuperClass: marks a class whose mapping information is applied to the entities that inherit from it. A mapped superclass has no separate table defined for it.@Embeddable: specifies a class whose instances are stored as a part of an owning entity. Each of the persistent properties or fields of the embedded object is mapped to the database table for the owning entity.@Embedded: specifies a persistent field or property of an entity whose value is an instance of an embeddable class. The embeddable class must be annotated as @Embeddable.@OneToMany: specifies a many-valued association with one-to-many multiplicity.@ManyToOne: specifies a single-valued association to another entity class that has many-to-one multiplicity.
8. Download
This was an example of a gradle project which includes common JPA inheritance and composition annotations.
You can download the full source code of this example here: JPA Inheritance vs Composition Spring Boot Example

