Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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,

like image 571
Octavian Theodor Avatar asked Aug 01 '16 10:08

Octavian Theodor


People also ask

How to map one to one relationship in JPA?

The best way to map a @OneToOne relationship is to use @MapsId . This way, you don't even need a bidirectional association since you can always fetch the PostDetails entity by using the Post entity identifier. This way, the id property serves as both Primary Key and Foreign Key.

Can we use composite keys in hibernate?

Use embeddable objects to join two primary keys into one composite key. Every JPA entity has a primary key, but some entities have more than one value as their primary key. In this case, you need to use a composite key. This Java tip introduces you to using composite keys in JPA and Hibernate.

How do I save a many-to-many relationship in JPA?

In JPA we use the @ManyToMany annotation to model many-to-many relationships. This type of relationship can be unidirectional or bidirectional: In a unidirectional relationship only one entity in the relationship points the other. In a bidirectional relationship both entities point to each other.


1 Answers

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.

like image 98
Vlad Mihalcea Avatar answered Sep 21 '22 18:09

Vlad Mihalcea