javahibernatejpaone-to-onenatural-key

How to model a one-to-one relationship in JPA when the "parent" table has a composite PK?


While there is plenty of information around on how to model, in JPA (2), a one-to-one relationship OR an entity having a natural key, I haven't been able to find a clear / simple answer to how to model the situation where we have both, i.e. a one-to-one relationship where the parent table has a natural key. It could obviously be that I might have missed such a tutorial; if so, pointing me to one could also be the answer.

And, as many times with JPA and noobs such as I, the moment one needs a bit more than the most basic model, one can quickly hit the wall.

Hence, considering the following DB model:

enter image description here

What would be the corresponding JPA-annotated object model? (I'm sparing you guys of the things I've tried since I don't want to influence the answer...)

Performance recommendations are also welcome (e.g. "a one-to-many could perform faster", etc.)!

Thanks,


Solution

  • The composite identifier is built out of two numerical columns so the mapping looks like this:

    @Embeddable
    public class EmployeeId implements Serializable {
    
        private Long companyId;
    
        private Long employeeId;
    
        public EmployeeId() {
        }
    
        public EmployeeId(Long companyId, Long employeeId) {
            this.companyId = companyId;
            this.employeeId = employeeId;
        }
    
        public Long getCompanyId() {
            return companyId;
        }
    
        public Long getEmployeeId() {
            return employeeId;
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof EmployeeId)) return false;
            EmployeeId that = (EmployeeId) o;
            return Objects.equals(getCompanyId(), that.getCompanyId()) &&
                    Objects.equals(getEmployeeId(), that.getEmployeeId());
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(getCompanyId(), getEmployeeId());
        }
    }
    

    The parent class, looks as follows:

    @Entity(name = "Employee")
    public static class Employee {
    
        @EmbeddedId
        private EmployeeId id;
    
        private String name;
    
        @OneToOne(mappedBy = "employee")
        private EmployeeDetails details;
    
        public EmployeeId getId() {
            return id;
        }
    
        public void setId(EmployeeId id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public EmployeeDetails getDetails() {
            return details;
        }
    
        public void setDetails(EmployeeDetails details) {
            this.details = details;
        }
    }
    

    And the child like this:

    @Entity(name = "EmployeeDetails")
    public static class EmployeeDetails {
    
        @EmbeddedId
        private EmployeeId id;
    
        @MapsId
        @OneToOne
        private Employee employee;
    
        private String details;
    
        public EmployeeId getId() {
            return id;
        }
    
        public void setId(EmployeeId id) {
            this.id = id;
        }
    
        public Employee getEmployee() {
            return employee;
        }
    
        public void setEmployee(Employee employee) {
            this.employee = employee;
            this.id = employee.getId();
        }
    
        public String getDetails() {
            return details;
        }
    
        public void setDetails(String details) {
            this.details = details;
        }
    }
    

    And everything works just fine:

    doInJPA(entityManager -> {
        Employee employee = new Employee();
        employee.setId(new EmployeeId(1L, 100L));
        employee.setName("Vlad Mihalcea");
        entityManager.persist(employee);
    });
    
    doInJPA(entityManager -> {
        Employee employee = entityManager.find(Employee.class, new EmployeeId(1L, 100L));
        EmployeeDetails employeeDetails = new EmployeeDetails();
        employeeDetails.setEmployee(employee);
        employeeDetails.setDetails("High-Performance Java Persistence");
        entityManager.persist(employeeDetails);
    });
    
    doInJPA(entityManager -> {
        EmployeeDetails employeeDetails = entityManager.find(EmployeeDetails.class, new EmployeeId(1L, 100L));
        assertNotNull(employeeDetails);
    });
    doInJPA(entityManager -> {
        Phone phone = entityManager.find(Phone.class, "012-345-6789");
        assertNotNull(phone);
        assertEquals(new EmployeeId(1L, 100L), phone.getEmployee().getId());
    });
    

    Code available on GitHub.