hibernatejpaeclipselinkopenjpadatanucleus

Nested ID class required to be named like relationship, even though not being a derived identifier?


There's chapter 10 in the book "Pro JPA 2 in Java EE 8", section called "Multiple Mapped Attributes", there the well-known department-project example using @IdClass, full example code at https://github.com/Apress/pro-jpa-2-in-java-ee-8/tree/master/examples/Chapter10/08-multipleMappedAttributes/src/model/examples/model.

Short story below.

Project entity:

@Entity 
@IdClass(ProjectId.class)
public class Project
{    
    @Id 
    private String name;

    @Id
    @ManyToOne 
    @JoinColumns({
       @JoinColumn(name="DEPT_NUM", referencedColumnName="NUM"),
       @JoinColumn(name="DEPT_CTRY", referencedColumnName="COUNTRY")
    })    
    private Department dept;

    // ...
}

Project ID class:

public class ProjectId implements Serializable
{
    private String name;
    private DeptId dept;     <-- named dept to conform with derived identifier naming conventions

    public ProjectId() {}
    
    public ProjectId(DeptId deptId, String name)
    {
        this.dept = deptId;  <-- naming "mismatch"
        this.name = name;
    }

    // ...
}

Department entity:

@Entity
@IdClass(DeptId.class)
public class Department
{
    @Id
    @Column(name="NUM")
    private int number;

    @Id
    private String country;

    private String name;

    // ...
}

Department ID class:

public class DeptId implements Serializable
{
    private int number;
    private String country;

    // ...
}

This is nothing special actually. Notice the ProjectId class having a nested composite PK class field private DeptId dept;.

Now to my question...

A few pages later in the book, at the end of section "Using EmbeddedId", there's a larger note / sub section named "ALTERNATIVE TO DERIVED IDENTIFIERS" with the text (you don't necessarily need to read the whole blurp to answer the question really, I post this for completeness only):

The @MapsId annotation and the ability to apply @Id to relationship attributes was introduced in JPA 2.0 to improve the situation that existed in JPA 1.0. At that time, only the one-to-one shared primary key scenario was specified using the @PrimaryKeyJoinColumn annotation (using the Id annotation is the preferred and recommended method going forward.

Although there was no specified way to solve the general case of including a foreign key in an identifier, it was generally supported through the practice of adding one or more additional (redundant) fields to the dependent entity. Each added field would hold a foreign key to the related entity, and, because both the added field and the relationship would be mapped to the same join column(s), one or the other of the two would need to marked as read-only (see "Read-Only Mappings" section), or not updatable or insertable. The following example shows how Listing 10-17 [author not: Projectentity above!] would be done using JPA 1.0. The ID class would be the same. Since the deptNumber and deptCountry attributes are identifier attributes, and can't be changed in the database, there is no need to set their updatability to false.

@Entity 
@IdClass(ProjectId.class)
public class Project
{    
    @Id 
    private String name;

    @Id
    @Column(name = "DEPT_NUM", insertable = false)
    private int deptNumber;

    @Id 
    @Column(name = "DEPT_NUM", insertable = false)
    private String deptCountry;

    @ManyToOne 
    @JoinColumns({
       @JoinColumn(name="DEPT_NUM", referencedColumnName="NUM"),
       @JoinColumn(name="DEPT_CTRY", referencedColumnName="COUNTRY")
    })    
    private Department dept;

    // ...
}

So, this is the "JPA 1.0" way. According to

The ID class would be the same.

this would mean, that the ProjectId ID class is as seen above, however, JPA 1.0 did not allow nested classes yet. So, what the book authors probably wanted to say here is that the backward-compatible, non-derived identifier mappings would look like this.

Now the question:

What does the JPA 2.0 ProjectId look like for the redundant @id field mappings, that is with nested ID class? It's not displayed in the book... 🤷‍♂️

public class ProjectId implements Serializable
{
    private String name;
    private DeptId ?;     <-- dept or deptId?

    public ProjectId() {}
    
    public ProjectId(DeptId deptId, String name)
    {
        this.? = deptId;
        this.name = name;
    }

    // ...
}

Since the relationship private Department dept; is no longer mapped as a derived identifier using @Id, is the name of the nested ID class field with type DeptId still required to be named dept or can it basically be named anything, e.g. deptId or department?

This is important. If the name would have to keep the name of the relationship, the rule once laid out for derived identifiers would also apply to non-derived identifier mappings using JPA 2.x nested ID classes...

PS: Note that this is a JPA spec question that I couldn't answer on my own. Trying this with EclipseLink or any other JPA provider will also not guarantee that the described behavior was implemented correctly. Hibernate for example throws an AnnotationException when the DeptId field in ProjectId has a different name than the relationship in the entity.

I added the JPA provider tags to attract the right people to maybe help me with this. Thanks.


Solution

  • As per anything to do with JPA, while the book is nice (and written by very knowledgeable people, one of whom was directly involved in writing the spec), if there is anything in doubt, go to the spec itself for clarification. Derived ID is covered with examples in section 2.4.1 and would clear this up for you. It requires a ProjectId.class to use the SAME NAME as the ID value in the entity, and the SAME TYPE as the ID it refers to - same format as you did for DeptId. In this case, it would need to be:

    public class ProjectId implements Serializable
    {
        private String name;
        private DeptId dept;
        // ...
    }
    

    As for the JPA 1.0 usage - The statement on ID class being the same probably meant that the ID class for this would require the same 3 ID fields (and types) that matched the @ID annotations - same as they would now for such a class definition. If you mark it as an @ID, it MUST be represented in the ID class.