Core Java

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.

JPA Inheritance vs Composition
Figure 1 JPA Inheritance vs Composition Diagram

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

Class Digram
Figure 2 Java Class Diagram

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: @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

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

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 between Item and Order entities.

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: @OneToMany marks one-to-many relationship between Order and Item entities.

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: @ManyToOne annotation marks the M-1 relationship between Task and Employee entities.

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: @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

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: Employee entity maps to T_EMPLOYEE table. It shows DDL as 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)).
  • Line 27: Item entity maps to T_ITEM table
  • Line 28: Order entity maps to T_ORDER table.
  • Line 29: Task entity maps to T_TASK table

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.

Download
You can download the full source code of this example here: JPA Inheritance vs Composition Spring Boot Example

Mary Zheng

Mary graduated from the Mechanical Engineering department at ShangHai JiaoTong University. She also holds a Master degree in Computer Science from Webster University. During her studies she has been involved with a large number of projects ranging from programming and software engineering. She worked as a lead Software Engineer where she led and worked with others to design, implement, and monitor the software solution.
Subscribe
Notify of
guest

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

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button