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

1

5

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,

Eleph answered 1/8, 2016 at 10:10 Comment(3)
The parent object has a composite PK, and so has @IdClass. The Child object has a single field annotated with @Id and a Parent reference. No idea how that is "non-standard". Who knows what "T_CHILD_C_PK" is. You put the FK at one side or the other, so I have to assume it is P_NK_1 and P_NK_2 in the ChildOctavia
Indeed, the previous diagram was less than clear, thanks. I've updated the post with a (hopefully) clearer image. The parent table (T_PARENT) has a natural key formed of 2 fields, (PARENT_PK1, PARENT_PK2) and is being referenced by the child table T_CHILD.Eleph
Your schema is clearer now, but the fact remains if you just have a @OneToOne in the Child back to the Parent then it maps simply (and the "parent" relation field in the Child gives the 2 FK columns in the CHILD table).Octavia
D
13

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.

Discernible answered 1/8, 2016 at 10:37 Comment(4)
Thanks Vlad! What I kinda' wanted to obtain was a member / property in the "parent" class (Employee in your example) giving me access to the child (EmployeeDetails). Something like: public class Employee { ... public EmployeeDetails getDetails() { ... } ...Eleph
Because this uses Objects class which was introduced in Java 7, it does not compile in Java 6.Eccentricity
Java 6 is very old. Maybe it's time to upgrade it.Discernible
And, if truly stuck for whatever reason with versions prior to 7, you could create your own replicas, eventually by simply copying the code from OpenJDK (although implementing on one's own should not be hard :) ) -- see hg.openjdk.java.net/jdk10/jdk10/jdk/file/72f33dbfcf3b/src/…Eleph

© 2022 - 2024 — McMap. All rights reserved.