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
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | 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
1 2 3 4 5 6 7 | 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
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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:
@MappedSuperclass
maps 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
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | 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:
@Embedded
annotation 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
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | 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:
@ManyToOne
annotation marks the many-to-one relationship betweenItem
andOrder
entities.
4.4 Order
In this step, I will create an Order
class extending from the SuperBaseClass
defined in step 4.1.
Order.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | 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:
@OneToMany
marks one-to-many relationship betweenOrder
andItem
entities.
4.5 Task
In this step, I will create a Task
class extending from the SuperBaseClass
defined in step 4.1.
Task.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | 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:
@ManyToOne
annotation marks the M-1 relationship betweenTask
andEmployee
entities.
4.6 Address
In this step, I will create an embeddable Address
class.
Address.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 | 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:
@Embeddable
marks 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
01 02 03 04 05 06 07 08 09 10 11 | 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
01 02 03 04 05 06 07 08 09 10 11 12 13 | 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
01 02 03 04 05 06 07 08 09 10 11 | 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
01 02 03 04 05 06 07 08 09 10 11 12 13 | 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
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | 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
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | 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
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | 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
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | 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:
Employee
entity maps toT_EMPLOYEE
table. 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:
Item
entity maps toT_ITEM
table - Line 28:
Order
entity maps toT_ORDER
table. - Line 29:
Task
entity maps toT_TASK
table
6.4 Test Order Service
In this step, I will create an OrderServiceTest
to test the methods.
OrderServiceTest.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | 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
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | 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