Search code examples
jpaeclipselinkjpa-2.0toplink

@ManyToOne where target PK is part of FK


I'm following an article to configure the relationship between two entities:

+-----------------------------+      +---------------------------------+
| redacted_identifier         |      | redacted_identifier_year        |
+-----------------------------+      +---------------------------------+
| redacted_identifier_id (PK) |      | redacted_identifier_id (PK, FK) |
| ...                         |      | year (PK)                       |
+-----------------------------+      | ...                             |
                                     +---------------------------------+

I've omitted fields not relevant to the question and redacted the original names, please bear with potential inconsistencies. Redacted names keep the same length, more on that later.

I've created these classes:

@Entity
@Table(identifier = "REDACTED_IDENTIFIER")
@SequenceGenerator(identifier = "REDACTED_IDENTIFIER_ID_SEQ", sequenceIdentifier = "REDACTED_IDENTIFIER_ID_SEQ", allocationSize = 1)
public class RedactedIdentifier implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "REDACTED_IDENTIFIER_ID_SEQ")
    @Column(identifier="REDACTED_IDENTIFIER_ID", nullable = false)
    private Long redactedIdentifierId;

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<RedactedIdentifierYear> redactedIdentifierYearList;

    public RedactedIdentifier() {
    }

    public void setRedactedIdentifierId(Long redactedIdentifierId) {
        this.redactedIdentifierId = redactedIdentifierId;
    }

    public Long getRedactedIdentifierId() {
        return redactedIdentifierId;
    }

    public List<RedactedIdentifierYear> getRedactedIdentifierYearList() {
        if(this.redactedIdentifierYearList == null){
            this.redactedIdentifierYearList = new ArrayList<RedactedIdentifierYear>();
        }
        return this.redactedIdentifierYearList;
    }

    public void setRedactedIdentifierYearList(List<RedactedIdentifierYear> redactedIdentifierYearList) {
        this.redactedIdentifierYearList = redactedIdentifierYearList;
    }

    public RedactedIdentifierYear addRedactedIdentifierYear(RedactedIdentifierYear redactedIdentifierYear) {
        getRedactedIdentifierYearList().add(redactedIdentifierYear);
        redactedIdentifierYear.setRedactedIdentifier(this);
        return redactedIdentifierYear;
    }

    public RedactedIdentifierYear removeRedactedIdentifierYear(RedactedIdentifierYear redactedIdentifierYear) {
        getRedactedIdentifierYearList().remove(redactedIdentifierYear);
        redactedIdentifierYear.setRedactedIdentifier(null);
        return redactedIdentifierYear;
    }
}
@Entity
@Table(identifier = "REDACTED_IDENTIFIER_YEAR")
@IdClass(RedactedIdentifierYearPK.class)
public class RedactedIdentifierYear implements Serializable {
    @Id
    @Column(nullable = false)
    private Integer year;

    @Id
    @ManyToOne
    @JoinColumn(identifier = "REDACTED_IDENTIFIER_ID", referencedColumnIdentifier = "REDACTED_IDENTIFIER_ID")
    private RedactedIdentifier redactedIdentifier;

    public RedactedIdentifierYear() {
    }

    public RedactedIdentifierYear(Integer year) {
        this.year = year;
    }

    public void setYear(Integer year) {
        this.year = year;
    }

    public Integer getYear() {
        return year;
    }

    public void setRedactedIdentifier(RedactedIdentifier redactedIdentifier) {
        this.redactedIdentifier = redactedIdentifier;
    }

    public RedactedIdentifier getRedactedIdentifier() {
        return redactedIdentifier;
    }
}
public class RedactedIdentifierYearPK implements Serializable {
    private Long redactedIdentifier;
    private Integer year;

    public RedactedIdentifierYearPK() {
    }

    public RedactedIdentifierYearPK(Long redactedIdentifier, Integer year) {
        this.redactedIdentifier = redactedIdentifier;
        this.year = year;
    }

    public boolean equals(Object other) {
        if (other instanceof RedactedIdentifierYearPK) {
            final RedactedIdentifierYearPK otherRedactedIdentifierYearPK = (RedactedIdentifierYearPK)other;
            final boolean areEqual = otherRedactedIdentifierYearPK.redactedIdentifier.equals(redactedIdentifier)
                && otherRedactedIdentifierYearPK.year.equals(year);
            return areEqual;
        }
        return false;
    }

    public int hashCode() {
        return super.hashCode();
    }

    public void setRedactedIdentifier(Long redactedIdentifier) {
        this.redactedIdentifier = redactedIdentifier;
    }

    public Long getRedactedIdentifier() {
        return redactedIdentifier;
    }

    public void setYear(Integer year) {
        this.year = year;
    }

    public Integer getYear() {
        return year;
    }
}

Then I use them as follows:

public float assignRedactedIdentifierYear(RedactedIdentifier redactedIdentifier, Integer year) {
    RedactedIdentifierYear redactedIdentifierYear = new RedactedIdentifierYear(year);
    redactedIdentifier.addRedactedIdentifierYear(redactedIdentifierYear);
}

But when flush happens, it tries to write into a completely wrong table:

INSERT INTO REDACTED_IDENTIFIER_REDACTED_IDENTIFIER_YEAR (REDACTE_REDACTED_IDENTIFIER_ID, REDACTEDIDENTIFIERYEARLIST_RED, REDACTEDIDENTIFIERYEARLIST_YEA) VALUES (?, ?, ?)
[params=(long) 2082, (long) 2082, (int) 2020]
               ^^^^         ^^^^
               Correct ID from sequence (twice)

Some names have been clearly truncated to 30 chars to comply with RDBMS (Oracle).

What's wrong/missing in my entity design?


Solution

  • I was missing a mappedBy property in the parent entity. The article I was checking probably takes it for granted:

    @OneToMany(mappedBy = "redactedIdentifier", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<RedactedIdentifierYear> redactedIdentifierYearList;