I was trying to use spring boot data JPA inheritance to achieve the following:
Here's an example code:
SQL:
CREATE TABLE Staff (
staff_id INT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
email VARCHAR(50) UNIQUE NOT NULL,
salary DECIMAL(10,2) NOT NULL
);
CREATE TABLE TeachingStaff (
staff_id INT PRIMARY KEY,
department VARCHAR(50) NOT NULL,
FOREIGN KEY (staff_id) REFERENCES Staff(staff_id)
);
Java:
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Staff {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "staff_id")
private Long id;
@Column(name = "name")
private String name;
@Column(name = "email")
private String email;
@Column(name = "salary")
private Double salary;
// constructors, getters and setters
}
@Entity
public class TeachingStaff extends Staff {
@Column(name = "department")
private String department;
@Column(name = "subject_taught")
private String subjectTaught;
// constructors, getters and setters
}
Staff repository:
@Repository
public interface StaffRepo extends JpaRepository<Staff, Long> {
Optional<Staff> findByEmail(String email);
}
TeachingStaff repository:
@Repository
public interface TeachingStaffRepo extends JpaRepository<TeachingStaff, Long> {}
Adding and updating a new staff (I'm omitting the service layer for the sake of brevity):
@Bean
CommandLineRunner run(StaffRepo staffRepo, TeachingStaffRepo teachingStaffRepo) {
return args -> {
// Adding a new staff
Staff newStaff = new Staff("John Doe", "[email protected]", 2000.0);
staffRepo.save(newStaff);
// Retrieving and updating an existing staff
Staff existingStaff = staffRepo.findByEmail("[email protected]").orElseThrow();
TeachingStaff newTeachingStaff = new TeachingStaff();
newTeachingStaff.setId(existingStaff.getId());
newTeachingStaff.setName(existingStaff.getName());
newTeachingStaff.setEmail(existingStaff.getEmail());
newTeachingStaff.setSalary(existingStaff.getSalary());
newTeachingStaff.setDepartment("Mathematics");
newTeachingStaff.setSubjectTaught("Calculus");
teachingStaffRepo.save(newTeachingStaff);
};
}
I've been trying different methods for a few days now but no luck yet. I tried all strategies in @Inheritance and @MappedSuperclass but they didn't work as expected.
The only method that creates the same SQL schema as the above one is by using InheritanceType.JOINED. The problem though is that when I want to update a staff and make him a teaching staff, a duplicate record is inserted in the staff table.
I read that this can be achieved by detaching the retrieved object (existingStaff), but this didn't resolve the duplication problem.
Is there a solution to this problem in Spring Boot Data JPA?
After days of searching and testing different methods I reached the conclusion that composition is the only built-in solution in Hibernate to my OOP-to-Relational-DB design.
Another solution is to add a custom method and implement a custom SQL (or JPQL) query in the Subclass' repository as follows:
@Modifying
@Query(value = "INSERT INTO teaching_staff " +
"(staff_id, department, subject_taught)" +
" VALUES (:#{#t.id}, :#{#t.department}, :#{#t.subjectTaught})", nativeQuery = true)
@Transactional
void makeTeachingStaff(@Param("t") TeachingStaff teachingStaff);